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