| 1 | // Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
| 2 | // file at the top-level directory of this distribution and at |
| 3 | // http://rust-lang.org/COPYRIGHT. |
| 4 | // |
| 5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 6 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 7 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| 8 | // option. This file may not be copied, modified, or distributed |
| 9 | // except according to those terms. |
| 10 | |
| 11 | //! A helper module to looking for windows-specific tools: |
| 12 | //! 1. On Windows host, probe the Windows Registry if needed; |
| 13 | //! 2. On non-Windows host, check specified environment variables. |
| 14 | |
| 15 | #![allow (clippy::upper_case_acronyms)] |
| 16 | |
| 17 | use std::{ |
| 18 | env, |
| 19 | ffi::{OsStr, OsString}, |
| 20 | ops::Deref, |
| 21 | path::PathBuf, |
| 22 | process::Command, |
| 23 | sync::Arc, |
| 24 | }; |
| 25 | |
| 26 | use crate::Tool; |
| 27 | use crate::ToolFamily; |
| 28 | |
| 29 | const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false }; |
| 30 | |
| 31 | #[derive (Copy, Clone)] |
| 32 | struct TargetArch<'a>(pub &'a str); |
| 33 | |
| 34 | impl PartialEq<&str> for TargetArch<'_> { |
| 35 | fn eq(&self, other: &&str) -> bool { |
| 36 | self.0 == *other |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | impl<'a> From<TargetArch<'a>> for &'a str { |
| 41 | fn from(target: TargetArch<'a>) -> Self { |
| 42 | target.0 |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | pub(crate) enum Env { |
| 47 | Owned(OsString), |
| 48 | Arced(Arc<OsStr>), |
| 49 | } |
| 50 | |
| 51 | impl AsRef<OsStr> for Env { |
| 52 | fn as_ref(&self) -> &OsStr { |
| 53 | self.deref() |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | impl Deref for Env { |
| 58 | type Target = OsStr; |
| 59 | |
| 60 | fn deref(&self) -> &Self::Target { |
| 61 | match self { |
| 62 | Env::Owned(os_str: &OsString) => os_str, |
| 63 | Env::Arced(os_str: &Arc) => os_str, |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | impl From<Env> for PathBuf { |
| 69 | fn from(env: Env) -> Self { |
| 70 | match env { |
| 71 | Env::Owned(os_str: OsString) => PathBuf::from(os_str), |
| 72 | Env::Arced(os_str: Arc) => PathBuf::from(os_str.deref()), |
| 73 | } |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | pub(crate) trait EnvGetter { |
| 78 | fn get_env(&self, name: &'static str) -> Option<Env>; |
| 79 | } |
| 80 | |
| 81 | struct StdEnvGetter; |
| 82 | |
| 83 | impl EnvGetter for StdEnvGetter { |
| 84 | #[allow (clippy::disallowed_methods)] |
| 85 | fn get_env(&self, name: &'static str) -> Option<Env> { |
| 86 | env::var_os(key:name).map(Env::Owned) |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | /// Attempts to find a tool within an MSVC installation using the Windows |
| 91 | /// registry as a point to search from. |
| 92 | /// |
| 93 | /// The `target` argument is the target that the tool should work for (e.g. |
| 94 | /// compile or link for) and the `tool` argument is the tool to find (e.g. |
| 95 | /// `cl.exe` or `link.exe`). |
| 96 | /// |
| 97 | /// This function will return `None` if the tool could not be found, or it will |
| 98 | /// return `Some(cmd)` which represents a command that's ready to execute the |
| 99 | /// tool with the appropriate environment variables set. |
| 100 | /// |
| 101 | /// Note that this function always returns `None` for non-MSVC targets. |
| 102 | pub fn find(target: &str, tool: &str) -> Option<Command> { |
| 103 | find_tool(target, tool).map(|c: Tool| c.to_command()) |
| 104 | } |
| 105 | |
| 106 | /// Similar to the `find` function above, this function will attempt the same |
| 107 | /// operation (finding a MSVC tool in a local install) but instead returns a |
| 108 | /// `Tool` which may be introspected. |
| 109 | pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { |
| 110 | find_tool_inner(target, tool, &StdEnvGetter) |
| 111 | } |
| 112 | |
| 113 | pub(crate) fn find_tool_inner( |
| 114 | target: &str, |
| 115 | tool: &str, |
| 116 | env_getter: &dyn EnvGetter, |
| 117 | ) -> Option<Tool> { |
| 118 | // This logic is all tailored for MSVC, if we're not that then bail out |
| 119 | // early. |
| 120 | if !target.contains("msvc" ) { |
| 121 | return None; |
| 122 | } |
| 123 | |
| 124 | // Split the target to get the arch. |
| 125 | let target = TargetArch(target.split_once('-' )?.0); |
| 126 | |
| 127 | // Looks like msbuild isn't located in the same location as other tools like |
| 128 | // cl.exe and lib.exe. |
| 129 | if tool.contains("msbuild" ) { |
| 130 | return impl_::find_msbuild(target, env_getter); |
| 131 | } |
| 132 | |
| 133 | // Looks like devenv isn't located in the same location as other tools like |
| 134 | // cl.exe and lib.exe. |
| 135 | if tool.contains("devenv" ) { |
| 136 | return impl_::find_devenv(target, env_getter); |
| 137 | } |
| 138 | |
| 139 | // Ok, if we're here, now comes the fun part of the probing. Default shells |
| 140 | // or shells like MSYS aren't really configured to execute `cl.exe` and the |
| 141 | // various compiler tools shipped as part of Visual Studio. Here we try to |
| 142 | // first find the relevant tool, then we also have to be sure to fill in |
| 143 | // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that |
| 144 | // the tool is actually usable. |
| 145 | |
| 146 | impl_::find_msvc_environment(tool, target, env_getter) |
| 147 | .or_else(|| impl_::find_msvc_15plus(tool, target, env_getter)) |
| 148 | .or_else(|| impl_::find_msvc_14(tool, target, env_getter)) |
| 149 | } |
| 150 | |
| 151 | /// A version of Visual Studio |
| 152 | #[derive (Debug, PartialEq, Eq, Copy, Clone)] |
| 153 | #[non_exhaustive ] |
| 154 | pub enum VsVers { |
| 155 | /// Visual Studio 12 (2013) |
| 156 | #[deprecated ( |
| 157 | note = "Visual Studio 12 is no longer supported. cc will never return this value." |
| 158 | )] |
| 159 | Vs12, |
| 160 | /// Visual Studio 14 (2015) |
| 161 | Vs14, |
| 162 | /// Visual Studio 15 (2017) |
| 163 | Vs15, |
| 164 | /// Visual Studio 16 (2019) |
| 165 | Vs16, |
| 166 | /// Visual Studio 17 (2022) |
| 167 | Vs17, |
| 168 | } |
| 169 | |
| 170 | /// Find the most recent installed version of Visual Studio |
| 171 | /// |
| 172 | /// This is used by the cmake crate to figure out the correct |
| 173 | /// generator. |
| 174 | #[allow (clippy::disallowed_methods)] |
| 175 | pub fn find_vs_version() -> Result<VsVers, String> { |
| 176 | fn has_msbuild_version(version: &str) -> bool { |
| 177 | impl_::has_msbuild_version(version, &StdEnvGetter) |
| 178 | } |
| 179 | |
| 180 | match std::env::var("VisualStudioVersion" ) { |
| 181 | Ok(version) => match &version[..] { |
| 182 | "17.0" => Ok(VsVers::Vs17), |
| 183 | "16.0" => Ok(VsVers::Vs16), |
| 184 | "15.0" => Ok(VsVers::Vs15), |
| 185 | "14.0" => Ok(VsVers::Vs14), |
| 186 | vers => Err(format!( |
| 187 | " \n\n\ |
| 188 | unsupported or unknown VisualStudio version: {}\n\ |
| 189 | if another version is installed consider running \ |
| 190 | the appropriate vcvars script before building this \ |
| 191 | crate \n\ |
| 192 | " , |
| 193 | vers |
| 194 | )), |
| 195 | }, |
| 196 | _ => { |
| 197 | // Check for the presence of a specific registry key |
| 198 | // that indicates visual studio is installed. |
| 199 | if has_msbuild_version("17.0" ) { |
| 200 | Ok(VsVers::Vs17) |
| 201 | } else if has_msbuild_version("16.0" ) { |
| 202 | Ok(VsVers::Vs16) |
| 203 | } else if has_msbuild_version("15.0" ) { |
| 204 | Ok(VsVers::Vs15) |
| 205 | } else if has_msbuild_version("14.0" ) { |
| 206 | Ok(VsVers::Vs14) |
| 207 | } else { |
| 208 | Err(" \n\n\ |
| 209 | couldn't determine visual studio generator \n\ |
| 210 | if VisualStudio is installed, however, consider \ |
| 211 | running the appropriate vcvars script before building \ |
| 212 | this crate \n\ |
| 213 | " |
| 214 | .to_string()) |
| 215 | } |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | /// Windows Implementation. |
| 221 | #[cfg (windows)] |
| 222 | mod impl_ { |
| 223 | use crate::windows::com; |
| 224 | use crate::windows::registry::{RegistryKey, LOCAL_MACHINE}; |
| 225 | use crate::windows::setup_config::SetupConfiguration; |
| 226 | use crate::windows::vs_instances::{VsInstances, VswhereInstance}; |
| 227 | use crate::windows::windows_sys::{ |
| 228 | GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE, |
| 229 | IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK, |
| 230 | }; |
| 231 | use std::convert::TryFrom; |
| 232 | use std::env; |
| 233 | use std::ffi::OsString; |
| 234 | use std::fs::File; |
| 235 | use std::io::Read; |
| 236 | use std::iter; |
| 237 | use std::mem; |
| 238 | use std::path::{Path, PathBuf}; |
| 239 | use std::process::Command; |
| 240 | use std::str::FromStr; |
| 241 | use std::sync::atomic::{AtomicBool, Ordering}; |
| 242 | use std::sync::Once; |
| 243 | |
| 244 | use super::{EnvGetter, TargetArch, MSVC_FAMILY}; |
| 245 | use crate::Tool; |
| 246 | |
| 247 | struct MsvcTool { |
| 248 | tool: PathBuf, |
| 249 | libs: Vec<PathBuf>, |
| 250 | path: Vec<PathBuf>, |
| 251 | include: Vec<PathBuf>, |
| 252 | } |
| 253 | |
| 254 | struct LibraryHandle(HMODULE); |
| 255 | |
| 256 | impl LibraryHandle { |
| 257 | fn new(name: &[u8]) -> Option<Self> { |
| 258 | let handle = unsafe { LoadLibraryA(name.as_ptr() as _) }; |
| 259 | (!handle.is_null()).then_some(Self(handle)) |
| 260 | } |
| 261 | |
| 262 | /// Get a function pointer to a function in the library. |
| 263 | /// # SAFETY |
| 264 | /// |
| 265 | /// The caller must ensure that the function signature matches the actual function. |
| 266 | /// The easiest way to do this is to add an entry to windows_sys_no_link.list and use the |
| 267 | /// generated function for `func_signature`. |
| 268 | /// |
| 269 | /// The function returned cannot be used after the handle is dropped. |
| 270 | unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> { |
| 271 | let symbol = GetProcAddress(self.0, name.as_ptr() as _); |
| 272 | symbol.map(|symbol| mem::transmute_copy(&symbol)) |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | type GetMachineTypeAttributesFuncType = |
| 277 | unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32; |
| 278 | const _: () = { |
| 279 | // Ensure that our hand-written signature matches the actual function signature. |
| 280 | // We can't use `GetMachineTypeAttributes` outside of a const scope otherwise we'll end up statically linking to |
| 281 | // it, which will fail to load on older versions of Windows. |
| 282 | let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes; |
| 283 | }; |
| 284 | |
| 285 | fn is_amd64_emulation_supported_inner() -> Option<bool> { |
| 286 | // GetMachineTypeAttributes is only available on Win11 22000+, so dynamically load it. |
| 287 | let kernel32 = LibraryHandle::new(b"kernel32.dll \0" )?; |
| 288 | // SAFETY: GetMachineTypeAttributesFuncType is checked to match the real function signature. |
| 289 | let get_machine_type_attributes = unsafe { |
| 290 | kernel32 |
| 291 | .get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes \0" ) |
| 292 | }?; |
| 293 | let mut attributes = Default::default(); |
| 294 | if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK |
| 295 | { |
| 296 | Some((attributes & UserEnabled) != 0) |
| 297 | } else { |
| 298 | Some(false) |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | fn is_amd64_emulation_supported() -> bool { |
| 303 | // TODO: Replace with a OnceLock once MSRV is 1.70. |
| 304 | static LOAD_VALUE: Once = Once::new(); |
| 305 | static IS_SUPPORTED: AtomicBool = AtomicBool::new(false); |
| 306 | |
| 307 | // Using Relaxed ordering since the Once is providing synchronization. |
| 308 | LOAD_VALUE.call_once(|| { |
| 309 | IS_SUPPORTED.store( |
| 310 | is_amd64_emulation_supported_inner().unwrap_or(false), |
| 311 | Ordering::Relaxed, |
| 312 | ); |
| 313 | }); |
| 314 | IS_SUPPORTED.load(Ordering::Relaxed) |
| 315 | } |
| 316 | |
| 317 | impl MsvcTool { |
| 318 | fn new(tool: PathBuf) -> MsvcTool { |
| 319 | MsvcTool { |
| 320 | tool, |
| 321 | libs: Vec::new(), |
| 322 | path: Vec::new(), |
| 323 | include: Vec::new(), |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | fn into_tool(self, env_getter: &dyn EnvGetter) -> Tool { |
| 328 | let MsvcTool { |
| 329 | tool, |
| 330 | libs, |
| 331 | path, |
| 332 | include, |
| 333 | } = self; |
| 334 | let mut tool = Tool::with_family(tool, MSVC_FAMILY); |
| 335 | add_env(&mut tool, "LIB" , libs, env_getter); |
| 336 | add_env(&mut tool, "PATH" , path, env_getter); |
| 337 | add_env(&mut tool, "INCLUDE" , include, env_getter); |
| 338 | tool |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | /// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the |
| 343 | /// given target's arch. Returns `None` if the variable does not exist. |
| 344 | fn is_vscmd_target(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<bool> { |
| 345 | let vscmd_arch = env_getter.get_env("VSCMD_ARG_TGT_ARCH" )?; |
| 346 | // Convert the Rust target arch to its VS arch equivalent. |
| 347 | let arch = match target.into() { |
| 348 | "x86_64" => "x64" , |
| 349 | "aarch64" | "arm64ec" => "arm64" , |
| 350 | "i686" | "i586" => "x86" , |
| 351 | "thumbv7a" => "arm" , |
| 352 | // An unrecognized arch. |
| 353 | _ => return Some(false), |
| 354 | }; |
| 355 | Some(vscmd_arch.as_ref() == arch) |
| 356 | } |
| 357 | |
| 358 | /// Attempt to find the tool using environment variables set by vcvars. |
| 359 | pub(super) fn find_msvc_environment( |
| 360 | tool: &str, |
| 361 | target: TargetArch<'_>, |
| 362 | env_getter: &dyn EnvGetter, |
| 363 | ) -> Option<Tool> { |
| 364 | // Early return if the environment isn't one that is known to have compiler toolsets in PATH |
| 365 | // `VCINSTALLDIR` is set from vcvarsall.bat (developer command prompt) |
| 366 | // `VSTEL_MSBuildProjectFullPath` is set by msbuild when invoking custom build steps |
| 367 | // NOTE: `VisualStudioDir` used to be used but this isn't set when invoking msbuild from the commandline |
| 368 | if env_getter.get_env("VCINSTALLDIR" ).is_none() |
| 369 | && env_getter.get_env("VSTEL_MSBuildProjectFullPath" ).is_none() |
| 370 | { |
| 371 | return None; |
| 372 | } |
| 373 | |
| 374 | // If the vscmd target differs from the requested target then |
| 375 | // attempt to get the tool using the VS install directory. |
| 376 | if is_vscmd_target(target, env_getter) == Some(false) { |
| 377 | // We will only get here with versions 15+. |
| 378 | let vs_install_dir: PathBuf = env_getter.get_env("VSINSTALLDIR" )?.into(); |
| 379 | tool_from_vs15plus_instance(tool, target, &vs_install_dir, env_getter) |
| 380 | } else { |
| 381 | // Fallback to simply using the current environment. |
| 382 | env_getter |
| 383 | .get_env("PATH" ) |
| 384 | .and_then(|path| { |
| 385 | env::split_paths(&path) |
| 386 | .map(|p| p.join(tool)) |
| 387 | .find(|p| p.exists()) |
| 388 | }) |
| 389 | .map(|path| Tool::with_family(path, MSVC_FAMILY)) |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | fn find_msbuild_vs17(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> { |
| 394 | find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe" , target, "17" , env_getter) |
| 395 | } |
| 396 | |
| 397 | #[allow (bare_trait_objects)] |
| 398 | fn vs16plus_instances( |
| 399 | target: TargetArch<'_>, |
| 400 | version: &'static str, |
| 401 | env_getter: &dyn EnvGetter, |
| 402 | ) -> Box<Iterator<Item = PathBuf>> { |
| 403 | let instances = if let Some(instances) = vs15plus_instances(target, env_getter) { |
| 404 | instances |
| 405 | } else { |
| 406 | return Box::new(iter::empty()); |
| 407 | }; |
| 408 | Box::new(instances.into_iter().filter_map(move |instance| { |
| 409 | let installation_name = instance.installation_name()?; |
| 410 | if installation_name.starts_with(&format!("VisualStudio/{}." , version)) |
| 411 | || installation_name.starts_with(&format!("VisualStudioPreview/{}." , version)) |
| 412 | { |
| 413 | Some(instance.installation_path()?) |
| 414 | } else { |
| 415 | None |
| 416 | } |
| 417 | })) |
| 418 | } |
| 419 | |
| 420 | fn find_tool_in_vs16plus_path( |
| 421 | tool: &str, |
| 422 | target: TargetArch<'_>, |
| 423 | version: &'static str, |
| 424 | env_getter: &dyn EnvGetter, |
| 425 | ) -> Option<Tool> { |
| 426 | vs16plus_instances(target, version, env_getter) |
| 427 | .filter_map(|path| { |
| 428 | let path = path.join(tool); |
| 429 | if !path.is_file() { |
| 430 | return None; |
| 431 | } |
| 432 | let mut tool = Tool::with_family(path, MSVC_FAMILY); |
| 433 | if target == "x86_64" { |
| 434 | tool.env.push(("Platform" .into(), "X64" .into())); |
| 435 | } |
| 436 | if target == "aarch64" || target == "arm64ec" { |
| 437 | tool.env.push(("Platform" .into(), "ARM64" .into())); |
| 438 | } |
| 439 | Some(tool) |
| 440 | }) |
| 441 | .next() |
| 442 | } |
| 443 | |
| 444 | fn find_msbuild_vs16(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> { |
| 445 | find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe" , target, "16" , env_getter) |
| 446 | } |
| 447 | |
| 448 | // In MSVC 15 (2017) MS once again changed the scheme for locating |
| 449 | // the tooling. Now we must go through some COM interfaces, which |
| 450 | // is super fun for Rust. |
| 451 | // |
| 452 | // Note that much of this logic can be found [online] wrt paths, COM, etc. |
| 453 | // |
| 454 | // [online]: https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/ |
| 455 | // |
| 456 | // Returns MSVC 15+ instances (15, 16 right now), the order should be consider undefined. |
| 457 | // |
| 458 | // However, on ARM64 this method doesn't work because VS Installer fails to register COM component on ARM64. |
| 459 | // Hence, as the last resort we try to use vswhere.exe to list available instances. |
| 460 | fn vs15plus_instances( |
| 461 | target: TargetArch<'_>, |
| 462 | env_getter: &dyn EnvGetter, |
| 463 | ) -> Option<VsInstances> { |
| 464 | vs15plus_instances_using_com() |
| 465 | .or_else(|| vs15plus_instances_using_vswhere(target, env_getter)) |
| 466 | } |
| 467 | |
| 468 | fn vs15plus_instances_using_com() -> Option<VsInstances> { |
| 469 | com::initialize().ok()?; |
| 470 | |
| 471 | let config = SetupConfiguration::new().ok()?; |
| 472 | let enum_setup_instances = config.enum_all_instances().ok()?; |
| 473 | |
| 474 | Some(VsInstances::ComBased(enum_setup_instances)) |
| 475 | } |
| 476 | |
| 477 | fn vs15plus_instances_using_vswhere( |
| 478 | target: TargetArch<'_>, |
| 479 | env_getter: &dyn EnvGetter, |
| 480 | ) -> Option<VsInstances> { |
| 481 | let program_files_path = env_getter |
| 482 | .get_env("ProgramFiles(x86)" ) |
| 483 | .or_else(|| env_getter.get_env("ProgramFiles" ))?; |
| 484 | |
| 485 | let program_files_path = Path::new(program_files_path.as_ref()); |
| 486 | |
| 487 | let vswhere_path = |
| 488 | program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe" ); |
| 489 | |
| 490 | if !vswhere_path.exists() { |
| 491 | return None; |
| 492 | } |
| 493 | |
| 494 | let tools_arch = match target.into() { |
| 495 | "i586" | "i686" | "x86_64" => Some("x86.x64" ), |
| 496 | "arm" | "thumbv7a" => Some("ARM" ), |
| 497 | "aarch64" | "arm64ec" => Some("ARM64" ), |
| 498 | _ => None, |
| 499 | }; |
| 500 | |
| 501 | let vswhere_output = Command::new(vswhere_path) |
| 502 | .args([ |
| 503 | "-latest" , |
| 504 | "-products" , |
| 505 | "*" , |
| 506 | "-requires" , |
| 507 | &format!("Microsoft.VisualStudio.Component.VC.Tools.{}" , tools_arch?), |
| 508 | "-format" , |
| 509 | "text" , |
| 510 | "-nologo" , |
| 511 | ]) |
| 512 | .stderr(std::process::Stdio::inherit()) |
| 513 | .output() |
| 514 | .ok()?; |
| 515 | |
| 516 | let vs_instances = |
| 517 | VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?); |
| 518 | |
| 519 | Some(vs_instances) |
| 520 | } |
| 521 | |
| 522 | // Inspired from official microsoft/vswhere ParseVersionString |
| 523 | // i.e. at most four u16 numbers separated by '.' |
| 524 | fn parse_version(version: &str) -> Option<Vec<u16>> { |
| 525 | version |
| 526 | .split('.' ) |
| 527 | .map(|chunk| u16::from_str(chunk).ok()) |
| 528 | .collect() |
| 529 | } |
| 530 | |
| 531 | pub(super) fn find_msvc_15plus( |
| 532 | tool: &str, |
| 533 | target: TargetArch<'_>, |
| 534 | env_getter: &dyn EnvGetter, |
| 535 | ) -> Option<Tool> { |
| 536 | let iter = vs15plus_instances(target, env_getter)?; |
| 537 | iter.into_iter() |
| 538 | .filter_map(|instance| { |
| 539 | let version = parse_version(&instance.installation_version()?)?; |
| 540 | let instance_path = instance.installation_path()?; |
| 541 | let tool = tool_from_vs15plus_instance(tool, target, &instance_path, env_getter)?; |
| 542 | Some((version, tool)) |
| 543 | }) |
| 544 | .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version)) |
| 545 | .map(|(_version, tool)| tool) |
| 546 | } |
| 547 | |
| 548 | // While the paths to Visual Studio 2017's devenv and MSBuild could |
| 549 | // potentially be retrieved from the registry, finding them via |
| 550 | // SetupConfiguration has shown to be [more reliable], and is preferred |
| 551 | // according to Microsoft. To help head off potential regressions though, |
| 552 | // we keep the registry method as a fallback option. |
| 553 | // |
| 554 | // [more reliable]: https://github.com/rust-lang/cc-rs/pull/331 |
| 555 | fn find_tool_in_vs15_path( |
| 556 | tool: &str, |
| 557 | target: TargetArch<'_>, |
| 558 | env_getter: &dyn EnvGetter, |
| 559 | ) -> Option<Tool> { |
| 560 | let mut path = match vs15plus_instances(target, env_getter) { |
| 561 | Some(instances) => instances |
| 562 | .into_iter() |
| 563 | .filter_map(|instance| instance.installation_path()) |
| 564 | .map(|path| path.join(tool)) |
| 565 | .find(|path| path.is_file()), |
| 566 | None => None, |
| 567 | }; |
| 568 | |
| 569 | if path.is_none() { |
| 570 | let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7" ; |
| 571 | path = LOCAL_MACHINE |
| 572 | .open(key.as_ref()) |
| 573 | .ok() |
| 574 | .and_then(|key| key.query_str("15.0" ).ok()) |
| 575 | .map(|path| PathBuf::from(path).join(tool)) |
| 576 | .and_then(|path| if path.is_file() { Some(path) } else { None }); |
| 577 | } |
| 578 | |
| 579 | path.map(|path| { |
| 580 | let mut tool = Tool::with_family(path, MSVC_FAMILY); |
| 581 | if target == "x86_64" { |
| 582 | tool.env.push(("Platform" .into(), "X64" .into())); |
| 583 | } else if target == "aarch64" { |
| 584 | tool.env.push(("Platform" .into(), "ARM64" .into())); |
| 585 | } |
| 586 | tool |
| 587 | }) |
| 588 | } |
| 589 | |
| 590 | fn tool_from_vs15plus_instance( |
| 591 | tool: &str, |
| 592 | target: TargetArch<'_>, |
| 593 | instance_path: &Path, |
| 594 | env_getter: &dyn EnvGetter, |
| 595 | ) -> Option<Tool> { |
| 596 | let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) = |
| 597 | vs15plus_vc_paths(target, instance_path, env_getter)?; |
| 598 | let tool_path = bin_path.join(tool); |
| 599 | if !tool_path.exists() { |
| 600 | return None; |
| 601 | }; |
| 602 | |
| 603 | let mut tool = MsvcTool::new(tool_path); |
| 604 | tool.path.push(bin_path.clone()); |
| 605 | tool.path.push(host_dylib_path); |
| 606 | if let Some(alt_lib_path) = alt_lib_path { |
| 607 | tool.libs.push(alt_lib_path); |
| 608 | } |
| 609 | tool.libs.push(lib_path); |
| 610 | tool.include.push(include_path); |
| 611 | |
| 612 | if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &root_path) { |
| 613 | tool.libs.push(atl_lib_path); |
| 614 | tool.include.push(atl_include_path); |
| 615 | } |
| 616 | |
| 617 | add_sdks(&mut tool, target, env_getter)?; |
| 618 | |
| 619 | Some(tool.into_tool(env_getter)) |
| 620 | } |
| 621 | |
| 622 | fn vs15plus_vc_paths( |
| 623 | target: TargetArch<'_>, |
| 624 | instance_path: &Path, |
| 625 | env_getter: &dyn EnvGetter, |
| 626 | ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> { |
| 627 | let version = vs15plus_vc_read_version(instance_path)?; |
| 628 | |
| 629 | let hosts = match host_arch() { |
| 630 | X86 => &["X86" ], |
| 631 | X86_64 => &["X64" ], |
| 632 | // Starting with VS 17.4, there is a natively hosted compiler on ARM64: |
| 633 | // https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/ |
| 634 | // On older versions of VS, we use x64 if running under emulation is supported, |
| 635 | // otherwise use x86. |
| 636 | AARCH64 => { |
| 637 | if is_amd64_emulation_supported() { |
| 638 | &["ARM64" , "X64" , "X86" ][..] |
| 639 | } else { |
| 640 | &["ARM64" , "X86" ] |
| 641 | } |
| 642 | } |
| 643 | _ => return None, |
| 644 | }; |
| 645 | let target = lib_subdir(target)?; |
| 646 | // The directory layout here is MSVC/bin/Host$host/$target/ |
| 647 | let path = instance_path.join(r"VC\Tools\MSVC" ).join(version); |
| 648 | // We use the first available host architecture that can build for the target |
| 649 | let (host_path, host) = hosts.iter().find_map(|&x| { |
| 650 | let candidate = path.join("bin" ).join(format!("Host{}" , x)); |
| 651 | if candidate.join(target).exists() { |
| 652 | Some((candidate, x)) |
| 653 | } else { |
| 654 | None |
| 655 | } |
| 656 | })?; |
| 657 | // This is the path to the toolchain for a particular target, running |
| 658 | // on a given host |
| 659 | let bin_path = host_path.join(target); |
| 660 | // But! we also need PATH to contain the target directory for the host |
| 661 | // architecture, because it contains dlls like mspdb140.dll compiled for |
| 662 | // the host architecture. |
| 663 | let host_dylib_path = host_path.join(host.to_lowercase()); |
| 664 | let lib_fragment = if use_spectre_mitigated_libs(env_getter) { |
| 665 | r"lib\spectre" |
| 666 | } else { |
| 667 | "lib" |
| 668 | }; |
| 669 | let lib_path = path.join(lib_fragment).join(target); |
| 670 | let alt_lib_path = (target == "arm64ec" ).then(|| path.join(lib_fragment).join("arm64ec" )); |
| 671 | let include_path = path.join("include" ); |
| 672 | Some(( |
| 673 | path, |
| 674 | bin_path, |
| 675 | host_dylib_path, |
| 676 | lib_path, |
| 677 | alt_lib_path, |
| 678 | include_path, |
| 679 | )) |
| 680 | } |
| 681 | |
| 682 | fn vs15plus_vc_read_version(dir: &Path) -> Option<String> { |
| 683 | // Try to open the default version file. |
| 684 | let mut version_path: PathBuf = |
| 685 | dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt" ); |
| 686 | let mut version_file = if let Ok(f) = File::open(&version_path) { |
| 687 | f |
| 688 | } else { |
| 689 | // If the default doesn't exist, search for other version files. |
| 690 | // These are in the form Microsoft.VCToolsVersion.v143.default.txt |
| 691 | // where `143` is any three decimal digit version number. |
| 692 | // This sorts versions by lexical order and selects the highest version. |
| 693 | let mut version_file = String::new(); |
| 694 | version_path.pop(); |
| 695 | for file in version_path.read_dir().ok()? { |
| 696 | let name = file.ok()?.file_name(); |
| 697 | let name = name.to_str()?; |
| 698 | if name.starts_with("Microsoft.VCToolsVersion.v" ) |
| 699 | && name.ends_with(".default.txt" ) |
| 700 | && name > &version_file |
| 701 | { |
| 702 | version_file.replace_range(.., name); |
| 703 | } |
| 704 | } |
| 705 | if version_file.is_empty() { |
| 706 | return None; |
| 707 | } |
| 708 | version_path.push(version_file); |
| 709 | File::open(version_path).ok()? |
| 710 | }; |
| 711 | |
| 712 | // Get the version string from the file we found. |
| 713 | let mut version = String::new(); |
| 714 | version_file.read_to_string(&mut version).ok()?; |
| 715 | version.truncate(version.trim_end().len()); |
| 716 | Some(version) |
| 717 | } |
| 718 | |
| 719 | fn use_spectre_mitigated_libs(env_getter: &dyn EnvGetter) -> bool { |
| 720 | env_getter |
| 721 | .get_env("VSCMD_ARG_VCVARS_SPECTRE" ) |
| 722 | .map(|env| env.as_ref() == "spectre" ) |
| 723 | .unwrap_or_default() |
| 724 | } |
| 725 | |
| 726 | fn atl_paths(target: TargetArch<'_>, path: &Path) -> Option<(PathBuf, PathBuf)> { |
| 727 | let atl_path = path.join("atlmfc" ); |
| 728 | let sub = lib_subdir(target)?; |
| 729 | if atl_path.exists() { |
| 730 | Some((atl_path.join("lib" ).join(sub), atl_path.join("include" ))) |
| 731 | } else { |
| 732 | None |
| 733 | } |
| 734 | } |
| 735 | |
| 736 | // For MSVC 14 we need to find the Universal CRT as well as either |
| 737 | // the Windows 10 SDK or Windows 8.1 SDK. |
| 738 | pub(super) fn find_msvc_14( |
| 739 | tool: &str, |
| 740 | target: TargetArch<'_>, |
| 741 | env_getter: &dyn EnvGetter, |
| 742 | ) -> Option<Tool> { |
| 743 | let vcdir = get_vc_dir("14.0" )?; |
| 744 | let mut tool = get_tool(tool, &vcdir, target)?; |
| 745 | add_sdks(&mut tool, target, env_getter)?; |
| 746 | Some(tool.into_tool(env_getter)) |
| 747 | } |
| 748 | |
| 749 | fn add_sdks( |
| 750 | tool: &mut MsvcTool, |
| 751 | target: TargetArch<'_>, |
| 752 | env_getter: &dyn EnvGetter, |
| 753 | ) -> Option<()> { |
| 754 | let sub = lib_subdir(target)?; |
| 755 | let (ucrt, ucrt_version) = get_ucrt_dir()?; |
| 756 | |
| 757 | let host = match host_arch() { |
| 758 | X86 => "x86" , |
| 759 | X86_64 => "x64" , |
| 760 | AARCH64 => "arm64" , |
| 761 | _ => return None, |
| 762 | }; |
| 763 | |
| 764 | tool.path |
| 765 | .push(ucrt.join("bin" ).join(&ucrt_version).join(host)); |
| 766 | |
| 767 | let ucrt_include = ucrt.join("include" ).join(&ucrt_version); |
| 768 | tool.include.push(ucrt_include.join("ucrt" )); |
| 769 | |
| 770 | let ucrt_lib = ucrt.join("lib" ).join(&ucrt_version); |
| 771 | tool.libs.push(ucrt_lib.join("ucrt" ).join(sub)); |
| 772 | |
| 773 | if let Some((sdk, version)) = get_sdk10_dir(env_getter) { |
| 774 | tool.path.push(sdk.join("bin" ).join(host)); |
| 775 | let sdk_lib = sdk.join("lib" ).join(&version); |
| 776 | tool.libs.push(sdk_lib.join("um" ).join(sub)); |
| 777 | let sdk_include = sdk.join("include" ).join(&version); |
| 778 | tool.include.push(sdk_include.join("um" )); |
| 779 | tool.include.push(sdk_include.join("cppwinrt" )); |
| 780 | tool.include.push(sdk_include.join("winrt" )); |
| 781 | tool.include.push(sdk_include.join("shared" )); |
| 782 | } else if let Some(sdk) = get_sdk81_dir() { |
| 783 | tool.path.push(sdk.join("bin" ).join(host)); |
| 784 | let sdk_lib = sdk.join("lib" ).join("winv6.3" ); |
| 785 | tool.libs.push(sdk_lib.join("um" ).join(sub)); |
| 786 | let sdk_include = sdk.join("include" ); |
| 787 | tool.include.push(sdk_include.join("um" )); |
| 788 | tool.include.push(sdk_include.join("winrt" )); |
| 789 | tool.include.push(sdk_include.join("shared" )); |
| 790 | } |
| 791 | |
| 792 | Some(()) |
| 793 | } |
| 794 | |
| 795 | fn add_env( |
| 796 | tool: &mut Tool, |
| 797 | env: &'static str, |
| 798 | paths: Vec<PathBuf>, |
| 799 | env_getter: &dyn EnvGetter, |
| 800 | ) { |
| 801 | let prev = env_getter.get_env(env); |
| 802 | let prev = prev.as_ref().map(AsRef::as_ref).unwrap_or_default(); |
| 803 | let prev = env::split_paths(&prev); |
| 804 | let new = paths.into_iter().chain(prev); |
| 805 | tool.env |
| 806 | .push((env.to_string().into(), env::join_paths(new).unwrap())); |
| 807 | } |
| 808 | |
| 809 | // Given a possible MSVC installation directory, we look for the linker and |
| 810 | // then add the MSVC library path. |
| 811 | fn get_tool(tool: &str, path: &Path, target: TargetArch<'_>) -> Option<MsvcTool> { |
| 812 | bin_subdir(target) |
| 813 | .into_iter() |
| 814 | .map(|(sub, host)| { |
| 815 | ( |
| 816 | path.join("bin" ).join(sub).join(tool), |
| 817 | path.join("bin" ).join(host), |
| 818 | ) |
| 819 | }) |
| 820 | .filter(|(path, _)| path.is_file()) |
| 821 | .map(|(path, host)| { |
| 822 | let mut tool = MsvcTool::new(path); |
| 823 | tool.path.push(host); |
| 824 | tool |
| 825 | }) |
| 826 | .filter_map(|mut tool| { |
| 827 | let sub = vc_lib_subdir(target)?; |
| 828 | tool.libs.push(path.join("lib" ).join(sub)); |
| 829 | tool.include.push(path.join("include" )); |
| 830 | let atlmfc_path = path.join("atlmfc" ); |
| 831 | if atlmfc_path.exists() { |
| 832 | tool.libs.push(atlmfc_path.join("lib" ).join(sub)); |
| 833 | tool.include.push(atlmfc_path.join("include" )); |
| 834 | } |
| 835 | Some(tool) |
| 836 | }) |
| 837 | .next() |
| 838 | } |
| 839 | |
| 840 | // To find MSVC we look in a specific registry key for the version we are |
| 841 | // trying to find. |
| 842 | fn get_vc_dir(ver: &str) -> Option<PathBuf> { |
| 843 | let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7" ; |
| 844 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
| 845 | let path = key.query_str(ver).ok()?; |
| 846 | Some(path.into()) |
| 847 | } |
| 848 | |
| 849 | // To find the Universal CRT we look in a specific registry key for where |
| 850 | // all the Universal CRTs are located and then sort them asciibetically to |
| 851 | // find the newest version. While this sort of sorting isn't ideal, it is |
| 852 | // what vcvars does so that's good enough for us. |
| 853 | // |
| 854 | // Returns a pair of (root, version) for the ucrt dir if found |
| 855 | fn get_ucrt_dir() -> Option<(PathBuf, String)> { |
| 856 | let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots" ; |
| 857 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
| 858 | let root = key.query_str("KitsRoot10" ).ok()?; |
| 859 | let readdir = Path::new(&root).join("lib" ).read_dir().ok()?; |
| 860 | let max_libdir = readdir |
| 861 | .filter_map(|dir| dir.ok()) |
| 862 | .map(|dir| dir.path()) |
| 863 | .filter(|dir| { |
| 864 | dir.components() |
| 865 | .last() |
| 866 | .and_then(|c| c.as_os_str().to_str()) |
| 867 | .map(|c| c.starts_with("10." ) && dir.join("ucrt" ).is_dir()) |
| 868 | .unwrap_or(false) |
| 869 | }) |
| 870 | .max()?; |
| 871 | let version = max_libdir.components().last().unwrap(); |
| 872 | let version = version.as_os_str().to_str().unwrap().to_string(); |
| 873 | Some((root.into(), version)) |
| 874 | } |
| 875 | |
| 876 | // Vcvars finds the correct version of the Windows 10 SDK by looking |
| 877 | // for the include `um\Windows.h` because sometimes a given version will |
| 878 | // only have UCRT bits without the rest of the SDK. Since we only care about |
| 879 | // libraries and not includes, we instead look for `um\x64\kernel32.lib`. |
| 880 | // Since the 32-bit and 64-bit libraries are always installed together we |
| 881 | // only need to bother checking x64, making this code a tiny bit simpler. |
| 882 | // Like we do for the Universal CRT, we sort the possibilities |
| 883 | // asciibetically to find the newest one as that is what vcvars does. |
| 884 | // Before doing that, we check the "WindowsSdkDir" and "WindowsSDKVersion" |
| 885 | // environment variables set by vcvars to use the environment sdk version |
| 886 | // if one is already configured. |
| 887 | fn get_sdk10_dir(env_getter: &dyn EnvGetter) -> Option<(PathBuf, String)> { |
| 888 | if let (Some(root), Some(version)) = ( |
| 889 | env_getter.get_env("WindowsSdkDir" ), |
| 890 | env_getter |
| 891 | .get_env("WindowsSDKVersion" ) |
| 892 | .as_ref() |
| 893 | .and_then(|version| version.as_ref().to_str()), |
| 894 | ) { |
| 895 | return Some(( |
| 896 | PathBuf::from(root), |
| 897 | version.trim_end_matches(' \\' ).to_string(), |
| 898 | )); |
| 899 | } |
| 900 | |
| 901 | let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0" ; |
| 902 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
| 903 | let root = key.query_str("InstallationFolder" ).ok()?; |
| 904 | let readdir = Path::new(&root).join("lib" ).read_dir().ok()?; |
| 905 | let mut dirs = readdir |
| 906 | .filter_map(|dir| dir.ok()) |
| 907 | .map(|dir| dir.path()) |
| 908 | .collect::<Vec<_>>(); |
| 909 | dirs.sort(); |
| 910 | let dir = dirs |
| 911 | .into_iter() |
| 912 | .rev() |
| 913 | .find(|dir| dir.join("um" ).join("x64" ).join("kernel32.lib" ).is_file())?; |
| 914 | let version = dir.components().last().unwrap(); |
| 915 | let version = version.as_os_str().to_str().unwrap().to_string(); |
| 916 | Some((root.into(), version)) |
| 917 | } |
| 918 | |
| 919 | // Interestingly there are several subdirectories, `win7` `win8` and |
| 920 | // `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same |
| 921 | // applies to us. Note that if we were targeting kernel mode drivers |
| 922 | // instead of user mode applications, we would care. |
| 923 | fn get_sdk81_dir() -> Option<PathBuf> { |
| 924 | let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1" ; |
| 925 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
| 926 | let root = key.query_str("InstallationFolder" ).ok()?; |
| 927 | Some(root.into()) |
| 928 | } |
| 929 | |
| 930 | const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0; |
| 931 | const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9; |
| 932 | const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12; |
| 933 | const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL; |
| 934 | const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64; |
| 935 | const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64; |
| 936 | |
| 937 | // When choosing the tool to use, we have to choose the one which matches |
| 938 | // the target architecture. Otherwise we end up in situations where someone |
| 939 | // on 32-bit Windows is trying to cross compile to 64-bit and it tries to |
| 940 | // invoke the native 64-bit compiler which won't work. |
| 941 | // |
| 942 | // For the return value of this function, the first member of the tuple is |
| 943 | // the folder of the tool we will be invoking, while the second member is |
| 944 | // the folder of the host toolchain for that tool which is essential when |
| 945 | // using a cross linker. We return a Vec since on x64 there are often two |
| 946 | // linkers that can target the architecture we desire. The 64-bit host |
| 947 | // linker is preferred, and hence first, due to 64-bit allowing it more |
| 948 | // address space to work with and potentially being faster. |
| 949 | fn bin_subdir(target: TargetArch<'_>) -> Vec<(&'static str, &'static str)> { |
| 950 | match (target.into(), host_arch()) { |
| 951 | ("i586" , X86) | ("i686" , X86) => vec![("" , "" )], |
| 952 | ("i586" , X86_64) | ("i686" , X86_64) => vec![("amd64_x86" , "amd64" ), ("" , "" )], |
| 953 | ("x86_64" , X86) => vec![("x86_amd64" , "" )], |
| 954 | ("x86_64" , X86_64) => vec![("amd64" , "amd64" ), ("x86_amd64" , "" )], |
| 955 | ("arm" , X86) | ("thumbv7a" , X86) => vec![("x86_arm" , "" )], |
| 956 | ("arm" , X86_64) | ("thumbv7a" , X86_64) => vec![("amd64_arm" , "amd64" ), ("x86_arm" , "" )], |
| 957 | _ => vec![], |
| 958 | } |
| 959 | } |
| 960 | |
| 961 | fn lib_subdir(target: TargetArch<'_>) -> Option<&'static str> { |
| 962 | match target.into() { |
| 963 | "i586" | "i686" => Some("x86" ), |
| 964 | "x86_64" => Some("x64" ), |
| 965 | "arm" | "thumbv7a" => Some("arm" ), |
| 966 | "aarch64" | "arm64ec" => Some("arm64" ), |
| 967 | _ => None, |
| 968 | } |
| 969 | } |
| 970 | |
| 971 | // MSVC's x86 libraries are not in a subfolder |
| 972 | fn vc_lib_subdir(target: TargetArch<'_>) -> Option<&'static str> { |
| 973 | match target.into() { |
| 974 | "i586" | "i686" => Some("" ), |
| 975 | "x86_64" => Some("amd64" ), |
| 976 | "arm" | "thumbv7a" => Some("arm" ), |
| 977 | "aarch64" => Some("arm64" ), |
| 978 | _ => None, |
| 979 | } |
| 980 | } |
| 981 | |
| 982 | #[allow (bad_style)] |
| 983 | fn host_arch() -> u16 { |
| 984 | type DWORD = u32; |
| 985 | type WORD = u16; |
| 986 | type LPVOID = *mut u8; |
| 987 | type DWORD_PTR = usize; |
| 988 | |
| 989 | #[repr (C)] |
| 990 | struct SYSTEM_INFO { |
| 991 | wProcessorArchitecture: WORD, |
| 992 | _wReserved: WORD, |
| 993 | _dwPageSize: DWORD, |
| 994 | _lpMinimumApplicationAddress: LPVOID, |
| 995 | _lpMaximumApplicationAddress: LPVOID, |
| 996 | _dwActiveProcessorMask: DWORD_PTR, |
| 997 | _dwNumberOfProcessors: DWORD, |
| 998 | _dwProcessorType: DWORD, |
| 999 | _dwAllocationGranularity: DWORD, |
| 1000 | _wProcessorLevel: WORD, |
| 1001 | _wProcessorRevision: WORD, |
| 1002 | } |
| 1003 | |
| 1004 | extern "system" { |
| 1005 | fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO); |
| 1006 | } |
| 1007 | |
| 1008 | unsafe { |
| 1009 | let mut info = mem::zeroed(); |
| 1010 | GetNativeSystemInfo(&mut info); |
| 1011 | info.wProcessorArchitecture |
| 1012 | } |
| 1013 | } |
| 1014 | |
| 1015 | // Given a registry key, look at all the sub keys and find the one which has |
| 1016 | // the maximal numeric value. |
| 1017 | // |
| 1018 | // Returns the name of the maximal key as well as the opened maximal key. |
| 1019 | fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> { |
| 1020 | let mut max_vers = 0; |
| 1021 | let mut max_key = None; |
| 1022 | for subkey in key.iter().filter_map(|k| k.ok()) { |
| 1023 | let val = subkey |
| 1024 | .to_str() |
| 1025 | .and_then(|s| s.trim_start_matches('v' ).replace('.' , "" ).parse().ok()); |
| 1026 | let val = match val { |
| 1027 | Some(s) => s, |
| 1028 | None => continue, |
| 1029 | }; |
| 1030 | if val > max_vers { |
| 1031 | if let Ok(k) = key.open(&subkey) { |
| 1032 | max_vers = val; |
| 1033 | max_key = Some((subkey, k)); |
| 1034 | } |
| 1035 | } |
| 1036 | } |
| 1037 | max_key |
| 1038 | } |
| 1039 | |
| 1040 | #[inline (always)] |
| 1041 | pub(super) fn has_msbuild_version(version: &str, env_getter: &dyn EnvGetter) -> bool { |
| 1042 | match version { |
| 1043 | "17.0" => { |
| 1044 | find_msbuild_vs17(TargetArch("x86_64" ), env_getter).is_some() |
| 1045 | || find_msbuild_vs17(TargetArch("i686" ), env_getter).is_some() |
| 1046 | || find_msbuild_vs17(TargetArch("aarch64" ), env_getter).is_some() |
| 1047 | } |
| 1048 | "16.0" => { |
| 1049 | find_msbuild_vs16(TargetArch("x86_64" ), env_getter).is_some() |
| 1050 | || find_msbuild_vs16(TargetArch("i686" ), env_getter).is_some() |
| 1051 | || find_msbuild_vs16(TargetArch("aarch64" ), env_getter).is_some() |
| 1052 | } |
| 1053 | "15.0" => { |
| 1054 | find_msbuild_vs15(TargetArch("x86_64" ), env_getter).is_some() |
| 1055 | || find_msbuild_vs15(TargetArch("i686" ), env_getter).is_some() |
| 1056 | || find_msbuild_vs15(TargetArch("aarch64" ), env_getter).is_some() |
| 1057 | } |
| 1058 | "14.0" => LOCAL_MACHINE |
| 1059 | .open(&OsString::from(format!( |
| 1060 | "SOFTWARE \\Microsoft \\MSBuild \\ToolsVersions \\{}" , |
| 1061 | version |
| 1062 | ))) |
| 1063 | .is_ok(), |
| 1064 | _ => false, |
| 1065 | } |
| 1066 | } |
| 1067 | |
| 1068 | pub(super) fn find_devenv(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> { |
| 1069 | find_devenv_vs15(target, env_getter) |
| 1070 | } |
| 1071 | |
| 1072 | fn find_devenv_vs15(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> { |
| 1073 | find_tool_in_vs15_path(r"Common7\IDE\devenv.exe" , target, env_getter) |
| 1074 | } |
| 1075 | |
| 1076 | // see http://stackoverflow.com/questions/328017/path-to-msbuild |
| 1077 | pub(super) fn find_msbuild(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> { |
| 1078 | // VS 15 (2017) changed how to locate msbuild |
| 1079 | if let Some(r) = find_msbuild_vs17(target, env_getter) { |
| 1080 | Some(r) |
| 1081 | } else if let Some(r) = find_msbuild_vs16(target, env_getter) { |
| 1082 | return Some(r); |
| 1083 | } else if let Some(r) = find_msbuild_vs15(target, env_getter) { |
| 1084 | return Some(r); |
| 1085 | } else { |
| 1086 | find_old_msbuild(target) |
| 1087 | } |
| 1088 | } |
| 1089 | |
| 1090 | fn find_msbuild_vs15(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> { |
| 1091 | find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe" , target, env_getter) |
| 1092 | } |
| 1093 | |
| 1094 | fn find_old_msbuild(target: TargetArch<'_>) -> Option<Tool> { |
| 1095 | let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions" ; |
| 1096 | LOCAL_MACHINE |
| 1097 | .open(key.as_ref()) |
| 1098 | .ok() |
| 1099 | .and_then(|key| { |
| 1100 | max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath" ).ok()) |
| 1101 | }) |
| 1102 | .map(|path| { |
| 1103 | let mut path = PathBuf::from(path); |
| 1104 | path.push("MSBuild.exe" ); |
| 1105 | let mut tool = Tool::with_family(path, MSVC_FAMILY); |
| 1106 | if target == "x86_64" { |
| 1107 | tool.env.push(("Platform" .into(), "X64" .into())); |
| 1108 | } |
| 1109 | tool |
| 1110 | }) |
| 1111 | } |
| 1112 | } |
| 1113 | |
| 1114 | /// Non-Windows Implementation. |
| 1115 | #[cfg (not(windows))] |
| 1116 | mod impl_ { |
| 1117 | use std::{env, ffi::OsStr}; |
| 1118 | |
| 1119 | use super::{EnvGetter, TargetArch, MSVC_FAMILY}; |
| 1120 | use crate::Tool; |
| 1121 | |
| 1122 | /// Finding msbuild.exe tool under unix system is not currently supported. |
| 1123 | /// Maybe can check it using an environment variable looks like `MSBUILD_BIN`. |
| 1124 | #[inline (always)] |
| 1125 | pub(super) fn find_msbuild(_target: TargetArch<'_>, _: &dyn EnvGetter) -> Option<Tool> { |
| 1126 | None |
| 1127 | } |
| 1128 | |
| 1129 | // Finding devenv.exe tool under unix system is not currently supported. |
| 1130 | // Maybe can check it using an environment variable looks like `DEVENV_BIN`. |
| 1131 | #[inline (always)] |
| 1132 | pub(super) fn find_devenv(_target: TargetArch<'_>, _: &dyn EnvGetter) -> Option<Tool> { |
| 1133 | None |
| 1134 | } |
| 1135 | |
| 1136 | /// Attempt to find the tool using environment variables set by vcvars. |
| 1137 | pub(super) fn find_msvc_environment( |
| 1138 | tool: &str, |
| 1139 | _target: TargetArch<'_>, |
| 1140 | env_getter: &dyn EnvGetter, |
| 1141 | ) -> Option<Tool> { |
| 1142 | // Early return if the environment doesn't contain a VC install. |
| 1143 | let vc_install_dir = env_getter.get_env("VCINSTALLDIR" )?; |
| 1144 | let vs_install_dir = env_getter.get_env("VSINSTALLDIR" )?; |
| 1145 | |
| 1146 | let get_tool = |install_dir: &OsStr| { |
| 1147 | env::split_paths(install_dir) |
| 1148 | .map(|p| p.join(tool)) |
| 1149 | .find(|p| p.exists()) |
| 1150 | .map(|path| Tool::with_family(path, MSVC_FAMILY)) |
| 1151 | }; |
| 1152 | |
| 1153 | // Take the path of tool for the vc install directory. |
| 1154 | get_tool(vc_install_dir.as_ref()) |
| 1155 | // Take the path of tool for the vs install directory. |
| 1156 | .or_else(|| get_tool(vs_install_dir.as_ref())) |
| 1157 | // Take the path of tool for the current path environment. |
| 1158 | .or_else(|| { |
| 1159 | env_getter |
| 1160 | .get_env("PATH" ) |
| 1161 | .as_ref() |
| 1162 | .map(|path| path.as_ref()) |
| 1163 | .and_then(get_tool) |
| 1164 | }) |
| 1165 | } |
| 1166 | |
| 1167 | #[inline (always)] |
| 1168 | pub(super) fn find_msvc_15plus( |
| 1169 | _tool: &str, |
| 1170 | _target: TargetArch<'_>, |
| 1171 | _: &dyn EnvGetter, |
| 1172 | ) -> Option<Tool> { |
| 1173 | None |
| 1174 | } |
| 1175 | |
| 1176 | // For MSVC 14 we need to find the Universal CRT as well as either |
| 1177 | // the Windows 10 SDK or Windows 8.1 SDK. |
| 1178 | #[inline (always)] |
| 1179 | pub(super) fn find_msvc_14( |
| 1180 | _tool: &str, |
| 1181 | _target: TargetArch<'_>, |
| 1182 | _: &dyn EnvGetter, |
| 1183 | ) -> Option<Tool> { |
| 1184 | None |
| 1185 | } |
| 1186 | |
| 1187 | #[inline (always)] |
| 1188 | pub(super) fn has_msbuild_version(_version: &str, _: &dyn EnvGetter) -> bool { |
| 1189 | false |
| 1190 | } |
| 1191 | } |
| 1192 | |