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