| // 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 <LICENSE-APACHE or |
| // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| //! A helper module to looking for windows-specific tools: |
| //! 1. On Windows host, probe the Windows Registry if needed; |
| //! 2. On non-Windows host, check specified environment variables. |
| |
| #![allow(clippy::upper_case_acronyms)] |
| |
| use std::process::Command; |
| |
| use crate::Tool; |
| use crate::ToolFamily; |
| |
| const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false }; |
| |
| #[derive(Copy, Clone)] |
| struct TargetArch<'a>(pub &'a str); |
| |
| impl PartialEq<&str> for TargetArch<'_> { |
| fn eq(&self, other: &&str) -> bool { |
| self.0 == *other |
| } |
| } |
| |
| impl<'a> From<TargetArch<'a>> for &'a str { |
| fn from(target: TargetArch<'a>) -> Self { |
| target.0 |
| } |
| } |
| |
| /// Attempts to find a tool within an MSVC installation using the Windows |
| /// registry as a point to search from. |
| /// |
| /// The `target` argument is the target that the tool should work for (e.g. |
| /// compile or link for) and the `tool` argument is the tool to find (e.g. |
| /// `cl.exe` or `link.exe`). |
| /// |
| /// This function will return `None` if the tool could not be found, or it will |
| /// return `Some(cmd)` which represents a command that's ready to execute the |
| /// tool with the appropriate environment variables set. |
| /// |
| /// Note that this function always returns `None` for non-MSVC targets. |
| pub fn find(target: &str, tool: &str) -> Option<Command> { |
| find_tool(target, tool).map(|c| c.to_command()) |
| } |
| |
| /// Similar to the `find` function above, this function will attempt the same |
| /// operation (finding a MSVC tool in a local install) but instead returns a |
| /// `Tool` which may be introspected. |
| 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; |
| } |
| |
| // Split the target to get the arch. |
| let target = TargetArch(target.split_once('-')?.0); |
| |
| // Looks like msbuild isn't located in the same location as other tools like |
| // cl.exe and lib.exe. |
| if tool.contains("msbuild") { |
| return impl_::find_msbuild(target); |
| } |
| |
| // Looks like devenv isn't located in the same location as other tools like |
| // cl.exe and lib.exe. |
| if tool.contains("devenv") { |
| return impl_::find_devenv(target); |
| } |
| |
| // 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. |
| |
| impl_::find_msvc_environment(tool, target) |
| .or_else(|| impl_::find_msvc_15plus(tool, target)) |
| .or_else(|| impl_::find_msvc_14(tool, target)) |
| .or_else(|| impl_::find_msvc_12(tool, target)) |
| } |
| |
| /// A version of Visual Studio |
| #[derive(Debug, PartialEq, Eq, Copy, Clone)] |
| #[non_exhaustive] |
| pub enum VsVers { |
| /// Visual Studio 12 (2013) |
| Vs12, |
| /// Visual Studio 14 (2015) |
| Vs14, |
| /// Visual Studio 15 (2017) |
| Vs15, |
| /// Visual Studio 16 (2019) |
| Vs16, |
| /// Visual Studio 17 (2022) |
| Vs17, |
| } |
| |
| /// Find the most recent installed version of Visual Studio |
| /// |
| /// This is used by the cmake crate to figure out the correct |
| /// generator. |
| pub fn find_vs_version() -> Result<VsVers, String> { |
| match std::env::var("VisualStudioVersion") { |
| Ok(version) => match &version[..] { |
| "17.0" => Ok(VsVers::Vs17), |
| "16.0" => Ok(VsVers::Vs16), |
| "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 presence of a specific registry key |
| // that indicates visual studio is installed. |
| if impl_::has_msbuild_version("17.0") { |
| Ok(VsVers::Vs17) |
| } else if impl_::has_msbuild_version("16.0") { |
| Ok(VsVers::Vs16) |
| } else 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\ |
| " |
| )) |
| } |
| } |
| } |
| } |
| |
| /// Windows Implementation. |
| #[cfg(windows)] |
| mod impl_ { |
| use crate::windows::com; |
| use crate::windows::registry::{RegistryKey, LOCAL_MACHINE}; |
| use crate::windows::setup_config::SetupConfiguration; |
| use crate::windows::vs_instances::{VsInstances, VswhereInstance}; |
| use crate::windows::windows_sys::{ |
| FreeLibrary, GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE, |
| IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK, |
| }; |
| use std::convert::TryFrom; |
| use std::env; |
| use std::ffi::OsString; |
| use std::fs::File; |
| use std::io::Read; |
| use std::iter; |
| use std::mem; |
| use std::path::{Path, PathBuf}; |
| use std::process::Command; |
| use std::str::FromStr; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| use std::sync::Once; |
| |
| use super::{TargetArch, MSVC_FAMILY}; |
| use crate::Tool; |
| |
| struct MsvcTool { |
| tool: PathBuf, |
| libs: Vec<PathBuf>, |
| path: Vec<PathBuf>, |
| include: Vec<PathBuf>, |
| } |
| |
| struct LibraryHandle(HMODULE); |
| |
| impl LibraryHandle { |
| fn new(name: &[u8]) -> Option<Self> { |
| let handle = unsafe { LoadLibraryA(name.as_ptr() as _) }; |
| (!handle.is_null()).then(|| Self(handle)) |
| } |
| |
| /// Get a function pointer to a function in the library. |
| /// SAFETY: The caller must ensure that the function signature matches the actual function. |
| /// The easiest way to do this is to add an entry to windows_sys_no_link.list and use the |
| /// generated function for `func_signature`. |
| unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> { |
| let symbol = unsafe { GetProcAddress(self.0, name.as_ptr() as _) }; |
| symbol.map(|symbol| unsafe { mem::transmute_copy(&symbol) }) |
| } |
| } |
| |
| impl Drop for LibraryHandle { |
| fn drop(&mut self) { |
| unsafe { FreeLibrary(self.0) }; |
| } |
| } |
| |
| type GetMachineTypeAttributesFuncType = |
| unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32; |
| const _: () = { |
| // Ensure that our hand-written signature matches the actual function signature. |
| // We can't use `GetMachineTypeAttributes` outside of a const scope otherwise we'll end up statically linking to |
| // it, which will fail to load on older versions of Windows. |
| let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes; |
| }; |
| |
| fn is_amd64_emulation_supported_inner() -> Option<bool> { |
| // GetMachineTypeAttributes is only available on Win11 22000+, so dynamically load it. |
| let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?; |
| // SAFETY: GetMachineTypeAttributesFuncType is checked to match the real function signature. |
| let get_machine_type_attributes = unsafe { |
| kernel32 |
| .get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0") |
| }?; |
| let mut attributes = Default::default(); |
| if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK |
| { |
| Some((attributes & UserEnabled) != 0) |
| } else { |
| Some(false) |
| } |
| } |
| |
| fn is_amd64_emulation_supported() -> bool { |
| // TODO: Replace with a OnceLock once MSRV is 1.70. |
| static LOAD_VALUE: Once = Once::new(); |
| static IS_SUPPORTED: AtomicBool = AtomicBool::new(false); |
| |
| // Using Relaxed ordering since the Once is providing synchronization. |
| LOAD_VALUE.call_once(|| { |
| IS_SUPPORTED.store( |
| is_amd64_emulation_supported_inner().unwrap_or(false), |
| Ordering::Relaxed, |
| ); |
| }); |
| IS_SUPPORTED.load(Ordering::Relaxed) |
| } |
| |
| impl MsvcTool { |
| fn new(tool: PathBuf) -> MsvcTool { |
| MsvcTool { |
| 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::with_family(tool, MSVC_FAMILY); |
| add_env(&mut tool, "LIB", libs); |
| add_env(&mut tool, "PATH", path); |
| add_env(&mut tool, "INCLUDE", include); |
| tool |
| } |
| } |
| |
| /// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the |
| /// given target's arch. Returns `None` if the variable does not exist. |
| fn is_vscmd_target(target: TargetArch<'_>) -> Option<bool> { |
| let vscmd_arch = env::var("VSCMD_ARG_TGT_ARCH").ok()?; |
| // Convert the Rust target arch to its VS arch equivalent. |
| let arch = match target.into() { |
| "x86_64" => "x64", |
| "aarch64" | "arm64ec" => "arm64", |
| "i686" | "i586" => "x86", |
| "thumbv7a" => "arm", |
| // An unrecognized arch. |
| _ => return Some(false), |
| }; |
| Some(vscmd_arch == arch) |
| } |
| |
| /// Attempt to find the tool using environment variables set by vcvars. |
| pub(super) fn find_msvc_environment(tool: &str, target: TargetArch<'_>) -> Option<Tool> { |
| // Early return if the environment doesn't contain a VC install. |
| if env::var_os("VCINSTALLDIR").is_none() { |
| return None; |
| } |
| let vs_install_dir = env::var_os("VSINSTALLDIR")?.into(); |
| |
| // If the vscmd target differs from the requested target then |
| // attempt to get the tool using the VS install directory. |
| if is_vscmd_target(target) == Some(false) { |
| // We will only get here with versions 15+. |
| tool_from_vs15plus_instance(tool, target, &vs_install_dir) |
| } else { |
| // Fallback to simply using the current environment. |
| env::var_os("PATH") |
| .and_then(|path| { |
| env::split_paths(&path) |
| .map(|p| p.join(tool)) |
| .find(|p| p.exists()) |
| }) |
| .map(|path| Tool::with_family(path, MSVC_FAMILY)) |
| } |
| } |
| |
| fn find_msbuild_vs17(target: TargetArch<'_>) -> Option<Tool> { |
| find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17") |
| } |
| |
| #[allow(bare_trait_objects)] |
| fn vs16plus_instances( |
| target: TargetArch<'_>, |
| version: &'static str, |
| ) -> Box<Iterator<Item = PathBuf>> { |
| let instances = if let Some(instances) = vs15plus_instances(target) { |
| instances |
| } else { |
| return Box::new(iter::empty()); |
| }; |
| Box::new(instances.into_iter().filter_map(move |instance| { |
| let installation_name = instance.installation_name()?; |
| if installation_name.starts_with(&format!("VisualStudio/{}.", version)) { |
| Some(instance.installation_path()?) |
| } else if installation_name.starts_with(&format!("VisualStudioPreview/{}.", version)) { |
| Some(instance.installation_path()?) |
| } else { |
| None |
| } |
| })) |
| } |
| |
| fn find_tool_in_vs16plus_path( |
| tool: &str, |
| target: TargetArch<'_>, |
| version: &'static str, |
| ) -> Option<Tool> { |
| vs16plus_instances(target, version) |
| .filter_map(|path| { |
| let path = path.join(tool); |
| if !path.is_file() { |
| return None; |
| } |
| let mut tool = Tool::with_family(path, MSVC_FAMILY); |
| if target == "x86_64" { |
| tool.env.push(("Platform".into(), "X64".into())); |
| } |
| if target == "aarch64" || target == "arm64ec" { |
| tool.env.push(("Platform".into(), "ARM64".into())); |
| } |
| Some(tool) |
| }) |
| .next() |
| } |
| |
| fn find_msbuild_vs16(target: TargetArch<'_>) -> Option<Tool> { |
| find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16") |
| } |
| |
| // 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. |
| // |
| // Note that much of this logic can be found [online] wrt paths, COM, etc. |
| // |
| // [online]: https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/ |
| // |
| // Returns MSVC 15+ instances (15, 16 right now), the order should be consider undefined. |
| // |
| // However, on ARM64 this method doesn't work because VS Installer fails to register COM component on ARM64. |
| // Hence, as the last resort we try to use vswhere.exe to list available instances. |
| fn vs15plus_instances(target: TargetArch<'_>) -> Option<VsInstances> { |
| vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target)) |
| } |
| |
| fn vs15plus_instances_using_com() -> Option<VsInstances> { |
| com::initialize().ok()?; |
| |
| let config = SetupConfiguration::new().ok()?; |
| let enum_setup_instances = config.enum_all_instances().ok()?; |
| |
| Some(VsInstances::ComBased(enum_setup_instances)) |
| } |
| |
| fn vs15plus_instances_using_vswhere(target: TargetArch<'_>) -> Option<VsInstances> { |
| let program_files_path: PathBuf = env::var("ProgramFiles(x86)") |
| .or_else(|_| env::var("ProgramFiles")) |
| .ok()? |
| .into(); |
| |
| let vswhere_path = |
| program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe"); |
| |
| if !vswhere_path.exists() { |
| return None; |
| } |
| |
| let tools_arch = match target.into() { |
| "i586" | "i686" | "x86_64" => Some("x86.x64"), |
| "arm" | "thumbv7a" => Some("ARM"), |
| "aarch64" | "arm64ec" => Some("ARM64"), |
| _ => None, |
| }; |
| |
| let vswhere_output = Command::new(vswhere_path) |
| .args(&[ |
| "-latest", |
| "-products", |
| "*", |
| "-requires", |
| &format!("Microsoft.VisualStudio.Component.VC.Tools.{}", tools_arch?), |
| "-format", |
| "text", |
| "-nologo", |
| ]) |
| .stderr(std::process::Stdio::inherit()) |
| .output() |
| .ok()?; |
| |
| let vs_instances = |
| VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?); |
| |
| Some(vs_instances) |
| } |
| |
| // Inspired from official microsoft/vswhere ParseVersionString |
| // i.e. at most four u16 numbers separated by '.' |
| fn parse_version(version: &str) -> Option<Vec<u16>> { |
| version |
| .split('.') |
| .map(|chunk| u16::from_str(chunk).ok()) |
| .collect() |
| } |
| |
| pub(super) fn find_msvc_15plus(tool: &str, target: TargetArch<'_>) -> Option<Tool> { |
| let iter = vs15plus_instances(target)?; |
| iter.into_iter() |
| .filter_map(|instance| { |
| let version = parse_version(&instance.installation_version()?)?; |
| let instance_path = instance.installation_path()?; |
| let tool = tool_from_vs15plus_instance(tool, target, &instance_path)?; |
| Some((version, tool)) |
| }) |
| .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version)) |
| .map(|(_version, tool)| tool) |
| } |
| |
| // While the paths to Visual Studio 2017's devenv and MSBuild could |
| // potentially be retrieved from the registry, finding them via |
| // SetupConfiguration has shown to be [more reliable], and is preferred |
| // according to Microsoft. To help head off potential regressions though, |
| // we keep the registry method as a fallback option. |
| // |
| // [more reliable]: https://github.com/rust-lang/cc-rs/pull/331 |
| fn find_tool_in_vs15_path(tool: &str, target: TargetArch<'_>) -> Option<Tool> { |
| let mut path = match vs15plus_instances(target) { |
| Some(instances) => instances |
| .into_iter() |
| .filter_map(|instance| instance.installation_path()) |
| .map(|path| path.join(tool)) |
| .find(|path| path.is_file()), |
| None => None, |
| }; |
| |
| if path.is_none() { |
| let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7"; |
| path = LOCAL_MACHINE |
| .open(key.as_ref()) |
| .ok() |
| .and_then(|key| key.query_str("15.0").ok()) |
| .map(|path| PathBuf::from(path).join(tool)) |
| .and_then(|path| if path.is_file() { Some(path) } else { None }); |
| } |
| |
| path.map(|path| { |
| let mut tool = Tool::with_family(path, MSVC_FAMILY); |
| if target == "x86_64" { |
| tool.env.push(("Platform".into(), "X64".into())); |
| } else if target == "aarch64" { |
| tool.env.push(("Platform".into(), "ARM64".into())); |
| } |
| tool |
| }) |
| } |
| |
| fn tool_from_vs15plus_instance( |
| tool: &str, |
| target: TargetArch<'_>, |
| instance_path: &PathBuf, |
| ) -> Option<Tool> { |
| let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) = |
| vs15plus_vc_paths(target, instance_path)?; |
| 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.path.push(host_dylib_path); |
| if let Some(alt_lib_path) = alt_lib_path { |
| tool.libs.push(alt_lib_path); |
| } |
| tool.libs.push(lib_path); |
| tool.include.push(include_path); |
| |
| if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &root_path) { |
| tool.libs.push(atl_lib_path); |
| tool.include.push(atl_include_path); |
| } |
| |
| add_sdks(&mut tool, target)?; |
| |
| Some(tool.into_tool()) |
| } |
| |
| fn vs15plus_vc_paths( |
| target: TargetArch<'_>, |
| instance_path: &Path, |
| ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> { |
| let version = vs15plus_vc_read_version(instance_path)?; |
| |
| let hosts = match host_arch() { |
| X86 => &["X86"], |
| X86_64 => &["X64"], |
| // Starting with VS 17.4, there is a natively hosted compiler on ARM64: |
| // https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/ |
| // On older versions of VS, we use x64 if running under emulation is supported, |
| // otherwise use x86. |
| AARCH64 => { |
| if is_amd64_emulation_supported() { |
| &["ARM64", "X64", "X86"][..] |
| } else { |
| &["ARM64", "X86"] |
| } |
| } |
| _ => return None, |
| }; |
| let target = lib_subdir(target)?; |
| // The directory layout here is MSVC/bin/Host$host/$target/ |
| let path = instance_path.join(r"VC\Tools\MSVC").join(version); |
| // We use the first available host architecture that can build for the target |
| let (host_path, host) = hosts.iter().find_map(|&x| { |
| let candidate = path.join("bin").join(format!("Host{}", x)); |
| if candidate.join(target).exists() { |
| Some((candidate, x)) |
| } else { |
| None |
| } |
| })?; |
| // This is the path to the toolchain for a particular target, running |
| // on a given host |
| let bin_path = host_path.join(target); |
| // But! we also need PATH to contain the target directory for the host |
| // architecture, because it contains dlls like mspdb140.dll compiled for |
| // the host architecture. |
| let host_dylib_path = host_path.join(host.to_lowercase()); |
| let lib_path = path.join("lib").join(target); |
| let alt_lib_path = (target == "arm64ec").then(|| path.join("lib").join("arm64ec")); |
| let include_path = path.join("include"); |
| Some(( |
| path, |
| bin_path, |
| host_dylib_path, |
| lib_path, |
| alt_lib_path, |
| include_path, |
| )) |
| } |
| |
| fn vs15plus_vc_read_version(dir: &Path) -> Option<String> { |
| // Try to open the default version file. |
| let mut version_path: PathBuf = |
| dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"); |
| let mut version_file = if let Ok(f) = File::open(&version_path) { |
| f |
| } else { |
| // If the default doesn't exist, search for other version files. |
| // These are in the form Microsoft.VCToolsVersion.v143.default.txt |
| // where `143` is any three decimal digit version number. |
| // This sorts versions by lexical order and selects the highest version. |
| let mut version_file = String::new(); |
| version_path.pop(); |
| for file in version_path.read_dir().ok()? { |
| let name = file.ok()?.file_name(); |
| let name = name.to_str()?; |
| if name.starts_with("Microsoft.VCToolsVersion.v") |
| && name.ends_with(".default.txt") |
| && name > &version_file |
| { |
| version_file.replace_range(.., name); |
| } |
| } |
| if version_file.is_empty() { |
| return None; |
| } |
| version_path.push(version_file); |
| File::open(version_path).ok()? |
| }; |
| |
| // Get the version string from the file we found. |
| let mut version = String::new(); |
| version_file.read_to_string(&mut version).ok()?; |
| version.truncate(version.trim_end().len()); |
| Some(version) |
| } |
| |
| fn atl_paths(target: TargetArch<'_>, path: &Path) -> Option<(PathBuf, PathBuf)> { |
| let atl_path = path.join("atlmfc"); |
| let sub = lib_subdir(target)?; |
| if atl_path.exists() { |
| Some((atl_path.join("lib").join(sub), atl_path.join("include"))) |
| } else { |
| None |
| } |
| } |
| |
| // For MSVC 14 we need to find the Universal CRT as well as either |
| // the Windows 10 SDK or Windows 8.1 SDK. |
| pub(super) fn find_msvc_14(tool: &str, target: TargetArch<'_>) -> Option<Tool> { |
| let vcdir = get_vc_dir("14.0")?; |
| let mut tool = get_tool(tool, &vcdir, target)?; |
| add_sdks(&mut tool, target)?; |
| Some(tool.into_tool()) |
| } |
| |
| fn add_sdks(tool: &mut MsvcTool, target: TargetArch<'_>) -> Option<()> { |
| let sub = lib_subdir(target)?; |
| let (ucrt, ucrt_version) = get_ucrt_dir()?; |
| |
| let host = match host_arch() { |
| X86 => "x86", |
| X86_64 => "x64", |
| AARCH64 => "arm64", |
| _ => return None, |
| }; |
| |
| tool.path |
| .push(ucrt.join("bin").join(&ucrt_version).join(host)); |
| |
| let ucrt_include = ucrt.join("include").join(&ucrt_version); |
| tool.include.push(ucrt_include.join("ucrt")); |
| |
| let ucrt_lib = ucrt.join("lib").join(&ucrt_version); |
| tool.libs.push(ucrt_lib.join("ucrt").join(sub)); |
| |
| if let Some((sdk, version)) = get_sdk10_dir() { |
| tool.path.push(sdk.join("bin").join(host)); |
| let sdk_lib = sdk.join("lib").join(&version); |
| tool.libs.push(sdk_lib.join("um").join(sub)); |
| let sdk_include = sdk.join("include").join(&version); |
| tool.include.push(sdk_include.join("um")); |
| tool.include.push(sdk_include.join("cppwinrt")); |
| tool.include.push(sdk_include.join("winrt")); |
| tool.include.push(sdk_include.join("shared")); |
| } else if let Some(sdk) = get_sdk81_dir() { |
| tool.path.push(sdk.join("bin").join(host)); |
| let sdk_lib = sdk.join("lib").join("winv6.3"); |
| tool.libs.push(sdk_lib.join("um").join(sub)); |
| let sdk_include = sdk.join("include"); |
| tool.include.push(sdk_include.join("um")); |
| tool.include.push(sdk_include.join("winrt")); |
| tool.include.push(sdk_include.join("shared")); |
| } |
| |
| Some(()) |
| } |
| |
| // For MSVC 12 we need to find the Windows 8.1 SDK. |
| pub(super) fn find_msvc_12(tool: &str, target: TargetArch<'_>) -> Option<Tool> { |
| let vcdir = get_vc_dir("12.0")?; |
| let mut tool = get_tool(tool, &vcdir, target)?; |
| let sub = lib_subdir(target)?; |
| let sdk81 = get_sdk81_dir()?; |
| tool.path.push(sdk81.join("bin").join(sub)); |
| let sdk_lib = sdk81.join("lib").join("winv6.3"); |
| tool.libs.push(sdk_lib.join("um").join(sub)); |
| let sdk_include = sdk81.join("include"); |
| tool.include.push(sdk_include.join("shared")); |
| tool.include.push(sdk_include.join("um")); |
| tool.include.push(sdk_include.join("winrt")); |
| Some(tool.into_tool()) |
| } |
| |
| fn add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>) { |
| 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: TargetArch<'_>) -> Option<MsvcTool> { |
| bin_subdir(target) |
| .into_iter() |
| .map(|(sub, host)| { |
| ( |
| path.join("bin").join(sub).join(tool), |
| path.join("bin").join(host), |
| ) |
| }) |
| .filter(|(path, _)| path.is_file()) |
| .map(|(path, host)| { |
| let mut tool = MsvcTool::new(path); |
| tool.path.push(host); |
| tool |
| }) |
| .filter_map(|mut tool| { |
| let sub = vc_lib_subdir(target)?; |
| tool.libs.push(path.join("lib").join(sub)); |
| tool.include.push(path.join("include")); |
| let atlmfc_path = path.join("atlmfc"); |
| if atlmfc_path.exists() { |
| tool.libs.push(atlmfc_path.join("lib").join(sub)); |
| tool.include.push(atlmfc_path.join("include")); |
| } |
| Some(tool) |
| }) |
| .next() |
| } |
| |
| // 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<PathBuf> { |
| let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7"; |
| let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
| let path = key.query_str(ver).ok()?; |
| Some(path.into()) |
| } |
| |
| // 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. |
| // |
| // 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 = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
| let root = key.query_str("KitsRoot10").ok()?; |
| let readdir = Path::new(&root).join("lib").read_dir().ok()?; |
| let max_libdir = 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. |
| // Before doing that, we check the "WindowsSdkDir" and "WindowsSDKVersion" |
| // environment variables set by vcvars to use the environment sdk version |
| // if one is already configured. |
| fn get_sdk10_dir() -> Option<(PathBuf, String)> { |
| if let (Ok(root), Ok(version)) = (env::var("WindowsSdkDir"), env::var("WindowsSDKVersion")) |
| { |
| return Some((root.into(), version.trim_end_matches('\\').to_string())); |
| } |
| |
| let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0"; |
| let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
| let root = key.query_str("InstallationFolder").ok()?; |
| let readdir = Path::new(&root).join("lib").read_dir().ok()?; |
| let mut dirs = readdir |
| .filter_map(|dir| dir.ok()) |
| .map(|dir| dir.path()) |
| .collect::<Vec<_>>(); |
| dirs.sort(); |
| let dir = dirs |
| .into_iter() |
| .rev() |
| .filter(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file()) |
| .next()?; |
| let version = dir.components().last().unwrap(); |
| let version = version.as_os_str().to_str().unwrap().to_string(); |
| Some((root.into(), version)) |
| } |
| |
| // 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 targeting kernel mode drivers |
| // instead of user mode applications, we would care. |
| fn get_sdk81_dir() -> Option<PathBuf> { |
| let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1"; |
| let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
| let root = key.query_str("InstallationFolder").ok()?; |
| Some(root.into()) |
| } |
| |
| const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0; |
| const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9; |
| const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12; |
| const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL; |
| const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64; |
| const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64; |
| |
| // 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: TargetArch<'_>) -> Vec<(&'static str, &'static str)> { |
| match (target.into(), host_arch()) { |
| ("i586", X86) | ("i686", X86) => vec![("", "")], |
| ("i586", X86_64) | ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")], |
| ("x86_64", X86) => vec![("x86_amd64", "")], |
| ("x86_64", X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")], |
| ("arm", X86) | ("thumbv7a", X86) => vec![("x86_arm", "")], |
| ("arm", X86_64) | ("thumbv7a", X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")], |
| _ => vec![], |
| } |
| } |
| |
| fn lib_subdir(target: TargetArch<'_>) -> Option<&'static str> { |
| match target.into() { |
| "i586" | "i686" => Some("x86"), |
| "x86_64" => Some("x64"), |
| "arm" | "thumbv7a" => Some("arm"), |
| "aarch64" | "arm64ec" => Some("arm64"), |
| _ => None, |
| } |
| } |
| |
| // MSVC's x86 libraries are not in a subfolder |
| fn vc_lib_subdir(target: TargetArch<'_>) -> Option<&'static str> { |
| match target.into() { |
| "i586" | "i686" => Some(""), |
| "x86_64" => Some("amd64"), |
| "arm" | "thumbv7a" => Some("arm"), |
| "aarch64" => Some("arm64"), |
| _ => None, |
| } |
| } |
| |
| #[allow(bad_style)] |
| fn host_arch() -> u16 { |
| type DWORD = u32; |
| type WORD = u16; |
| type LPVOID = *mut u8; |
| type DWORD_PTR = usize; |
| |
| #[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 |
| // 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)); |
| } |
| } |
| } |
| max_key |
| } |
| |
| pub(super) fn has_msbuild_version(version: &str) -> bool { |
| match version { |
| "17.0" => { |
| find_msbuild_vs17(TargetArch("x86_64")).is_some() |
| || find_msbuild_vs17(TargetArch("i686")).is_some() |
| || find_msbuild_vs17(TargetArch("aarch64")).is_some() |
| } |
| "16.0" => { |
| find_msbuild_vs16(TargetArch("x86_64")).is_some() |
| || find_msbuild_vs16(TargetArch("i686")).is_some() |
| || find_msbuild_vs16(TargetArch("aarch64")).is_some() |
| } |
| "15.0" => { |
| find_msbuild_vs15(TargetArch("x86_64")).is_some() |
| || find_msbuild_vs15(TargetArch("i686")).is_some() |
| || find_msbuild_vs15(TargetArch("aarch64")).is_some() |
| } |
| "12.0" | "14.0" => LOCAL_MACHINE |
| .open(&OsString::from(format!( |
| "SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}", |
| version |
| ))) |
| .is_ok(), |
| _ => false, |
| } |
| } |
| |
| pub(super) fn find_devenv(target: TargetArch<'_>) -> Option<Tool> { |
| find_devenv_vs15(target) |
| } |
| |
| fn find_devenv_vs15(target: TargetArch<'_>) -> Option<Tool> { |
| find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target) |
| } |
| |
| // see http://stackoverflow.com/questions/328017/path-to-msbuild |
| pub(super) fn find_msbuild(target: TargetArch<'_>) -> Option<Tool> { |
| // VS 15 (2017) changed how to locate msbuild |
| if let Some(r) = find_msbuild_vs17(target) { |
| Some(r) |
| } else if let Some(r) = find_msbuild_vs16(target) { |
| return Some(r); |
| } else if let Some(r) = find_msbuild_vs15(target) { |
| return Some(r); |
| } else { |
| find_old_msbuild(target) |
| } |
| } |
| |
| fn find_msbuild_vs15(target: TargetArch<'_>) -> Option<Tool> { |
| find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target) |
| } |
| |
| fn find_old_msbuild(target: TargetArch<'_>) -> Option<Tool> { |
| let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions"; |
| LOCAL_MACHINE |
| .open(key.as_ref()) |
| .ok() |
| .and_then(|key| { |
| max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok()) |
| }) |
| .map(|path| { |
| let mut path = PathBuf::from(path); |
| path.push("MSBuild.exe"); |
| let mut tool = Tool::with_family(path, MSVC_FAMILY); |
| if target == "x86_64" { |
| tool.env.push(("Platform".into(), "X64".into())); |
| } |
| tool |
| }) |
| } |
| } |
| |
| /// Non-Windows Implementation. |
| #[cfg(not(windows))] |
| mod impl_ { |
| use std::{env, ffi::OsString}; |
| |
| use super::{TargetArch, MSVC_FAMILY}; |
| use crate::Tool; |
| |
| /// Finding msbuild.exe tool under unix system is not currently supported. |
| /// Maybe can check it using an environment variable looks like `MSBUILD_BIN`. |
| pub(super) fn find_msbuild(_target: TargetArch<'_>) -> Option<Tool> { |
| None |
| } |
| |
| // Finding devenv.exe tool under unix system is not currently supported. |
| // Maybe can check it using an environment variable looks like `DEVENV_BIN`. |
| pub(super) fn find_devenv(_target: TargetArch<'_>) -> Option<Tool> { |
| None |
| } |
| |
| /// Attempt to find the tool using environment variables set by vcvars. |
| pub(super) fn find_msvc_environment(tool: &str, _target: TargetArch<'_>) -> Option<Tool> { |
| // Early return if the environment doesn't contain a VC install. |
| let vc_install_dir = env::var_os("VCINSTALLDIR")?; |
| let vs_install_dir = env::var_os("VSINSTALLDIR")?; |
| |
| let get_tool = |install_dir: OsString| { |
| env::split_paths(&install_dir) |
| .map(|p| p.join(tool)) |
| .find(|p| p.exists()) |
| .map(|path| Tool::with_family(path.into(), MSVC_FAMILY)) |
| }; |
| |
| // Take the path of tool for the vc install directory. |
| get_tool(vc_install_dir) |
| // Take the path of tool for the vs install directory. |
| .or_else(|| get_tool(vs_install_dir)) |
| // Take the path of tool for the current path environment. |
| .or_else(|| env::var_os("PATH").and_then(|path| get_tool(path))) |
| } |
| |
| pub(super) fn find_msvc_15plus(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> { |
| None |
| } |
| |
| // For MSVC 14 we need to find the Universal CRT as well as either |
| // the Windows 10 SDK or Windows 8.1 SDK. |
| pub(super) fn find_msvc_14(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> { |
| None |
| } |
| |
| // For MSVC 12 we need to find the Windows 8.1 SDK. |
| pub(super) fn find_msvc_12(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> { |
| None |
| } |
| |
| pub(super) fn has_msbuild_version(version: &str) -> bool { |
| match version { |
| "17.0" => false, |
| "16.0" => false, |
| "15.0" => false, |
| "12.0" | "14.0" => false, |
| _ => false, |
| } |
| } |
| } |