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
17use std::{
18 env,
19 ffi::{OsStr, OsString},
20 ops::Deref,
21 path::PathBuf,
22 process::Command,
23 sync::Arc,
24};
25
26use crate::Tool;
27use crate::ToolFamily;
28
29const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false };
30
31#[derive(Copy, Clone)]
32struct TargetArch<'a>(pub &'a str);
33
34impl PartialEq<&str> for TargetArch<'_> {
35 fn eq(&self, other: &&str) -> bool {
36 self.0 == *other
37 }
38}
39
40impl<'a> From<TargetArch<'a>> for &'a str {
41 fn from(target: TargetArch<'a>) -> Self {
42 target.0
43 }
44}
45
46pub(crate) enum Env {
47 Owned(OsString),
48 Arced(Arc<OsStr>),
49}
50
51impl AsRef<OsStr> for Env {
52 fn as_ref(&self) -> &OsStr {
53 self.deref()
54 }
55}
56
57impl 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
68impl 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
77pub(crate) trait EnvGetter {
78 fn get_env(&self, name: &'static str) -> Option<Env>;
79}
80
81struct StdEnvGetter;
82
83impl 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.
102pub 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.
109pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
110 find_tool_inner(target, tool, &StdEnvGetter)
111}
112
113pub(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]
154pub 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)]
175pub 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)]
222mod 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))]
1116mod 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