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