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 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://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 probe the Windows Registry when looking for
12 //! windows-specific tools.
13 
14 use std::process::Command;
15 
16 use crate::Tool;
17 #[cfg(windows)]
18 use crate::ToolFamily;
19 
20 #[cfg(windows)]
21 const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false };
22 
23 /// Attempts to find a tool within an MSVC installation using the Windows
24 /// registry as a point to search from.
25 ///
26 /// The `target` argument is the target that the tool should work for (e.g.
27 /// compile or link for) and the `tool` argument is the tool to find (e.g.
28 /// `cl.exe` or `link.exe`).
29 ///
30 /// This function will return `None` if the tool could not be found, or it will
31 /// return `Some(cmd)` which represents a command that's ready to execute the
32 /// tool with the appropriate environment variables set.
33 ///
34 /// Note that this function always returns `None` for non-MSVC targets.
find(target: &str, tool: &str) -> Option<Command>35 pub fn find(target: &str, tool: &str) -> Option<Command> {
36     find_tool(target, tool).map(|c| c.to_command())
37 }
38 
39 /// Similar to the `find` function above, this function will attempt the same
40 /// operation (finding a MSVC tool in a local install) but instead returns a
41 /// `Tool` which may be introspected.
42 #[cfg(not(windows))]
find_tool(_target: &str, _tool: &str) -> Option<Tool>43 pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> {
44     None
45 }
46 
47 /// Documented above.
48 #[cfg(windows)]
find_tool(target: &str, tool: &str) -> Option<Tool>49 pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
50     // This logic is all tailored for MSVC, if we're not that then bail out
51     // early.
52     if !target.contains("msvc") {
53         return None;
54     }
55 
56     // Looks like msbuild isn't located in the same location as other tools like
57     // cl.exe and lib.exe. To handle this we probe for it manually with
58     // dedicated registry keys.
59     if tool.contains("msbuild") {
60         return impl_::find_msbuild(target);
61     }
62 
63     if tool.contains("devenv") {
64         return impl_::find_devenv(target);
65     }
66 
67     // Ok, if we're here, now comes the fun part of the probing. Default shells
68     // or shells like MSYS aren't really configured to execute `cl.exe` and the
69     // various compiler tools shipped as part of Visual Studio. Here we try to
70     // first find the relevant tool, then we also have to be sure to fill in
71     // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that
72     // the tool is actually usable.
73 
74     return impl_::find_msvc_environment(tool, target)
75         .or_else(|| impl_::find_msvc_15plus(tool, target))
76         .or_else(|| impl_::find_msvc_14(tool, target))
77         .or_else(|| impl_::find_msvc_12(tool, target))
78         .or_else(|| impl_::find_msvc_11(tool, target));
79 }
80 
81 /// A version of Visual Studio
82 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
83 pub enum VsVers {
84     /// Visual Studio 12 (2013)
85     Vs12,
86     /// Visual Studio 14 (2015)
87     Vs14,
88     /// Visual Studio 15 (2017)
89     Vs15,
90     /// Visual Studio 16 (2019)
91     Vs16,
92     /// Visual Studio 17 (2022)
93     Vs17,
94 
95     /// Hidden variant that should not be matched on. Callers that want to
96     /// handle an enumeration of `VsVers` instances should always have a default
97     /// case meaning that it's a VS version they don't understand.
98     #[doc(hidden)]
99     #[allow(bad_style)]
100     __Nonexhaustive_do_not_match_this_or_your_code_will_break,
101 }
102 
103 /// Find the most recent installed version of Visual Studio
104 ///
105 /// This is used by the cmake crate to figure out the correct
106 /// generator.
107 #[cfg(not(windows))]
find_vs_version() -> Result<VsVers, String>108 pub fn find_vs_version() -> Result<VsVers, String> {
109     Err(format!("not windows"))
110 }
111 
112 /// Documented above
113 #[cfg(windows)]
find_vs_version() -> Result<VsVers, String>114 pub fn find_vs_version() -> Result<VsVers, String> {
115     use std::env;
116 
117     match env::var("VisualStudioVersion") {
118         Ok(version) => match &version[..] {
119             "17.0" => Ok(VsVers::Vs17),
120             "16.0" => Ok(VsVers::Vs16),
121             "15.0" => Ok(VsVers::Vs15),
122             "14.0" => Ok(VsVers::Vs14),
123             "12.0" => Ok(VsVers::Vs12),
124             vers => Err(format!(
125                 "\n\n\
126                  unsupported or unknown VisualStudio version: {}\n\
127                  if another version is installed consider running \
128                  the appropriate vcvars script before building this \
129                  crate\n\
130                  ",
131                 vers
132             )),
133         },
134         _ => {
135             // Check for the presence of a specific registry key
136             // that indicates visual studio is installed.
137             if impl_::has_msbuild_version("16.0") {
138                 Ok(VsVers::Vs16)
139             } else if impl_::has_msbuild_version("15.0") {
140                 Ok(VsVers::Vs15)
141             } else if impl_::has_msbuild_version("14.0") {
142                 Ok(VsVers::Vs14)
143             } else if impl_::has_msbuild_version("12.0") {
144                 Ok(VsVers::Vs12)
145             } else {
146                 Err(format!(
147                     "\n\n\
148                      couldn't determine visual studio generator\n\
149                      if VisualStudio is installed, however, consider \
150                      running the appropriate vcvars script before building \
151                      this crate\n\
152                      "
153                 ))
154             }
155         }
156     }
157 }
158 
159 #[cfg(windows)]
160 mod impl_ {
161     use crate::com;
162     use crate::registry::{RegistryKey, LOCAL_MACHINE};
163     use crate::setup_config::SetupConfiguration;
164     use crate::vs_instances::{VsInstances, VswhereInstance};
165     use std::convert::TryFrom;
166     use std::env;
167     use std::ffi::OsString;
168     use std::fs::File;
169     use std::io::Read;
170     use std::iter;
171     use std::mem;
172     use std::path::{Path, PathBuf};
173     use std::process::Command;
174     use std::str::FromStr;
175 
176     use super::MSVC_FAMILY;
177     use crate::Tool;
178 
179     struct MsvcTool {
180         tool: PathBuf,
181         libs: Vec<PathBuf>,
182         path: Vec<PathBuf>,
183         include: Vec<PathBuf>,
184     }
185 
186     impl MsvcTool {
new(tool: PathBuf) -> MsvcTool187         fn new(tool: PathBuf) -> MsvcTool {
188             MsvcTool {
189                 tool: tool,
190                 libs: Vec::new(),
191                 path: Vec::new(),
192                 include: Vec::new(),
193             }
194         }
195 
into_tool(self) -> Tool196         fn into_tool(self) -> Tool {
197             let MsvcTool {
198                 tool,
199                 libs,
200                 path,
201                 include,
202             } = self;
203             let mut tool = Tool::with_family(tool.into(), MSVC_FAMILY);
204             add_env(&mut tool, "LIB", libs);
205             add_env(&mut tool, "PATH", path);
206             add_env(&mut tool, "INCLUDE", include);
207             tool
208         }
209     }
210 
211     /// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the
212     /// given target's arch. Returns `None` if the variable does not exist.
213     #[cfg(windows)]
is_vscmd_target(target: &str) -> Option<bool>214     fn is_vscmd_target(target: &str) -> Option<bool> {
215         let vscmd_arch = env::var("VSCMD_ARG_TGT_ARCH").ok()?;
216         // Convert the Rust target arch to its VS arch equivalent.
217         let arch = match target.split("-").next() {
218             Some("x86_64") => "x64",
219             Some("aarch64") => "arm64",
220             Some("i686") | Some("i586") => "x86",
221             Some("thumbv7a") => "arm",
222             // An unrecognized arch.
223             _ => return Some(false),
224         };
225         Some(vscmd_arch == arch)
226     }
227 
228     /// Attempt to find the tool using environment variables set by vcvars.
find_msvc_environment(tool: &str, target: &str) -> Option<Tool>229     pub fn find_msvc_environment(tool: &str, target: &str) -> Option<Tool> {
230         // Early return if the environment doesn't contain a VC install.
231         if env::var_os("VCINSTALLDIR").is_none() {
232             return None;
233         }
234         let vs_install_dir = env::var_os("VSINSTALLDIR")?.into();
235 
236         // If the vscmd target differs from the requested target then
237         // attempt to get the tool using the VS install directory.
238         if is_vscmd_target(target) == Some(false) {
239             // We will only get here with versions 15+.
240             tool_from_vs15plus_instance(tool, target, &vs_install_dir)
241         } else {
242             // Fallback to simply using the current environment.
243             env::var_os("PATH")
244                 .and_then(|path| {
245                     env::split_paths(&path)
246                         .map(|p| p.join(tool))
247                         .find(|p| p.exists())
248                 })
249                 .map(|path| Tool::with_family(path.into(), MSVC_FAMILY))
250         }
251     }
252 
253     #[allow(bare_trait_objects)]
vs16_instances(target: &str) -> Box<Iterator<Item = PathBuf>>254     fn vs16_instances(target: &str) -> Box<Iterator<Item = PathBuf>> {
255         let instances = if let Some(instances) = vs15plus_instances(target) {
256             instances
257         } else {
258             return Box::new(iter::empty());
259         };
260         Box::new(instances.into_iter().filter_map(|instance| {
261             let installation_name = instance.installation_name()?;
262             if installation_name.starts_with("VisualStudio/16.") {
263                 Some(instance.installation_path()?)
264             } else if installation_name.starts_with("VisualStudioPreview/16.") {
265                 Some(instance.installation_path()?)
266             } else {
267                 None
268             }
269         }))
270     }
271 
find_tool_in_vs16_path(tool: &str, target: &str) -> Option<Tool>272     fn find_tool_in_vs16_path(tool: &str, target: &str) -> Option<Tool> {
273         vs16_instances(target)
274             .filter_map(|path| {
275                 let path = path.join(tool);
276                 if !path.is_file() {
277                     return None;
278                 }
279                 let mut tool = Tool::with_family(path, MSVC_FAMILY);
280                 if target.contains("x86_64") {
281                     tool.env.push(("Platform".into(), "X64".into()));
282                 }
283                 if target.contains("aarch64") {
284                     tool.env.push(("Platform".into(), "ARM64".into()));
285                 }
286                 Some(tool)
287             })
288             .next()
289     }
290 
find_msbuild_vs16(target: &str) -> Option<Tool>291     fn find_msbuild_vs16(target: &str) -> Option<Tool> {
292         find_tool_in_vs16_path(r"MSBuild\Current\Bin\MSBuild.exe", target)
293     }
294 
295     // In MSVC 15 (2017) MS once again changed the scheme for locating
296     // the tooling.  Now we must go through some COM interfaces, which
297     // is super fun for Rust.
298     //
299     // Note that much of this logic can be found [online] wrt paths, COM, etc.
300     //
301     // [online]: https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/
302     //
303     // Returns MSVC 15+ instances (15, 16 right now), the order should be consider undefined.
304     //
305     // However, on ARM64 this method doesn't work because VS Installer fails to register COM component on ARM64.
306     // Hence, as the last resort we try to use vswhere.exe to list available instances.
vs15plus_instances(target: &str) -> Option<VsInstances>307     fn vs15plus_instances(target: &str) -> Option<VsInstances> {
308         vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target))
309     }
310 
vs15plus_instances_using_com() -> Option<VsInstances>311     fn vs15plus_instances_using_com() -> Option<VsInstances> {
312         com::initialize().ok()?;
313 
314         let config = SetupConfiguration::new().ok()?;
315         let enum_setup_instances = config.enum_all_instances().ok()?;
316 
317         Some(VsInstances::ComBased(enum_setup_instances))
318     }
319 
vs15plus_instances_using_vswhere(target: &str) -> Option<VsInstances>320     fn vs15plus_instances_using_vswhere(target: &str) -> Option<VsInstances> {
321         let program_files_path: PathBuf = env::var("ProgramFiles(x86)")
322             .or_else(|_| env::var("ProgramFiles"))
323             .ok()?
324             .into();
325 
326         let vswhere_path =
327             program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe");
328 
329         if !vswhere_path.exists() {
330             return None;
331         }
332 
333         let arch = target.split('-').next().unwrap();
334         let tools_arch = match arch {
335             "i586" | "i686" | "x86_64" => Some("x86.x64"),
336             "arm" | "thumbv7a" => Some("ARM"),
337             "aarch64" => Some("ARM64"),
338             _ => None,
339         };
340 
341         let vswhere_output = Command::new(vswhere_path)
342             .args(&[
343                 "-latest",
344                 "-products",
345                 "*",
346                 "-requires",
347                 &format!("Microsoft.VisualStudio.Component.VC.Tools.{}", tools_arch?),
348                 "-format",
349                 "text",
350                 "-nologo",
351             ])
352             .stderr(std::process::Stdio::inherit())
353             .output()
354             .ok()?;
355 
356         let vs_instances =
357             VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?);
358 
359         Some(vs_instances)
360     }
361 
362     // Inspired from official microsoft/vswhere ParseVersionString
363     // i.e. at most four u16 numbers separated by '.'
parse_version(version: &str) -> Option<Vec<u16>>364     fn parse_version(version: &str) -> Option<Vec<u16>> {
365         version
366             .split('.')
367             .map(|chunk| u16::from_str(chunk).ok())
368             .collect()
369     }
370 
find_msvc_15plus(tool: &str, target: &str) -> Option<Tool>371     pub fn find_msvc_15plus(tool: &str, target: &str) -> Option<Tool> {
372         let iter = vs15plus_instances(target)?;
373         iter.into_iter()
374             .filter_map(|instance| {
375                 let version = parse_version(&instance.installation_version()?)?;
376                 let instance_path = instance.installation_path()?;
377                 let tool = tool_from_vs15plus_instance(tool, target, &instance_path)?;
378                 Some((version, tool))
379             })
380             .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version))
381             .map(|(_version, tool)| tool)
382     }
383 
384     // While the paths to Visual Studio 2017's devenv and MSBuild could
385     // potentially be retrieved from the registry, finding them via
386     // SetupConfiguration has shown to be [more reliable], and is preferred
387     // according to Microsoft. To help head off potential regressions though,
388     // we keep the registry method as a fallback option.
389     //
390     // [more reliable]: https://github.com/alexcrichton/cc-rs/pull/331
find_tool_in_vs15_path(tool: &str, target: &str) -> Option<Tool>391     fn find_tool_in_vs15_path(tool: &str, target: &str) -> Option<Tool> {
392         let mut path = match vs15plus_instances(target) {
393             Some(instances) => instances
394                 .into_iter()
395                 .filter_map(|instance| instance.installation_path())
396                 .map(|path| path.join(tool))
397                 .find(|ref path| path.is_file()),
398             None => None,
399         };
400 
401         if path.is_none() {
402             let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7";
403             path = LOCAL_MACHINE
404                 .open(key.as_ref())
405                 .ok()
406                 .and_then(|key| key.query_str("15.0").ok())
407                 .map(|path| PathBuf::from(path).join(tool))
408                 .and_then(|path| if path.is_file() { Some(path) } else { None });
409         }
410 
411         path.map(|path| {
412             let mut tool = Tool::with_family(path, MSVC_FAMILY);
413             if target.contains("x86_64") {
414                 tool.env.push(("Platform".into(), "X64".into()));
415             }
416             if target.contains("aarch64") {
417                 tool.env.push(("Platform".into(), "ARM64".into()));
418             }
419             tool
420         })
421     }
422 
tool_from_vs15plus_instance( tool: &str, target: &str, instance_path: &PathBuf, ) -> Option<Tool>423     fn tool_from_vs15plus_instance(
424         tool: &str,
425         target: &str,
426         instance_path: &PathBuf,
427     ) -> Option<Tool> {
428         let (bin_path, host_dylib_path, lib_path, include_path) =
429             vs15plus_vc_paths(target, instance_path)?;
430         let tool_path = bin_path.join(tool);
431         if !tool_path.exists() {
432             return None;
433         };
434 
435         let mut tool = MsvcTool::new(tool_path);
436         tool.path.push(bin_path.clone());
437         tool.path.push(host_dylib_path);
438         tool.libs.push(lib_path);
439         tool.include.push(include_path);
440 
441         if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &bin_path) {
442             tool.libs.push(atl_lib_path);
443             tool.include.push(atl_include_path);
444         }
445 
446         add_sdks(&mut tool, target)?;
447 
448         Some(tool.into_tool())
449     }
450 
vs15plus_vc_paths( target: &str, instance_path: &PathBuf, ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf)>451     fn vs15plus_vc_paths(
452         target: &str,
453         instance_path: &PathBuf,
454     ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf)> {
455         let version_path =
456             instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
457         let mut version_file = File::open(version_path).ok()?;
458         let mut version = String::new();
459         version_file.read_to_string(&mut version).ok()?;
460         let version = version.trim();
461         let host = match host_arch() {
462             X86 => "X86",
463             X86_64 => "X64",
464             // There is no natively hosted compiler on ARM64.
465             // Instead, use the x86 toolchain under emulation (there is no x64 emulation).
466             AARCH64 => "X86",
467             _ => return None,
468         };
469         let target = lib_subdir(target)?;
470         // The directory layout here is MSVC/bin/Host$host/$target/
471         let path = instance_path.join(r"VC\Tools\MSVC").join(version);
472         // This is the path to the toolchain for a particular target, running
473         // on a given host
474         let bin_path = path
475             .join("bin")
476             .join(&format!("Host{}", host))
477             .join(&target);
478         // But! we also need PATH to contain the target directory for the host
479         // architecture, because it contains dlls like mspdb140.dll compiled for
480         // the host architecture.
481         let host_dylib_path = path
482             .join("bin")
483             .join(&format!("Host{}", host))
484             .join(&host.to_lowercase());
485         let lib_path = path.join("lib").join(&target);
486         let include_path = path.join("include");
487         Some((bin_path, host_dylib_path, lib_path, include_path))
488     }
489 
atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)>490     fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> {
491         let atl_path = path.join("atlmfc");
492         let sub = lib_subdir(target)?;
493         if atl_path.exists() {
494             Some((atl_path.join("lib").join(sub), atl_path.join("include")))
495         } else {
496             None
497         }
498     }
499 
500     // For MSVC 14 we need to find the Universal CRT as well as either
501     // the Windows 10 SDK or Windows 8.1 SDK.
find_msvc_14(tool: &str, target: &str) -> Option<Tool>502     pub fn find_msvc_14(tool: &str, target: &str) -> Option<Tool> {
503         let vcdir = get_vc_dir("14.0")?;
504         let mut tool = get_tool(tool, &vcdir, target)?;
505         add_sdks(&mut tool, target)?;
506         Some(tool.into_tool())
507     }
508 
add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()>509     fn add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()> {
510         let sub = lib_subdir(target)?;
511         let (ucrt, ucrt_version) = get_ucrt_dir()?;
512 
513         let host = match host_arch() {
514             X86 => "x86",
515             X86_64 => "x64",
516             AARCH64 => "arm64",
517             _ => return None,
518         };
519 
520         tool.path
521             .push(ucrt.join("bin").join(&ucrt_version).join(host));
522 
523         let ucrt_include = ucrt.join("include").join(&ucrt_version);
524         tool.include.push(ucrt_include.join("ucrt"));
525 
526         let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
527         tool.libs.push(ucrt_lib.join("ucrt").join(sub));
528 
529         if let Some((sdk, version)) = get_sdk10_dir() {
530             tool.path.push(sdk.join("bin").join(host));
531             let sdk_lib = sdk.join("lib").join(&version);
532             tool.libs.push(sdk_lib.join("um").join(sub));
533             let sdk_include = sdk.join("include").join(&version);
534             tool.include.push(sdk_include.join("um"));
535             tool.include.push(sdk_include.join("cppwinrt"));
536             tool.include.push(sdk_include.join("winrt"));
537             tool.include.push(sdk_include.join("shared"));
538         } else if let Some(sdk) = get_sdk81_dir() {
539             tool.path.push(sdk.join("bin").join(host));
540             let sdk_lib = sdk.join("lib").join("winv6.3");
541             tool.libs.push(sdk_lib.join("um").join(sub));
542             let sdk_include = sdk.join("include");
543             tool.include.push(sdk_include.join("um"));
544             tool.include.push(sdk_include.join("winrt"));
545             tool.include.push(sdk_include.join("shared"));
546         }
547 
548         Some(())
549     }
550 
551     // For MSVC 12 we need to find the Windows 8.1 SDK.
find_msvc_12(tool: &str, target: &str) -> Option<Tool>552     pub fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> {
553         let vcdir = get_vc_dir("12.0")?;
554         let mut tool = get_tool(tool, &vcdir, target)?;
555         let sub = lib_subdir(target)?;
556         let sdk81 = get_sdk81_dir()?;
557         tool.path.push(sdk81.join("bin").join(sub));
558         let sdk_lib = sdk81.join("lib").join("winv6.3");
559         tool.libs.push(sdk_lib.join("um").join(sub));
560         let sdk_include = sdk81.join("include");
561         tool.include.push(sdk_include.join("shared"));
562         tool.include.push(sdk_include.join("um"));
563         tool.include.push(sdk_include.join("winrt"));
564         Some(tool.into_tool())
565     }
566 
567     // For MSVC 11 we need to find the Windows 8 SDK.
find_msvc_11(tool: &str, target: &str) -> Option<Tool>568     pub fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> {
569         let vcdir = get_vc_dir("11.0")?;
570         let mut tool = get_tool(tool, &vcdir, target)?;
571         let sub = lib_subdir(target)?;
572         let sdk8 = get_sdk8_dir()?;
573         tool.path.push(sdk8.join("bin").join(sub));
574         let sdk_lib = sdk8.join("lib").join("win8");
575         tool.libs.push(sdk_lib.join("um").join(sub));
576         let sdk_include = sdk8.join("include");
577         tool.include.push(sdk_include.join("shared"));
578         tool.include.push(sdk_include.join("um"));
579         tool.include.push(sdk_include.join("winrt"));
580         Some(tool.into_tool())
581     }
582 
add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>)583     fn add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>) {
584         let prev = env::var_os(env).unwrap_or(OsString::new());
585         let prev = env::split_paths(&prev);
586         let new = paths.into_iter().chain(prev);
587         tool.env
588             .push((env.to_string().into(), env::join_paths(new).unwrap()));
589     }
590 
591     // Given a possible MSVC installation directory, we look for the linker and
592     // then add the MSVC library path.
get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool>593     fn get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool> {
594         bin_subdir(target)
595             .into_iter()
596             .map(|(sub, host)| {
597                 (
598                     path.join("bin").join(sub).join(tool),
599                     path.join("bin").join(host),
600                 )
601             })
602             .filter(|&(ref path, _)| path.is_file())
603             .map(|(path, host)| {
604                 let mut tool = MsvcTool::new(path);
605                 tool.path.push(host);
606                 tool
607             })
608             .filter_map(|mut tool| {
609                 let sub = vc_lib_subdir(target)?;
610                 tool.libs.push(path.join("lib").join(sub));
611                 tool.include.push(path.join("include"));
612                 let atlmfc_path = path.join("atlmfc");
613                 if atlmfc_path.exists() {
614                     tool.libs.push(atlmfc_path.join("lib").join(sub));
615                     tool.include.push(atlmfc_path.join("include"));
616                 }
617                 Some(tool)
618             })
619             .next()
620     }
621 
622     // To find MSVC we look in a specific registry key for the version we are
623     // trying to find.
get_vc_dir(ver: &str) -> Option<PathBuf>624     fn get_vc_dir(ver: &str) -> Option<PathBuf> {
625         let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
626         let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
627         let path = key.query_str(ver).ok()?;
628         Some(path.into())
629     }
630 
631     // To find the Universal CRT we look in a specific registry key for where
632     // all the Universal CRTs are located and then sort them asciibetically to
633     // find the newest version. While this sort of sorting isn't ideal,  it is
634     // what vcvars does so that's good enough for us.
635     //
636     // Returns a pair of (root, version) for the ucrt dir if found
get_ucrt_dir() -> Option<(PathBuf, String)>637     fn get_ucrt_dir() -> Option<(PathBuf, String)> {
638         let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
639         let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
640         let root = key.query_str("KitsRoot10").ok()?;
641         let readdir = Path::new(&root).join("lib").read_dir().ok()?;
642         let max_libdir = readdir
643             .filter_map(|dir| dir.ok())
644             .map(|dir| dir.path())
645             .filter(|dir| {
646                 dir.components()
647                     .last()
648                     .and_then(|c| c.as_os_str().to_str())
649                     .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
650                     .unwrap_or(false)
651             })
652             .max()?;
653         let version = max_libdir.components().last().unwrap();
654         let version = version.as_os_str().to_str().unwrap().to_string();
655         Some((root.into(), version))
656     }
657 
658     // Vcvars finds the correct version of the Windows 10 SDK by looking
659     // for the include `um\Windows.h` because sometimes a given version will
660     // only have UCRT bits without the rest of the SDK. Since we only care about
661     // libraries and not includes, we instead look for `um\x64\kernel32.lib`.
662     // Since the 32-bit and 64-bit libraries are always installed together we
663     // only need to bother checking x64, making this code a tiny bit simpler.
664     // Like we do for the Universal CRT, we sort the possibilities
665     // asciibetically to find the newest one as that is what vcvars does.
get_sdk10_dir() -> Option<(PathBuf, String)>666     fn get_sdk10_dir() -> Option<(PathBuf, String)> {
667         let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
668         let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
669         let root = key.query_str("InstallationFolder").ok()?;
670         let readdir = Path::new(&root).join("lib").read_dir().ok()?;
671         let mut dirs = readdir
672             .filter_map(|dir| dir.ok())
673             .map(|dir| dir.path())
674             .collect::<Vec<_>>();
675         dirs.sort();
676         let dir = dirs
677             .into_iter()
678             .rev()
679             .filter(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())
680             .next()?;
681         let version = dir.components().last().unwrap();
682         let version = version.as_os_str().to_str().unwrap().to_string();
683         Some((root.into(), version))
684     }
685 
686     // Interestingly there are several subdirectories, `win7` `win8` and
687     // `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same
688     // applies to us. Note that if we were targeting kernel mode drivers
689     // instead of user mode applications, we would care.
get_sdk81_dir() -> Option<PathBuf>690     fn get_sdk81_dir() -> Option<PathBuf> {
691         let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
692         let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
693         let root = key.query_str("InstallationFolder").ok()?;
694         Some(root.into())
695     }
696 
get_sdk8_dir() -> Option<PathBuf>697     fn get_sdk8_dir() -> Option<PathBuf> {
698         let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0";
699         let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
700         let root = key.query_str("InstallationFolder").ok()?;
701         Some(root.into())
702     }
703 
704     const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
705     const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
706     const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12;
707     const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
708     const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
709     const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64;
710 
711     // When choosing the tool to use, we have to choose the one which matches
712     // the target architecture. Otherwise we end up in situations where someone
713     // on 32-bit Windows is trying to cross compile to 64-bit and it tries to
714     // invoke the native 64-bit compiler which won't work.
715     //
716     // For the return value of this function, the first member of the tuple is
717     // the folder of the tool we will be invoking, while the second member is
718     // the folder of the host toolchain for that tool which is essential when
719     // using a cross linker. We return a Vec since on x64 there are often two
720     // linkers that can target the architecture we desire. The 64-bit host
721     // linker is preferred, and hence first, due to 64-bit allowing it more
722     // address space to work with and potentially being faster.
bin_subdir(target: &str) -> Vec<(&'static str, &'static str)>723     fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> {
724         let arch = target.split('-').next().unwrap();
725         match (arch, host_arch()) {
726             ("i586", X86) | ("i686", X86) => vec![("", "")],
727             ("i586", X86_64) | ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
728             ("x86_64", X86) => vec![("x86_amd64", "")],
729             ("x86_64", X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
730             ("arm", X86) | ("thumbv7a", X86) => vec![("x86_arm", "")],
731             ("arm", X86_64) | ("thumbv7a", X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
732             _ => vec![],
733         }
734     }
735 
lib_subdir(target: &str) -> Option<&'static str>736     fn lib_subdir(target: &str) -> Option<&'static str> {
737         let arch = target.split('-').next().unwrap();
738         match arch {
739             "i586" | "i686" => Some("x86"),
740             "x86_64" => Some("x64"),
741             "arm" | "thumbv7a" => Some("arm"),
742             "aarch64" => Some("arm64"),
743             _ => None,
744         }
745     }
746 
747     // MSVC's x86 libraries are not in a subfolder
vc_lib_subdir(target: &str) -> Option<&'static str>748     fn vc_lib_subdir(target: &str) -> Option<&'static str> {
749         let arch = target.split('-').next().unwrap();
750         match arch {
751             "i586" | "i686" => Some(""),
752             "x86_64" => Some("amd64"),
753             "arm" | "thumbv7a" => Some("arm"),
754             "aarch64" => Some("arm64"),
755             _ => None,
756         }
757     }
758 
759     #[allow(bad_style)]
host_arch() -> u16760     fn host_arch() -> u16 {
761         type DWORD = u32;
762         type WORD = u16;
763         type LPVOID = *mut u8;
764         type DWORD_PTR = usize;
765 
766         #[repr(C)]
767         struct SYSTEM_INFO {
768             wProcessorArchitecture: WORD,
769             _wReserved: WORD,
770             _dwPageSize: DWORD,
771             _lpMinimumApplicationAddress: LPVOID,
772             _lpMaximumApplicationAddress: LPVOID,
773             _dwActiveProcessorMask: DWORD_PTR,
774             _dwNumberOfProcessors: DWORD,
775             _dwProcessorType: DWORD,
776             _dwAllocationGranularity: DWORD,
777             _wProcessorLevel: WORD,
778             _wProcessorRevision: WORD,
779         }
780 
781         extern "system" {
782             fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
783         }
784 
785         unsafe {
786             let mut info = mem::zeroed();
787             GetNativeSystemInfo(&mut info);
788             info.wProcessorArchitecture
789         }
790     }
791 
792     // Given a registry key, look at all the sub keys and find the one which has
793     // the maximal numeric value.
794     //
795     // Returns the name of the maximal key as well as the opened maximal key.
max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)>796     fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
797         let mut max_vers = 0;
798         let mut max_key = None;
799         for subkey in key.iter().filter_map(|k| k.ok()) {
800             let val = subkey
801                 .to_str()
802                 .and_then(|s| s.trim_left_matches("v").replace(".", "").parse().ok());
803             let val = match val {
804                 Some(s) => s,
805                 None => continue,
806             };
807             if val > max_vers {
808                 if let Ok(k) = key.open(&subkey) {
809                     max_vers = val;
810                     max_key = Some((subkey, k));
811                 }
812             }
813         }
814         max_key
815     }
816 
has_msbuild_version(version: &str) -> bool817     pub fn has_msbuild_version(version: &str) -> bool {
818         match version {
819             "16.0" => {
820                 find_msbuild_vs16("x86_64-pc-windows-msvc").is_some()
821                     || find_msbuild_vs16("i686-pc-windows-msvc").is_some()
822                     || find_msbuild_vs16("aarch64-pc-windows-msvc").is_some()
823             }
824             "15.0" => {
825                 find_msbuild_vs15("x86_64-pc-windows-msvc").is_some()
826                     || find_msbuild_vs15("i686-pc-windows-msvc").is_some()
827                     || find_msbuild_vs15("aarch64-pc-windows-msvc").is_some()
828             }
829             "12.0" | "14.0" => LOCAL_MACHINE
830                 .open(&OsString::from(format!(
831                     "SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}",
832                     version
833                 )))
834                 .is_ok(),
835             _ => false,
836         }
837     }
838 
find_devenv(target: &str) -> Option<Tool>839     pub fn find_devenv(target: &str) -> Option<Tool> {
840         find_devenv_vs15(&target)
841     }
842 
find_devenv_vs15(target: &str) -> Option<Tool>843     fn find_devenv_vs15(target: &str) -> Option<Tool> {
844         find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target)
845     }
846 
847     // see http://stackoverflow.com/questions/328017/path-to-msbuild
find_msbuild(target: &str) -> Option<Tool>848     pub fn find_msbuild(target: &str) -> Option<Tool> {
849         // VS 15 (2017) changed how to locate msbuild
850         if let Some(r) = find_msbuild_vs16(target) {
851             return Some(r);
852         } else if let Some(r) = find_msbuild_vs15(target) {
853             return Some(r);
854         } else {
855             find_old_msbuild(target)
856         }
857     }
858 
find_msbuild_vs15(target: &str) -> Option<Tool>859     fn find_msbuild_vs15(target: &str) -> Option<Tool> {
860         find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target)
861     }
862 
find_old_msbuild(target: &str) -> Option<Tool>863     fn find_old_msbuild(target: &str) -> Option<Tool> {
864         let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
865         LOCAL_MACHINE
866             .open(key.as_ref())
867             .ok()
868             .and_then(|key| {
869                 max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
870             })
871             .map(|path| {
872                 let mut path = PathBuf::from(path);
873                 path.push("MSBuild.exe");
874                 let mut tool = Tool::with_family(path, MSVC_FAMILY);
875                 if target.contains("x86_64") {
876                     tool.env.push(("Platform".into(), "X64".into()));
877                 }
878                 tool
879             })
880     }
881 }
882