]> git.proxmox.com Git - rustc.git/blob - vendor/cc/src/windows_registry.rs
New upstream version 1.59.0+dfsg1
[rustc.git] / vendor / cc / src / windows_registry.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 // 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.
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))]
43 pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> {
44 None
45 }
46
47 /// Documented above.
48 #[cfg(windows)]
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))]
108 pub fn find_vs_version() -> Result<VsVers, String> {
109 Err(format!("not windows"))
110 }
111
112 /// Documented above
113 #[cfg(windows)]
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 {
187 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
196 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)]
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.
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)]
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
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
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.
307 fn vs15plus_instances(target: &str) -> Option<VsInstances> {
308 vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target))
309 }
310
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
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 '.'
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
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
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
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
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
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.
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
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.
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.
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
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.
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.
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
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.
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.
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
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.
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
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
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)]
760 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.
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
817 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
839 pub fn find_devenv(target: &str) -> Option<Tool> {
840 find_devenv_vs15(&target)
841 }
842
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
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
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
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 }