diff --git a/mk/target.mk b/mk/target.mk index c398950965f..c2de9af39c7 100644 --- a/mk/target.mk +++ b/mk/target.mk @@ -249,11 +249,9 @@ endef $(foreach host,$(CFG_HOST), \ $(foreach target,$(CFG_TARGET), \ - $(foreach stage,$(STAGES), \ - $(foreach crate,$(CRATES), \ - $(eval $(call SETUP_LIB_MSVC_ENV_VARS,$(stage),$(target),$(host),$(crate))))))) + $(foreach crate,$(CRATES), \ + $(eval $(call SETUP_LIB_MSVC_ENV_VARS,0,$(target),$(host),$(crate)))))) $(foreach host,$(CFG_HOST), \ $(foreach target,$(CFG_TARGET), \ - $(foreach stage,$(STAGES), \ - $(foreach tool,$(TOOLS), \ - $(eval $(call SETUP_TOOL_MSVC_ENV_VARS,$(stage),$(target),$(host),$(tool))))))) + $(foreach tool,$(TOOLS), \ + $(eval $(call SETUP_TOOL_MSVC_ENV_VARS,0,$(target),$(host),$(tool)))))) diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index cf5feabcc57..e0495226d90 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -12,6 +12,7 @@ use super::archive::{Archive, ArchiveBuilder, ArchiveConfig, METADATA_FILENAME}; use super::linker::{Linker, GnuLinker, MsvcLinker}; use super::rpath::RPathConfig; use super::rpath; +use super::msvc; use super::svh::Svh; use session::config; use session::config::NoDebugInfo; @@ -358,10 +359,14 @@ pub fn mangle_internal_name_by_path_and_seq(path: PathElems, flav: &str) -> Stri mangle(path.chain(Some(gensym_name(flav))), None) } -pub fn get_cc_prog(sess: &Session) -> String { - match sess.opts.cg.linker { - Some(ref linker) => return linker.to_string(), - None => sess.target.target.options.linker.clone(), +pub fn get_linker(sess: &Session) -> (String, Command) { + if let Some(ref linker) = sess.opts.cg.linker { + (linker.clone(), Command::new(linker)) + } else if sess.target.target.options.is_like_msvc { + ("link.exe".to_string(), msvc::link_exe_cmd(sess)) + } else { + (sess.target.target.options.linker.clone(), + Command::new(&sess.target.target.options.linker)) } } @@ -807,8 +812,7 @@ fn link_natively(sess: &Session, trans: &CrateTranslation, dylib: bool, let tmpdir = TempDir::new("rustc").ok().expect("needs a temp dir"); // The invocations of cc share some flags across platforms - let pname = get_cc_prog(sess); - let mut cmd = Command::new(&pname); + let (pname, mut cmd) = get_linker(sess); cmd.env("PATH", command_path(sess)); let root = sess.target_filesearch(PathKind::Native).get_lib_path(); diff --git a/src/librustc_trans/back/msvc/mod.rs b/src/librustc_trans/back/msvc/mod.rs new file mode 100644 index 00000000000..0077e7eed52 --- /dev/null +++ b/src/librustc_trans/back/msvc/mod.rs @@ -0,0 +1,239 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! MSVC-specific logic for linkers and such. +//! +//! This module contains a cross-platform interface but has a blank unix +//! implementation. The Windows implementation builds on top of Windows native +//! libraries (reading registry keys), so it otherwise wouldn't link on unix. +//! +//! Note that we don't have much special logic for finding the system linker on +//! any other platforms, so it may seem a little odd to single out MSVC to have +//! a good deal of code just to find the linker. Unlike Unix systems, however, +//! the MSVC linker is not in the system PATH by default. It also additionally +//! needs a few environment variables or command line flags to be able to link +//! against system libraries. +//! +//! In order to have a nice smooth experience on Windows, the logic in this file +//! is here to find the MSVC linker and set it up in the default configuration +//! one would need to set up anyway. This means that the Rust compiler can be +//! run not only in the developer shells of MSVC but also the standard cmd.exe +//! shell or MSYS shells. +//! +//! As a high-level note, all logic in this module for looking up various +//! paths/files is copied over from Clang in its MSVCToolChain.cpp file, but +//! comments can also be found below leading through the various code paths. + +use std::process::Command; +use session::Session; + +#[cfg(windows)] +mod registry; + +#[cfg(windows)] +pub fn link_exe_cmd(sess: &Session) -> Command { + use std::env; + use std::ffi::OsString; + use std::fs; + use std::path::PathBuf; + use self::registry::{RegistryKey, LOCAL_MACHINE}; + + // When finding the link.exe binary the 32-bit version is at the top level + // but the versions to cross to other architectures are stored in + // sub-folders. Unknown architectures also just bail out early to return the + // standard `link.exe` command. + let extra = match &sess.target.target.arch[..] { + "x86" => "", + "x86_64" => "amd64", + "arm" => "arm", + _ => return Command::new("link.exe"), + }; + + let vs_install_dir = get_vs_install_dir(); + + // First up, we need to find the `link.exe` binary itself, and there's a few + // locations that we can look. First up is the standard VCINSTALLDIR + // environment variable which is normally set by the vcvarsall.bat file. If + // an environment is set up manually by whomever's driving the compiler then + // we shouldn't muck with that decision and should instead respect that. + // + // Next up is looking in PATH itself. Here we look for `cl.exe` and then + // assume that `link.exe` is next to it if we find it. Note that we look for + // `cl.exe` because MinGW ships /usr/bin/link.exe which is normally found in + // PATH but we're not interested in finding that. + // + // Finally we read the Windows registry to discover the VS install root. + // From here we probe for `link.exe` just to make sure that it exists. + let mut cmd = env::var_os("VCINSTALLDIR").and_then(|dir| { + let mut p = PathBuf::from(dir); + p.push("bin"); + p.push(extra); + p.push("link.exe"); + if fs::metadata(&p).is_ok() {Some(p)} else {None} + }).or_else(|| { + env::var_os("PATH").and_then(|path| { + env::split_paths(&path).find(|path| { + fs::metadata(&path.join("cl.exe")).is_ok() + }).map(|p| { + p.join("link.exe") + }) + }) + }).or_else(|| { + vs_install_dir.as_ref().and_then(|p| { + let mut p = p.join("VC/bin"); + p.push(extra); + p.push("link.exe"); + if fs::metadata(&p).is_ok() {Some(p)} else {None} + }) + }).map(|linker| { + Command::new(linker) + }).unwrap_or_else(|| { + Command::new("link.exe") + }); + + // The MSVC linker uses the LIB environment variable as the default lookup + // path for libraries. This environment variable is normally set up by the + // VS shells, so we only want to start adding our own pieces if it's not + // set. + // + // If we're adding our own pieces, then we need to add two primary + // directories to the default search path for the linker. The first is in + // the VS install direcotry and the next is the Windows SDK directory. + if env::var_os("LIB").is_none() { + if let Some(mut vs_install_dir) = vs_install_dir { + vs_install_dir.push("VC/lib"); + vs_install_dir.push(extra); + let mut arg = OsString::from("/LIBPATH:"); + arg.push(&vs_install_dir); + cmd.arg(arg); + } + if let Some(path) = get_windows_sdk_lib_path(sess) { + let mut arg = OsString::from("/LIBPATH:"); + arg.push(&path); + cmd.arg(arg); + } + } + + return cmd; + + // When looking for the Visual Studio installation directory we look in a + // number of locations in varying degrees of precedence: + // + // 1. The Visual Studio registry keys + // 2. The Visual Studio Express registry keys + // 3. A number of somewhat standard environment variables + // + // If we find a hit from any of these keys then we strip off the IDE/Tools + // folders which are typically found at the end. + // + // As a final note, when we take a look at the registry keys they're + // typically found underneath the version of what's installed, but we don't + // quite know what's installed. As a result we probe all sub-keys of the two + // keys we're looking at to find out the maximum version of what's installed + // and we use that root directory. + fn get_vs_install_dir() -> Option { + LOCAL_MACHINE.open(r"SOFTWARE\Microsoft\VisualStudio".as_ref()).or_else(|_| { + LOCAL_MACHINE.open(r"SOFTWARE\Microsoft\VCExpress".as_ref()) + }).ok().and_then(|key| { + max_version(&key).and_then(|(_vers, key)| { + key.query_str("InstallDir").ok() + }) + }).or_else(|| { + env::var_os("VS120COMNTOOLS") + }).or_else(|| { + env::var_os("VS100COMNTOOLS") + }).or_else(|| { + env::var_os("VS90COMNTOOLS") + }).or_else(|| { + env::var_os("VS80COMNTOOLS") + }).map(PathBuf::from).and_then(|mut dir| { + if dir.ends_with("Common7/IDE") || dir.ends_with("Common7/Tools") { + dir.pop(); + dir.pop(); + Some(dir) + } else { + None + } + }) + } + + // Given a registry key, look at all the sub keys and find the one which has + // the maximal numeric value. + // + // Returns the name of the maximal key as well as the opened maximal key. + fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> { + let mut max_vers = 0; + let mut max_key = None; + for subkey in key.iter().filter_map(|k| k.ok()) { + let val = subkey.to_str().and_then(|s| { + s.trim_left_matches("v").replace(".", "").parse().ok() + }); + let val = match val { + Some(s) => s, + None => continue, + }; + if val > max_vers { + if let Ok(k) = key.open(&subkey) { + max_vers = val; + max_key = Some((subkey, k)); + } + } + } + return max_key + } + + fn get_windows_sdk_lib_path(sess: &Session) -> Option { + let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows"; + let key = LOCAL_MACHINE.open(key.as_ref()); + let (n, k) = match key.ok().as_ref().and_then(max_version) { + Some(p) => p, + None => return None, + }; + let mut parts = n.to_str().unwrap().trim_left_matches("v").splitn(2, "."); + let major = parts.next().unwrap().parse::().unwrap(); + let _minor = parts.next().unwrap().parse::().unwrap(); + let path = match k.query_str("InstallationFolder") { + Ok(p) => PathBuf::from(p).join("Lib"), + Err(..) => return None, + }; + if major <= 7 { + // In Windows SDK 7.x, x86 libraries are directly in the Lib folder, + // x64 libraries are inside, and it's not necessary to link agains + // the SDK 7.x when targeting ARM or other architectures. + let x86 = match &sess.target.target.arch[..] { + "x86" => true, + "x86_64" => false, + _ => return None, + }; + Some(if x86 {path} else {path.join("x64")}) + } else { + // Windows SDK 8.x installes libraries in a folder whose names + // depend on the version of the OS you're targeting. By default + // choose the newest, which usually corresponds to the version of + // the OS you've installed the SDK on. + let extra = match &sess.target.target.arch[..] { + "x86" => "x86", + "x86_64" => "x64", + "arm" => "arm", + _ => return None, + }; + ["winv6.3", "win8", "win7"].iter().map(|p| path.join(p)).find(|part| { + fs::metadata(part).is_ok() + }).map(|path| { + path.join("um").join(extra) + }) + } + } +} + +#[cfg(not(windows))] +pub fn link_exe_cmd(_sess: &Session) -> Command { + Command::new("link.exe") +} diff --git a/src/librustc_trans/back/msvc/registry.rs b/src/librustc_trans/back/msvc/registry.rs new file mode 100644 index 00000000000..97fd7f99d19 --- /dev/null +++ b/src/librustc_trans/back/msvc/registry.rs @@ -0,0 +1,170 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::io; +use std::ffi::{OsString, OsStr}; +use std::os::windows::prelude::*; +use std::ops::RangeFrom; +use libc::{DWORD, LPCWSTR, LONG, LPDWORD, LPBYTE, ERROR_SUCCESS}; + +const HKEY_LOCAL_MACHINE: HKEY = 0x80000002 as HKEY; +const KEY_WOW64_32KEY: REGSAM = 0x0200; +const KEY_READ: REGSAM = (STANDARD_RIGTS_READ | KEY_QUERY_VALUE | + KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY) & !SYNCHRONIZE; +const STANDARD_RIGTS_READ: REGSAM = READ_CONTROL; +const READ_CONTROL: REGSAM = 0x00020000; +const KEY_QUERY_VALUE: REGSAM = 0x0001; +const KEY_ENUMERATE_SUB_KEYS: REGSAM = 0x0008; +const KEY_NOTIFY: REGSAM = 0x0010; +const SYNCHRONIZE: REGSAM = 0x00100000; +const REG_SZ: DWORD = 1; +const ERROR_NO_MORE_ITEMS: DWORD = 259; + +enum __HKEY__ {} +pub type HKEY = *mut __HKEY__; +pub type PHKEY = *mut HKEY; +pub type REGSAM = DWORD; +pub type LPWSTR = *mut u16; +pub type PFILETIME = *mut (); + +#[link(name = "advapi32")] +extern "system" { + fn RegOpenKeyExW(hKey: HKEY, + lpSubKey: LPCWSTR, + ulOptions: DWORD, + samDesired: REGSAM, + phkResult: PHKEY) -> LONG; + fn RegQueryValueExW(hKey: HKEY, + lpValueName: LPCWSTR, + lpReserved: LPDWORD, + lpType: LPDWORD, + lpData: LPBYTE, + lpcbData: LPDWORD) -> LONG; + fn RegEnumKeyExW(hKey: HKEY, + dwIndex: DWORD, + lpName: LPWSTR, + lpcName: LPDWORD, + lpReserved: LPDWORD, + lpClass: LPWSTR, + lpcClass: LPDWORD, + lpftLastWriteTime: PFILETIME) -> LONG; + fn RegCloseKey(hKey: HKEY) -> LONG; +} + +pub struct RegistryKey(Repr); + +struct OwnedKey(HKEY); + +enum Repr { + Const(HKEY), + Owned(OwnedKey), +} + +pub struct Iter<'a> { + idx: RangeFrom, + key: &'a RegistryKey, +} + +unsafe impl Sync for RegistryKey {} +unsafe impl Send for RegistryKey {} + +pub static LOCAL_MACHINE: RegistryKey = RegistryKey(Repr::Const(HKEY_LOCAL_MACHINE)); + +impl RegistryKey { + fn raw(&self) -> HKEY { + match self.0 { + Repr::Const(val) => val, + Repr::Owned(ref val) => val.0, + } + } + + pub fn open(&self, key: &OsStr) -> io::Result { + let key = key.encode_wide().chain(Some(0)).collect::>(); + let mut ret = 0 as *mut _; + let err = unsafe { + RegOpenKeyExW(self.raw(), key.as_ptr(), 0, + KEY_READ | KEY_WOW64_32KEY, &mut ret) + }; + if err == ERROR_SUCCESS { + Ok(RegistryKey(Repr::Owned(OwnedKey(ret)))) + } else { + Err(io::Error::from_raw_os_error(err as i32)) + } + } + + pub fn iter(&self) -> Iter { + Iter { idx: 0.., key: self } + } + + pub fn query_str(&self, name: &str) -> io::Result { + let name: &OsStr = name.as_ref(); + let name = name.encode_wide().chain(Some(0)).collect::>(); + let mut len = 0; + let mut kind = 0; + unsafe { + let err = RegQueryValueExW(self.raw(), name.as_ptr(), 0 as *mut _, + &mut kind, 0 as *mut _, &mut len); + if err != ERROR_SUCCESS { + return Err(io::Error::from_raw_os_error(err as i32)) + } + if kind != REG_SZ { + return Err(io::Error::new(io::ErrorKind::Other, + "registry key wasn't a string")) + } + + // The length here is the length in bytes, but we're using wide + // characters so we need to be sure to halve it for the capacity + // passed in. + let mut v = Vec::with_capacity(len as usize / 2); + let err = RegQueryValueExW(self.raw(), name.as_ptr(), 0 as *mut _, + 0 as *mut _, v.as_mut_ptr() as *mut _, + &mut len); + if err != ERROR_SUCCESS { + return Err(io::Error::from_raw_os_error(err as i32)) + } + v.set_len(len as usize / 2); + + // Some registry keys may have a terminating nul character, but + // we're not interested in that, so chop it off if it's there. + if v[v.len() - 1] == 0 { + v.pop(); + } + Ok(OsString::from_wide(&v)) + } + } +} + +impl Drop for OwnedKey { + fn drop(&mut self) { + unsafe { RegCloseKey(self.0); } + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = io::Result; + + fn next(&mut self) -> Option> { + self.idx.next().and_then(|i| unsafe { + let mut v = Vec::with_capacity(256); + let mut len = v.capacity() as DWORD; + let ret = RegEnumKeyExW(self.key.raw(), i, v.as_mut_ptr(), &mut len, + 0 as *mut _, 0 as *mut _, 0 as *mut _, + 0 as *mut _); + if ret == ERROR_NO_MORE_ITEMS as LONG { + None + } else if ret != ERROR_SUCCESS { + Some(Err(io::Error::from_raw_os_error(ret as i32))) + } else { + v.set_len(len as usize); + Some(Ok(OsString::from_wide(&v))) + } + }) + } +} diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs index 267f0b6d953..90ddba4e09c 100644 --- a/src/librustc_trans/back/write.rs +++ b/src/librustc_trans/back/write.rs @@ -9,7 +9,7 @@ // except according to those terms. use back::lto; -use back::link::{get_cc_prog, remove}; +use back::link::{get_linker, remove}; use session::config::{OutputFilenames, Passes, SomePasses, AllPasses}; use session::Session; use session::config; @@ -27,7 +27,7 @@ use std::ffi::{CStr, CString}; use std::fs; use std::mem; use std::path::Path; -use std::process::{Command, Stdio}; +use std::process::Stdio; use std::ptr; use std::str; use std::sync::{Arc, Mutex}; @@ -737,8 +737,7 @@ pub fn run_passes(sess: &Session, None }; - let pname = get_cc_prog(sess); - let mut cmd = Command::new(&pname[..]); + let (pname, mut cmd) = get_linker(sess); cmd.args(&sess.target.target.options.pre_link_args); cmd.arg("-nostdlib"); @@ -767,8 +766,7 @@ pub fn run_passes(sess: &Session, }, Err(e) => { sess.err(&format!("could not exec the linker `{}`: {}", - pname, - e)); + pname, e)); sess.abort_if_errors(); }, } @@ -986,8 +984,7 @@ fn run_work_multithreaded(sess: &Session, } pub fn run_assembler(sess: &Session, outputs: &OutputFilenames) { - let pname = get_cc_prog(sess); - let mut cmd = Command::new(&pname[..]); + let (pname, mut cmd) = get_linker(sess); cmd.arg("-c").arg("-o").arg(&outputs.path(config::OutputTypeObject)) .arg(&outputs.temp_path(config::OutputTypeAssembly)); @@ -1007,9 +1004,7 @@ pub fn run_assembler(sess: &Session, outputs: &OutputFilenames) { } }, Err(e) => { - sess.err(&format!("could not exec the linker `{}`: {}", - pname, - e)); + sess.err(&format!("could not exec the linker `{}`: {}", pname, e)); sess.abort_if_errors(); } } diff --git a/src/librustc_trans/lib.rs b/src/librustc_trans/lib.rs index dc692b0e765..66881edaa8b 100644 --- a/src/librustc_trans/lib.rs +++ b/src/librustc_trans/lib.rs @@ -83,7 +83,7 @@ pub mod back { pub mod link; pub mod lto; pub mod write; - + pub mod msvc; } pub mod trans;