diff --git a/gcc-test/build.rs b/gcc-test/build.rs index 4ce6628..beb0baf 100644 --- a/gcc-test/build.rs +++ b/gcc-test/build.rs @@ -46,6 +46,7 @@ fn main() { if target.contains("msvc") { let out = out.join("tmp"); fs::create_dir(&out).unwrap(); + println!("nmake 1"); let status = gcc::windows_registry::find(&target, "nmake.exe").unwrap() .arg("/fsrc/NMakefile") .env("OUT_DIR", &out) @@ -60,6 +61,7 @@ fn main() { env::remove_var("VCINSTALLDIR"); env::remove_var("INCLUDE"); env::remove_var("LIB"); + println!("nmake 2"); let status = gcc::windows_registry::find(&target, "nmake.exe").unwrap() .arg("/fsrc/NMakefile") .env("OUT_DIR", &out) diff --git a/src/windows_registry.rs b/src/windows_registry.rs index 350fb76..4f07945 100644 --- a/src/windows_registry.rs +++ b/src/windows_registry.rs @@ -15,6 +15,13 @@ use std::process::Command; use Tool; +macro_rules! otry { + ($expr:expr) => (match $expr { + Some(val) => val, + None => return None, + }) +} + /// Attempts to find a tool within an MSVC installation using the Windows /// registry as a point to search from. /// @@ -44,184 +51,323 @@ pub fn find_tool(_target: &str, _tool: &str) -> Option { pub fn find_tool(target: &str, tool: &str) -> Option { use std::env; use std::ffi::OsString; - use std::io; - use std::fs; + use std::mem; use std::path::{Path, PathBuf}; use registry::{RegistryKey, LOCAL_MACHINE}; - if !target.contains("msvc") { return None } + struct MsvcTool { + tool: PathBuf, + libs: Vec, + path: Vec, + include: Vec, + } + + impl MsvcTool { + fn new(tool: PathBuf) -> MsvcTool { + MsvcTool { + tool: tool, + libs: Vec::new(), + path: Vec::new(), + include: Vec::new(), + } + } + + fn into_tool(self) -> Tool { + let MsvcTool { tool, libs, path, include } = self; + let mut tool = Tool::new(tool.into()); + add_env(&mut tool, "LIB", libs); + add_env(&mut tool, "PATH", path); + add_env(&mut tool, "INCLUDE", include); + return tool + } + } + + // This logic is all tailored for MSVC, if we're not that then bail out + // early. + if !target.contains("msvc") { + return None + } + // Looks like msbuild isn't located in the same location as other tools like + // cl.exe and lib.exe. To handle this we probe for it manually with + // dedicated registry keys. if tool.contains("msbuild") { return find_msbuild(target) } - // When finding binaries 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 = if target.starts_with("i686") { - "" - } else if target.starts_with("x86_64") { - "amd64" - } else if target.starts_with("arm") { - "arm" - } else { - return None - }; + // If VCINSTALLDIR is set, then someone's probably already run vcvars and we + // should just find whatever that indicates. + if env::var_os("VCINSTALLDIR").is_some() { + return env::var_os("PATH").and_then(|path| { + env::split_paths(&path).map(|p| p.join(tool)).find(|p| p.exists()) + }).map(|path| { + Tool::new(path.into()) + }) + } - let vs_install_dir = get_vs_install_dir(); - let mut path_to_add = None; + // Ok, if we're here, now comes the fun part of the probing. Default shells + // or shells like MSYS aren't really configured to execute `cl.exe` and the + // various compiler tools shipped as part of Visual Studio. Here we try to + // first find the relevant tool, then we also have to be sure to fill in + // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that + // the tool is actually usable. - // 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. - // - // Finally we read the Windows registry to discover the VS install root. - // From here we probe 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); - let tool = p.join(tool); - if fs::metadata(&tool).is_ok() { - path_to_add = Some(p); - Some(tool) - } else { - None - } + return find_msvc_latest(tool, target, "15.0").or_else(|| { + find_msvc_latest(tool, target, "14.0") }).or_else(|| { - env::var_os("PATH").and_then(|path| { - env::split_paths(&path).map(|p| p.join(tool)).find(|path| { - fs::metadata(path).is_ok() - }) - }) + find_msvc_12(tool, target) }).or_else(|| { - vs_install_dir.as_ref().and_then(|p| { - let mut p = p.join("VC/bin"); - p.push(extra); - let tool = p.join(tool); - if fs::metadata(&tool).is_ok() { - path_to_add = Some(p); - Some(tool) - } else { - None - } - }) - }).map(|tool| { - Tool::new(tool.into()) - }).unwrap_or_else(|| { - Tool::new(tool.into()) + find_msvc_11(tool, target) }); - let mut paths = Vec::new(); - if let Some(path) = path_to_add { - paths.push(path); - if let Some(root) = get_windows_sdk_bin_path(target) { - paths.push(root); + // For MSVC 14 or newer we need to find the Universal CRT as well as either + // the Windows 10 SDK or Windows 8.1 SDK. + fn find_msvc_latest(tool: &str, target: &str, ver: &str) -> Option { + let vcdir = otry!(get_vc_dir(ver)); + let mut tool = otry!(get_tool(tool, &vcdir, target)); + let sub = otry!(lib_subdir(target)); + let (ucrt, ucrt_version) = otry!(get_ucrt_dir()); + + let ucrt_include = ucrt.join("Include").join(&ucrt_version); + tool.include.push(ucrt_include.join("ucrt")); + tool.include.push(ucrt_include.join("um")); + tool.include.push(ucrt_include.join("winrt")); + tool.include.push(ucrt_include.join("shared")); + + let ucrt_lib = ucrt.join("Lib").join(&ucrt_version); + tool.libs.push(ucrt_lib.join("ucrt").join(sub)); + + if let Some(dir) = get_sdk10_dir() { + tool.libs.push(dir.join("um").join(sub)); + tool.path.push(dir.join("bin").join(sub)); + tool.include.push(dir.join("include/shared")); + tool.include.push(dir.join("include/um")); + tool.include.push(dir.join("include/winrt")); + } else if let Some(dir) = get_sdk81_dir() { + tool.libs.push(dir.join("um").join(sub)); + tool.path.push(dir.join("bin").join(sub)); + tool.include.push(dir.join("include/shared")); + tool.include.push(dir.join("include/um")); + tool.include.push(dir.join("include/winrt")); + } else { + return None } + Some(tool.into_tool()) + } + + // For MSVC 12 we need to find the Windows 8.1 SDK. + fn find_msvc_12(tool: &str, target: &str) -> Option { + let vcdir = otry!(get_vc_dir("12.0")); + let mut tool = otry!(get_tool(tool, &vcdir, target)); + let sub = otry!(lib_subdir(target)); + let sdk81 = otry!(get_sdk81_dir()); + tool.libs.push(sdk81.join("um").join(sub)); + tool.path.push(sdk81.join("bin").join(sub)); + tool.include.push(sdk81.join("include/shared")); + tool.include.push(sdk81.join("include/um")); + tool.include.push(sdk81.join("include/winrt")); + Some(tool.into_tool()) + } + + // For MSVC 11 we need to find the Windows 8 SDK. + fn find_msvc_11(tool: &str, target: &str) -> Option { + let vcdir = otry!(get_vc_dir("11.0")); + let mut tool = otry!(get_tool(tool, &vcdir, target)); + let sub = otry!(lib_subdir(target)); + let sdk8 = otry!(get_sdk8_dir()); + tool.libs.push(sdk8.join("um").join(sub)); + tool.path.push(sdk8.join("bin").join(sub)); + tool.include.push(sdk8.join("include/shared")); + tool.include.push(sdk8.join("include/um")); + tool.include.push(sdk8.join("include/winrt")); + Some(tool.into_tool()) + } + + fn add_env(tool: &mut Tool, env: &str, paths: Vec) { + let prev = env::var_os(env).unwrap_or(OsString::new()); + let prev = env::split_paths(&prev); + let new = paths.into_iter().chain(prev); + tool.env.push((env.to_string().into(), env::join_paths(new).unwrap())); + } + + // Given a possible MSVC installation directory, we look for the linker and + // then add the MSVC library path. + fn get_tool(tool: &str, path: &Path, target: &str) -> Option { + bin_subdir(target).into_iter().map(|(sub, host)| { + (path.join("bin").join(sub).join(tool), + path.join("bin").join(host)) + }).filter(|&(ref path, _)| { + path.is_file() + }).map(|(path, host)| { + let mut tool = MsvcTool::new(path); + tool.path.push(host); + tool + }).filter_map(|mut tool| { + let sub = otry!(vc_lib_subdir(target)); + tool.libs.push(path.join("lib").join(sub)); + tool.include.push(path.join("include")); + Some(tool) + }).next() } - if let Some(path) = env::var_os("PATH") { - paths.extend(env::split_paths(&path)); + + // To find MSVC we look in a specific registry key for the version we are + // trying to find. + fn get_vc_dir(ver: &str) -> Option { + let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7"; + let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok()); + let path = otry!(key.query_str(ver).ok()); + Some(path.into()) } - cmd.env.push(("PATH".into(), env::join_paths(&paths).unwrap().into())); - // The MSVC compiler uses the INCLUDE environment variable as the default - // lookup path for headers. 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. + // To find the Universal CRT we look in a specific registry key for where + // all the Universal CRTs are located and then sort them asciibetically to + // find the newest version. While this sort of sorting isn't ideal, it is + // what vcvars does so that's good enough for us. // - // 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("INCLUDE").is_none() { - let mut includes = Vec::new(); - if let Some(ref vs_install_dir) = vs_install_dir { - includes.push(vs_install_dir.join("VC/include")); - if let Some((ucrt_root, vers)) = ucrt_install_dir(vs_install_dir) { - let include = ucrt_root.join("Include").join(vers); - includes.push(include.join("ucrt")); - includes.push(include.join("um")); - includes.push(include.join("winrt")); - includes.push(include.join("shared")); - } - } - if let Some((path, major)) = get_windows_sdk_path() { - if major >= 8 { - includes.push(path.join("include/shared")); - includes.push(path.join("include/um")); - includes.push(path.join("include/winrt")); - } else { - includes.push(path.join("include")); - } - } else if let Some(ref vs_install_dir) = vs_install_dir { - includes.push(vs_install_dir.clone()); + // Returns a pair of (root, version) for the ucrt dir if found + fn get_ucrt_dir() -> Option<(PathBuf, String)> { + let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots"; + let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok()); + let root = otry!(key.query_str("KitsRoot10").ok()); + let readdir = otry!(Path::new(&root).join("lib").read_dir().ok()); + let max_libdir = otry!(readdir.filter_map(|dir| { + dir.ok() + }).map(|dir| { + dir.path() + }).filter(|dir| { + dir.components().last().and_then(|c| { + c.as_os_str().to_str() + }).map(|c| { + c.starts_with("10.") && dir.join("ucrt").is_dir() + }).unwrap_or(false) + }).max()); + let version = max_libdir.components().last().unwrap(); + let version = version.as_os_str().to_str().unwrap().to_string(); + Some((root.into(), version)) + } + + // Vcvars finds the correct version of the Windows 10 SDK by looking + // for the include `um\Windows.h` because sometimes a given version will + // only have UCRT bits without the rest of the SDK. Since we only care about + // libraries and not includes, we instead look for `um\x64\kernel32.lib`. + // Since the 32-bit and 64-bit libraries are always installed together we + // only need to bother checking x64, making this code a tiny bit simpler. + // Like we do for the Universal CRT, we sort the possibilities + // asciibetically to find the newest one as that is what vcvars does. + fn get_sdk10_dir() -> Option { + let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0"; + let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok()); + let root = otry!(key.query_str("InstallationFolder").ok()); + let readdir = otry!(Path::new(&root).join("lib").read_dir().ok()); + let mut dirs = readdir.filter_map(|dir| dir.ok()) + .map(|dir| dir.path()) + .collect::>(); + dirs.sort(); + dirs.into_iter().rev().filter(|dir| { + dir.join("um").join("x64").join("kernel32.lib").is_file() + }).next() + } + + // Interestingly there are several subdirectories, `win7` `win8` and + // `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same + // applies to us. Note that if we were targetting kernel mode drivers + // instead of user mode applications, we would care. + fn get_sdk81_dir() -> Option { + let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1"; + let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok()); + let root = otry!(key.query_str("InstallationFolder").ok()); + Some(Path::new(&root).join("lib").join("winv6.3")) + } + + fn get_sdk8_dir() -> Option { + let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0"; + let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok()); + let root = otry!(key.query_str("InstallationFolder").ok()); + Some(Path::new(&root).join("lib").join("win8")) + } + + const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0; + const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9; + const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL; + const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64; + + // When choosing the tool to use, we have to choose the one which matches + // the target architecture. Otherwise we end up in situations where someone + // on 32-bit Windows is trying to cross compile to 64-bit and it tries to + // invoke the native 64-bit compiler which won't work. + // + // For the return value of this function, the first member of the tuple is + // the folder of the tool we will be invoking, while the second member is + // the folder of the host toolchain for that tool which is essential when + // using a cross linker. We return a Vec since on x64 there are often two + // linkers that can target the architecture we desire. The 64-bit host + // linker is preferred, and hence first, due to 64-bit allowing it more + // address space to work with and potentially being faster. + fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> { + let arch = target.split('-').next().unwrap(); + match (arch, host_arch()) { + ("i686", X86) => vec![("", "")], + ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")], + ("x86_64", X86) => vec![("x86_amd64", "")], + ("x86_64", X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")], + ("arm", X86) => vec![("x86_arm", "")], + ("arm", X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")], + _ => vec![], } - cmd.env.push(("INCLUDE".into(), - env::join_paths(&includes).unwrap().into())); } - // Similarly with INCLUDE above, let's set LIB if it's not defined. - if env::var_os("LIB").is_none() { - let mut libs = Vec::new(); - if let Some(ref vs_install_dir) = vs_install_dir { - libs.push(vs_install_dir.join("VC/lib").join(extra)); - if let Some((ucrt_root, vers)) = ucrt_install_dir(vs_install_dir) { - if let Some(arch) = windows_sdk_v8_subdir(target) { - let lib = ucrt_root.join("Lib").join(vers); - libs.push(lib.join("ucrt").join(arch)); - libs.push(lib.join("um").join(arch)); - } - } + fn lib_subdir(target: &str) -> Option<&'static str> { + let arch = target.split('-').next().unwrap(); + match arch { + "i686" => Some("x86"), + "x86_64" => Some("x64"), + "arm" => Some("arm"), + _ => None, } - if let Some(path) = get_windows_sdk_lib_path(target) { - libs.push(path); + } + + // MSVC's x86 libraries are not in a subfolder + fn vc_lib_subdir(target: &str) -> Option<&'static str> { + let arch = target.split('-').next().unwrap(); + match arch { + "i686" => Some(""), + "x86_64" => Some("amd64"), + "arm" => Some("arm"), + _ => None, } - cmd.env.push(("LIB".into(), env::join_paths(&libs).unwrap().into())); } - return Some(cmd); + #[allow(bad_style)] + pub fn host_arch() -> u16 { + type DWORD = u32; + type WORD = u16; + type LPVOID = *mut u8; + type DWORD_PTR = usize; - // 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 - } - }) + #[repr(C)] + struct SYSTEM_INFO { + wProcessorArchitecture: WORD, + _wReserved: WORD, + _dwPageSize: DWORD, + _lpMinimumApplicationAddress: LPVOID, + _lpMaximumApplicationAddress: LPVOID, + _dwActiveProcessorMask: DWORD_PTR, + _dwNumberOfProcessors: DWORD, + _dwProcessorType: DWORD, + _dwAllocationGranularity: DWORD, + _wProcessorLevel: WORD, + _wProcessorRevision: WORD, + } + + extern "system" { + fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO); + } + + unsafe { + let mut info = mem::zeroed(); + GetNativeSystemInfo(&mut info); + info.wProcessorArchitecture + } } // Given a registry key, look at all the sub keys and find the one which has @@ -249,118 +395,6 @@ pub fn find_tool(target: &str, tool: &str) -> Option { return max_key } - fn get_windows_sdk_path() -> Option<(PathBuf, usize)> { - 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(); - k.query_str("InstallationFolder").ok().map(|p| { - (PathBuf::from(p), major) - }) - } - - fn get_windows_sdk_lib_path(target: &str) -> Option { - let (mut root, major) = match get_windows_sdk_path() { - Some(pair) => pair, - None => return None, - }; - root.push("Lib"); - 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. - if target.starts_with("i686") { - Some(root) - } else if target.starts_with("x86_64") { - Some(root.join("x64")) - } else { - None - } - } 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 windows_sdk_v8_subdir(target) { - Some(extra) => extra, - None => return None, - }; - ["winv6.3", "win8", "win7"].iter().map(|p| root.join(p)).find(|part| { - fs::metadata(part).is_ok() - }).map(|path| { - path.join("um").join(extra) - }) - } - } - - fn get_windows_sdk_bin_path(target: &str) -> Option { - let (mut root, major) = match get_windows_sdk_path() { - Some(pair) => pair, - None => return None, - }; - root.push("bin"); - if major <= 7 { - None // untested path, not sure if this dir exists - } else { - root.push(match windows_sdk_v8_subdir(target) { - Some(extra) => extra, - None => return None, - }); - if fs::metadata(&root).is_ok() {Some(root)} else {None} - } - } - - fn windows_sdk_v8_subdir(target: &str) -> Option<&'static str> { - if target.starts_with("i686") { - Some("x86") - } else if target.starts_with("x86_64") { - Some("x64") - } else if target.starts_with("arm") { - Some("arm") - } else { - None - } - } - - fn ucrt_install_dir(vs_install_dir: &Path) -> Option<(PathBuf, String)> { - let is_vs_14 = vs_install_dir.iter().filter_map(|p| p.to_str()).any(|s| { - s == "Microsoft Visual Studio 14.0" - }); - if !is_vs_14 { - return None - } - let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots"; - let sdk_dir = LOCAL_MACHINE.open(key.as_ref()).and_then(|p| { - p.query_str("KitsRoot10") - }).map(PathBuf::from); - let sdk_dir = match sdk_dir { - Ok(p) => p, - Err(..) => return None, - }; - (move || -> io::Result<_> { - let mut max = None; - let mut max_s = None; - for entry in try!(fs::read_dir(&sdk_dir.join("Lib"))) { - let entry = try!(entry); - if let Ok(s) = entry.file_name().into_string() { - if let Ok(u) = s.replace(".", "").parse::() { - if Some(u) > max { - max = Some(u); - max_s = Some(s); - } - } - } - } - Ok(max_s.map(|m| (sdk_dir, m))) - })().ok().and_then(|x| x) - } - // see http://stackoverflow.com/questions/328017/path-to-msbuild fn find_msbuild(target: &str) -> Option { let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";