|
| 1 | +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
| 2 | +// file at the top-level directory of this distribution and at |
| 3 | +// http://rust-lang.org/COPYRIGHT. |
| 4 | +// |
| 5 | +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 6 | +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 7 | +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| 8 | +// option. This file may not be copied, modified, or distributed |
| 9 | +// except according to those terms. |
| 10 | + |
| 11 | +//! MSVC-specific logic for linkers and such. |
| 12 | +//! |
| 13 | +//! This module contains a cross-platform interface but has a blank unix |
| 14 | +//! implementation. The Windows implementation builds on top of Windows native |
| 15 | +//! libraries (reading registry keys), so it otherwise wouldn't link on unix. |
| 16 | +//! |
| 17 | +//! Note that we don't have much special logic for finding the system linker on |
| 18 | +//! any other platforms, so it may seem a little odd to single out MSVC to have |
| 19 | +//! a good deal of code just to find the linker. Unlike Unix systems, however, |
| 20 | +//! the MSVC linker is not in the system PATH by default. It also additionally |
| 21 | +//! needs a few environment variables or command line flags to be able to link |
| 22 | +//! against system libraries. |
| 23 | +//! |
| 24 | +//! In order to have a nice smooth experience on Windows, the logic in this file |
| 25 | +//! is here to find the MSVC linker and set it up in the default configuration |
| 26 | +//! one would need to set up anyway. This means that the Rust compiler can be |
| 27 | +//! run not only in the developer shells of MSVC but also the standard cmd.exe |
| 28 | +//! shell or MSYS shells. |
| 29 | +//! |
| 30 | +//! As a high-level note, all logic in this module for looking up various |
| 31 | +//! paths/files is copied over from Clang in its MSVCToolChain.cpp file, but |
| 32 | +//! comments can also be found below leading through the various code paths. |
| 33 | +
|
| 34 | +use std::process::Command; |
| 35 | +use session::Session; |
| 36 | + |
| 37 | +#[cfg(windows)] |
| 38 | +mod registry; |
| 39 | + |
| 40 | +#[cfg(windows)] |
| 41 | +pub fn link_exe_cmd(sess: &Session) -> Command { |
| 42 | + use std::env; |
| 43 | + use std::ffi::OsString; |
| 44 | + use std::fs; |
| 45 | + use std::path::PathBuf; |
| 46 | + use self::registry::{RegistryKey, LOCAL_MACHINE}; |
| 47 | + |
| 48 | + // When finding the link.exe binary the 32-bit version is at the top level |
| 49 | + // but the versions to cross to other architectures are stored in |
| 50 | + // sub-folders. Unknown architectures also just bail out early to return the |
| 51 | + // standard `link.exe` command. |
| 52 | + let extra = match &sess.target.target.arch[..] { |
| 53 | + "x86" => "", |
| 54 | + "x86_64" => "amd64", |
| 55 | + "arm" => "arm", |
| 56 | + _ => return Command::new("link.exe"), |
| 57 | + }; |
| 58 | + |
| 59 | + let vs_install_dir = get_vs_install_dir(); |
| 60 | + |
| 61 | + // First up, we need to find the `link.exe` binary itself, and there's a few |
| 62 | + // locations that we can look. First up is the standard VCINSTALLDIR |
| 63 | + // environment variable which is normally set by the vcvarsall.bat file. If |
| 64 | + // an environment is set up manually by whomever's driving the compiler then |
| 65 | + // we shouldn't muck with that decision and should instead respect that. |
| 66 | + // |
| 67 | + // Next up is looking in PATH itself. Here we look for `cl.exe` and then |
| 68 | + // assume that `link.exe` is next to it if we find it. Note that we look for |
| 69 | + // `cl.exe` because MinGW ships /usr/bin/link.exe which is normally found in |
| 70 | + // PATH but we're not interested in finding that. |
| 71 | + // |
| 72 | + // Finally we read the Windows registry to discover the VS install root. |
| 73 | + // From here we probe for `link.exe` just to make sure that it exists. |
| 74 | + let mut cmd = env::var_os("VCINSTALLDIR").and_then(|dir| { |
| 75 | + let mut p = PathBuf::from(dir); |
| 76 | + p.push("bin"); |
| 77 | + p.push(extra); |
| 78 | + p.push("link.exe"); |
| 79 | + if fs::metadata(&p).is_ok() {Some(p)} else {None} |
| 80 | + }).or_else(|| { |
| 81 | + env::var_os("PATH").and_then(|path| { |
| 82 | + env::split_paths(&path).find(|path| { |
| 83 | + fs::metadata(&path.join("cl.exe")).is_ok() |
| 84 | + }).map(|p| { |
| 85 | + p.join("link.exe") |
| 86 | + }) |
| 87 | + }) |
| 88 | + }).or_else(|| { |
| 89 | + vs_install_dir.as_ref().and_then(|p| { |
| 90 | + let mut p = p.join("VC/bin"); |
| 91 | + p.push(extra); |
| 92 | + p.push("link.exe"); |
| 93 | + if fs::metadata(&p).is_ok() {Some(p)} else {None} |
| 94 | + }) |
| 95 | + }).map(|linker| { |
| 96 | + Command::new(linker) |
| 97 | + }).unwrap_or_else(|| { |
| 98 | + Command::new("link.exe") |
| 99 | + }); |
| 100 | + |
| 101 | + // The MSVC linker uses the LIB environment variable as the default lookup |
| 102 | + // path for libraries. This environment variable is normally set up by the |
| 103 | + // VS shells, so we only want to start adding our own pieces if it's not |
| 104 | + // set. |
| 105 | + // |
| 106 | + // If we're adding our own pieces, then we need to add two primary |
| 107 | + // directories to the default search path for the linker. The first is in |
| 108 | + // the VS install direcotry and the next is the Windows SDK directory. |
| 109 | + if env::var_os("LIB").is_none() { |
| 110 | + if let Some(mut vs_install_dir) = vs_install_dir { |
| 111 | + vs_install_dir.push("VC/lib"); |
| 112 | + vs_install_dir.push(extra); |
| 113 | + let mut arg = OsString::from("/LIBPATH:"); |
| 114 | + arg.push(&vs_install_dir); |
| 115 | + cmd.arg(arg); |
| 116 | + } |
| 117 | + if let Some(path) = get_windows_sdk_lib_path(sess) { |
| 118 | + let mut arg = OsString::from("/LIBPATH:"); |
| 119 | + arg.push(&path); |
| 120 | + cmd.arg(arg); |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + return cmd; |
| 125 | + |
| 126 | + // When looking for the Visual Studio installation directory we look in a |
| 127 | + // number of locations in varying degrees of precedence: |
| 128 | + // |
| 129 | + // 1. The Visual Studio registry keys |
| 130 | + // 2. The Visual Studio Express registry keys |
| 131 | + // 3. A number of somewhat standard environment variables |
| 132 | + // |
| 133 | + // If we find a hit from any of these keys then we strip off the IDE/Tools |
| 134 | + // folders which are typically found at the end. |
| 135 | + // |
| 136 | + // As a final note, when we take a look at the registry keys they're |
| 137 | + // typically found underneath the version of what's installed, but we don't |
| 138 | + // quite know what's installed. As a result we probe all sub-keys of the two |
| 139 | + // keys we're looking at to find out the maximum version of what's installed |
| 140 | + // and we use that root directory. |
| 141 | + fn get_vs_install_dir() -> Option<PathBuf> { |
| 142 | + LOCAL_MACHINE.open(r"SOFTWARE\Microsoft\VisualStudio".as_ref()).or_else(|_| { |
| 143 | + LOCAL_MACHINE.open(r"SOFTWARE\Microsoft\VCExpress".as_ref()) |
| 144 | + }).ok().and_then(|key| { |
| 145 | + max_version(&key).and_then(|(_vers, key)| { |
| 146 | + key.query_str("InstallDir").ok() |
| 147 | + }) |
| 148 | + }).or_else(|| { |
| 149 | + env::var_os("VS120COMNTOOLS") |
| 150 | + }).or_else(|| { |
| 151 | + env::var_os("VS100COMNTOOLS") |
| 152 | + }).or_else(|| { |
| 153 | + env::var_os("VS90COMNTOOLS") |
| 154 | + }).or_else(|| { |
| 155 | + env::var_os("VS80COMNTOOLS") |
| 156 | + }).map(PathBuf::from).and_then(|mut dir| { |
| 157 | + if dir.ends_with("Common7/IDE") || dir.ends_with("Common7/Tools") { |
| 158 | + dir.pop(); |
| 159 | + dir.pop(); |
| 160 | + Some(dir) |
| 161 | + } else { |
| 162 | + None |
| 163 | + } |
| 164 | + }) |
| 165 | + } |
| 166 | + |
| 167 | + // Given a registry key, look at all the sub keys and find the one which has |
| 168 | + // the maximal numeric value. |
| 169 | + // |
| 170 | + // Returns the name of the maximal key as well as the opened maximal key. |
| 171 | + fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> { |
| 172 | + let mut max_vers = 0; |
| 173 | + let mut max_key = None; |
| 174 | + for subkey in key.iter().filter_map(|k| k.ok()) { |
| 175 | + let val = subkey.to_str().and_then(|s| { |
| 176 | + s.trim_left_matches("v").replace(".", "").parse().ok() |
| 177 | + }); |
| 178 | + let val = match val { |
| 179 | + Some(s) => s, |
| 180 | + None => continue, |
| 181 | + }; |
| 182 | + if val > max_vers { |
| 183 | + if let Ok(k) = key.open(&subkey) { |
| 184 | + max_vers = val; |
| 185 | + max_key = Some((subkey, k)); |
| 186 | + } |
| 187 | + } |
| 188 | + } |
| 189 | + return max_key |
| 190 | + } |
| 191 | + |
| 192 | + fn get_windows_sdk_lib_path(sess: &Session) -> Option<PathBuf> { |
| 193 | + let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows"; |
| 194 | + let key = LOCAL_MACHINE.open(key.as_ref()); |
| 195 | + let (n, k) = match key.ok().as_ref().and_then(max_version) { |
| 196 | + Some(p) => p, |
| 197 | + None => return None, |
| 198 | + }; |
| 199 | + let mut parts = n.to_str().unwrap().trim_left_matches("v").splitn(2, "."); |
| 200 | + let major = parts.next().unwrap().parse::<usize>().unwrap(); |
| 201 | + let _minor = parts.next().unwrap().parse::<usize>().unwrap(); |
| 202 | + let path = match k.query_str("InstallationFolder") { |
| 203 | + Ok(p) => PathBuf::from(p).join("Lib"), |
| 204 | + Err(..) => return None, |
| 205 | + }; |
| 206 | + if major <= 7 { |
| 207 | + // In Windows SDK 7.x, x86 libraries are directly in the Lib folder, |
| 208 | + // x64 libraries are inside, and it's not necessary to link agains |
| 209 | + // the SDK 7.x when targeting ARM or other architectures. |
| 210 | + let x86 = match &sess.target.target.arch[..] { |
| 211 | + "x86" => true, |
| 212 | + "x86_64" => false, |
| 213 | + _ => return None, |
| 214 | + }; |
| 215 | + Some(if x86 {path} else {path.join("x64")}) |
| 216 | + } else { |
| 217 | + // Windows SDK 8.x installes libraries in a folder whose names |
| 218 | + // depend on the version of the OS you're targeting. By default |
| 219 | + // choose the newest, which usually corresponds to the version of |
| 220 | + // the OS you've installed the SDK on. |
| 221 | + let extra = match &sess.target.target.arch[..] { |
| 222 | + "x86" => "x86", |
| 223 | + "x86_64" => "x64", |
| 224 | + "arm" => "arm", |
| 225 | + _ => return None, |
| 226 | + }; |
| 227 | + ["winv6.3", "win8", "win7"].iter().map(|p| path.join(p)).find(|part| { |
| 228 | + fs::metadata(part).is_ok() |
| 229 | + }).map(|path| { |
| 230 | + path.join("um").join(extra) |
| 231 | + }) |
| 232 | + } |
| 233 | + } |
| 234 | +} |
| 235 | + |
| 236 | +#[cfg(not(windows))] |
| 237 | +pub fn link_exe_cmd(_sess: &Session) -> Command { |
| 238 | + Command::new("link.exe") |
| 239 | +} |
0 commit comments