diff options
Diffstat (limited to 'gcc/src/windows_registry.rs')
-rw-r--r-- | gcc/src/windows_registry.rs | 267 |
1 files changed, 232 insertions, 35 deletions
diff --git a/gcc/src/windows_registry.rs b/gcc/src/windows_registry.rs index 08b1df5..d05fc1d 100644 --- a/gcc/src/windows_registry.rs +++ b/gcc/src/windows_registry.rs @@ -15,6 +15,7 @@ use std::process::Command; use Tool; +#[cfg(windows)] macro_rules! otry { ($expr:expr) => (match $expr { Some(val) => val, @@ -50,10 +51,119 @@ pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> { #[cfg(windows)] pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { use std::env; + + // 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 impl_::find_msbuild(target); + } + + // 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())); + } + + // 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. + + return impl_::find_msvc_15(tool, target) + .or_else(|| impl_::find_msvc_14(tool, target)) + .or_else(|| impl_::find_msvc_12(tool, target)) + .or_else(|| impl_::find_msvc_11(tool, target)); +} + +/// A version of Visual Studio +pub enum VsVers { + /// Visual Studio 12 (2013) + Vs12, + /// Visual Studio 14 (2015) + Vs14, + /// Visual Studio 15 (2017) + Vs15, + + /// Hidden variant that should not be matched on. Callers that want to + /// handle an enumeration of `VsVers` instances should always have a default + /// case meaning that it's a VS version they don't understand. + #[doc(hidden)] + __Nonexhaustive_do_not_match_this_or_your_code_will_break, +} + +/// Find the most recent installed version of Visual Studio +/// +/// This is used by the cmake crate to figure out the correct +/// generator. +#[cfg(not(windows))] +pub fn find_vs_version() -> Result<VsVers, String> { + Err(format!("not windows")) +} + +/// Documented above +#[cfg(windows)] +pub fn find_vs_version() -> Result<VsVers, String> { + use std::env; + + match env::var("VisualStudioVersion") { + Ok(version) => { + match &version[..] { + "15.0" => Ok(VsVers::Vs15), + "14.0" => Ok(VsVers::Vs14), + "12.0" => Ok(VsVers::Vs12), + vers => Err(format!("\n\n\ + unsupported or unknown VisualStudio version: {}\n\ + if another version is installed consider running \ + the appropriate vcvars script before building this \ + crate\n\ + ", vers)), + } + } + _ => { + // Check for the presense of a specific registry key + // that indicates visual studio is installed. + if impl_::has_msbuild_version("15.0") { + Ok(VsVers::Vs15) + } else if impl_::has_msbuild_version("14.0") { + Ok(VsVers::Vs14) + } else if impl_::has_msbuild_version("12.0") { + Ok(VsVers::Vs12) + } else { + Err(format!("\n\n\ + couldn't determine visual studio generator\n\ + if VisualStudio is installed, however, consider \ + running the appropriate vcvars script before building \ + this crate\n\ + ")) + } + } + } +} + +#[cfg(windows)] +mod impl_ { + use std::env; use std::ffi::OsString; use std::mem; use std::path::{Path, PathBuf}; + use std::fs::File; + use std::io::Read; use registry::{RegistryKey, LOCAL_MACHINE}; + use com; + use setup_config::{SetupConfiguration, SetupInstance}; + + use Tool; struct MsvcTool { tool: PathBuf, @@ -82,47 +192,91 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { } } - // This logic is all tailored for MSVC, if we're not that then bail out - // early. - if !target.contains("msvc") { - return None; - } + // In MSVC 15 (2017) MS once again changed the scheme for locating + // the tooling. Now we must go through some COM interfaces, which + // is super fun for Rust. + pub fn find_msvc_15(tool: &str, target: &str) -> Option<Tool> { + otry!(com::initialize().ok()); + + let config = otry!(SetupConfiguration::new().ok()); + let iter = otry!(config.enum_all_instances().ok()); + for instance in iter { + let instance = otry!(instance.ok()); + let tool = tool_from_vs15_instance(tool, target, &instance); + if tool.is_some() { + return tool; + } + } - // 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); + 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())); + fn tool_from_vs15_instance(tool: &str, target: &str, + instance: &SetupInstance) -> Option<Tool> { + let (bin_path, lib_path, include_path) = otry!(vs15_vc_paths(target, instance)); + let tool_path = bin_path.join(tool); + if !tool_path.exists() { return None }; + + let mut tool = MsvcTool::new(tool_path); + tool.path.push(bin_path.clone()); + tool.libs.push(lib_path); + tool.include.push(include_path); + + if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &bin_path) { + tool.libs.push(atl_lib_path); + tool.include.push(atl_include_path); + } + + otry!(add_sdks(&mut tool, target)); + + Some(tool.into_tool()) } - // 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. + fn vs15_vc_paths(target: &str, instance: &SetupInstance) -> Option<(PathBuf, PathBuf, PathBuf)> { + let instance_path: PathBuf = otry!(instance.installation_path().ok()).into(); + let version_path = instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"); + let mut version_file = otry!(File::open(version_path).ok()); + let mut version = String::new(); + otry!(version_file.read_to_string(&mut version).ok()); + let version = version.trim(); + let host = match host_arch() { + X86 => "X86", + X86_64 => "X64", + _ => return None, + }; + let target = otry!(lib_subdir(target)); + let path = instance_path.join(r"VC\Tools\MSVC").join(version); + let bin_path = path.join("bin").join(&format!("Host{}", host)).join(&target); + let lib_path = path.join("lib").join(&target); + let include_path = path.join("include"); + Some((bin_path, lib_path, include_path)) + } - return find_msvc_latest(tool, target, "15.0") - .or_else(|| find_msvc_latest(tool, target, "14.0")) - .or_else(|| find_msvc_12(tool, target)) - .or_else(|| find_msvc_11(tool, target)); + fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> { + let atl_path = path.join("atlfmc"); + let sub = otry!(lib_subdir(target)); + if atl_path.exists() { + Some((atl_path.join("lib").join(sub), atl_path.join("include"))) + } else { + None + } + } - // For MSVC 14 or newer we need to find the Universal CRT as well as either + // For MSVC 14 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<Tool> { - let vcdir = otry!(get_vc_dir(ver)); + pub fn find_msvc_14(tool: &str, target: &str) -> Option<Tool> { + let vcdir = otry!(get_vc_dir("14.0")); let mut tool = otry!(get_tool(tool, &vcdir, target)); + otry!(add_sdks(&mut tool, target)); + Some(tool.into_tool()) + } + + fn add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()> { let sub = otry!(lib_subdir(target)); let (ucrt, ucrt_version) = otry!(get_ucrt_dir()); + tool.path.push(ucrt.join("bin").join(&ucrt_version).join(sub)); + let ucrt_include = ucrt.join("include").join(&ucrt_version); tool.include.push(ucrt_include.join("ucrt")); @@ -145,14 +299,13 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { tool.include.push(sdk_include.join("um")); tool.include.push(sdk_include.join("winrt")); tool.include.push(sdk_include.join("shared")); - } else { - return None; } - Some(tool.into_tool()) + + Some(()) } // For MSVC 12 we need to find the Windows 8.1 SDK. - fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> { + pub fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> { let vcdir = otry!(get_vc_dir("12.0")); let mut tool = otry!(get_tool(tool, &vcdir, target)); let sub = otry!(lib_subdir(target)); @@ -168,7 +321,7 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { } // For MSVC 11 we need to find the Windows 8 SDK. - fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> { + pub fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> { let vcdir = otry!(get_vc_dir("11.0")); let mut tool = otry!(get_tool(tool, &vcdir, target)); let sub = otry!(lib_subdir(target)); @@ -403,8 +556,52 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { max_key } + pub fn has_msbuild_version(version: &str) -> bool { + match version { + "15.0" => { + find_msbuild_vs15("x86_64-pc-windows-msvc").is_some() || + find_msbuild_vs15("i686-pc-windows-msvc").is_some() + } + "12.0" | "14.0" => { + LOCAL_MACHINE.open( + &OsString::from(format!("SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}", + version))).is_ok() + } + _ => false + } + } + // see http://stackoverflow.com/questions/328017/path-to-msbuild - fn find_msbuild(target: &str) -> Option<Tool> { + pub fn find_msbuild(target: &str) -> Option<Tool> { + // VS 15 (2017) changed how to locate msbuild + if let Some(r) = find_msbuild_vs15(target) { + return Some(r); + } else { + find_old_msbuild(target) + } + } + + fn find_msbuild_vs15(target: &str) -> Option<Tool> { + // Seems like this could also go through SetupConfiguration, + // or that find_msvc_15 could just use this registry key + // instead of the COM interface. + let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7"; + LOCAL_MACHINE.open(key.as_ref()) + .ok() + .and_then(|key| { + key.query_str("15.0").ok() + }) + .map(|path| { + let path = PathBuf::from(path).join(r"MSBuild\15.0\Bin\MSBuild.exe"); + let mut tool = Tool::new(path); + if target.contains("x86_64") { + tool.env.push(("Platform".into(), "X64".into())); + } + tool + }) + } + + fn find_old_msbuild(target: &str) -> Option<Tool> { let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions"; LOCAL_MACHINE.open(key.as_ref()) .ok() |