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