diff --git a/src/windows_registry.rs b/src/windows_registry.rs index f895a40..11656d2 100644 --- a/src/windows_registry.rs +++ b/src/windows_registry.rs @@ -51,6 +51,103 @@ pub fn find_tool(_target: &str, _tool: &str) -> Option { #[cfg(windows)] pub fn find_tool(target: &str, tool: &str) -> Option { 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 +} + +/// 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 { + Err(format!("not windows")) +} + +/// Documented above +#[cfg(windows)] +pub fn find_vs_version() -> Result { + 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}; @@ -60,6 +157,8 @@ pub fn find_tool(target: &str, tool: &str) -> Option { use com; use setup_config::{SetupConfiguration, SetupInstance}; + use Tool; + struct MsvcTool { tool: PathBuf, libs: Vec, @@ -87,43 +186,10 @@ pub fn find_tool(target: &str, tool: &str) -> Option { } } - // 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); - } - - // 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 find_msvc_15(tool, target) - .or_else(|| find_msvc_14(tool, target)) - .or_else(|| find_msvc_12(tool, target)) - .or_else(|| find_msvc_11(tool, target)); - // 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. - fn find_msvc_15(tool: &str, target: &str) -> Option { + pub fn find_msvc_15(tool: &str, target: &str) -> Option { otry!(com::initialize().ok()); let config = otry!(SetupConfiguration::new().ok()); @@ -192,7 +258,7 @@ pub fn find_tool(target: &str, tool: &str) -> Option { // 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_14(tool: &str, target: &str) -> Option { + pub fn find_msvc_14(tool: &str, target: &str) -> Option { let vcdir = otry!(get_vc_dir("14.0")); let mut tool = otry!(get_tool(tool, &vcdir, target)); otry!(add_sdks(&mut tool, target)); @@ -233,7 +299,7 @@ pub fn find_tool(target: &str, tool: &str) -> Option { } // For MSVC 12 we need to find the Windows 8.1 SDK. - fn find_msvc_12(tool: &str, target: &str) -> Option { + pub 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)); @@ -249,7 +315,7 @@ pub fn find_tool(target: &str, tool: &str) -> Option { } // For MSVC 11 we need to find the Windows 8 SDK. - fn find_msvc_11(tool: &str, target: &str) -> Option { + pub 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)); @@ -484,8 +550,52 @@ pub fn find_tool(target: &str, tool: &str) -> Option { 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 { + pub fn find_msbuild(target: &str) -> Option { + // 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 { + // 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 { let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions"; LOCAL_MACHINE.open(key.as_ref()) .ok()