cc/windows/
find_tools.rs

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