1#![allow(clippy::upper_case_acronyms)]
16
17use std::{
18 env,
19 ffi::{OsStr, OsString},
20 ops::Deref,
21 path::PathBuf,
22 process::Command,
23 sync::Arc,
24};
25
26use crate::Tool;
27use crate::ToolFamily;
28
29const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false };
30
31#[derive(Copy, Clone, PartialEq, Eq)]
33enum TargetArch {
34 X86,
35 X64,
36 Arm,
37 Arm64,
38 Arm64ec,
39}
40impl TargetArch {
41 fn new(arch: &str) -> Option<Self> {
43 match arch {
45 "x64" | "x86_64" => Some(Self::X64),
46 "arm64" | "aarch64" => Some(Self::Arm64),
47 "arm64ec" => Some(Self::Arm64ec),
48 "x86" | "i686" | "i586" => Some(Self::X86),
49 "arm" | "thumbv7a" => Some(Self::Arm),
50 _ => None,
51 }
52 }
53
54 #[cfg(windows)]
55 fn as_vs_arch(&self) -> &'static str {
57 match self {
58 Self::X64 => "x64",
59 Self::Arm64 | Self::Arm64ec => "arm64",
60 Self::X86 => "x86",
61 Self::Arm => "arm",
62 }
63 }
64}
65
66pub(crate) enum Env {
67 Owned(OsString),
68 Arced(Arc<OsStr>),
69}
70
71impl AsRef<OsStr> for Env {
72 fn as_ref(&self) -> &OsStr {
73 self.deref()
74 }
75}
76
77impl Deref for Env {
78 type Target = OsStr;
79
80 fn deref(&self) -> &Self::Target {
81 match self {
82 Env::Owned(os_str) => os_str,
83 Env::Arced(os_str) => os_str,
84 }
85 }
86}
87
88impl From<Env> for PathBuf {
89 fn from(env: Env) -> Self {
90 match env {
91 Env::Owned(os_str) => PathBuf::from(os_str),
92 Env::Arced(os_str) => PathBuf::from(os_str.deref()),
93 }
94 }
95}
96
97pub(crate) trait EnvGetter {
98 fn get_env(&self, name: &'static str) -> Option<Env>;
99}
100
101struct StdEnvGetter;
102
103impl EnvGetter for StdEnvGetter {
104 #[allow(clippy::disallowed_methods)]
105 fn get_env(&self, name: &'static str) -> Option<Env> {
106 env::var_os(name).map(Env::Owned)
107 }
108}
109
110pub fn find(arch_or_target: &str, tool: &str) -> Option<Command> {
131 find_tool(arch_or_target, tool).map(|c| c.to_command())
132}
133
134pub fn find_tool(arch_or_target: &str, tool: &str) -> Option<Tool> {
138 let full_arch = if let Some((full_arch, rest)) = arch_or_target.split_once("-") {
139 if !rest.contains("msvc") {
142 return None;
143 }
144 full_arch
145 } else {
146 arch_or_target
147 };
148 find_tool_inner(full_arch, tool, &StdEnvGetter)
149}
150
151pub(crate) fn find_tool_inner(
152 full_arch: &str,
153 tool: &str,
154 env_getter: &dyn EnvGetter,
155) -> Option<Tool> {
156 let target = TargetArch::new(full_arch)?;
158
159 if tool.contains("msbuild") {
162 return impl_::find_msbuild(target, env_getter);
163 }
164
165 if tool.contains("devenv") {
168 return impl_::find_devenv(target, env_getter);
169 }
170
171 impl_::find_msvc_environment(tool, target, env_getter)
179 .or_else(|| impl_::find_msvc_15plus(tool, target, env_getter))
180 .or_else(|| impl_::find_msvc_14(tool, target, env_getter))
181}
182
183#[derive(Debug, PartialEq, Eq, Copy, Clone)]
185#[non_exhaustive]
186pub enum VsVers {
187 #[deprecated(
189 note = "Visual Studio 12 is no longer supported. cc will never return this value."
190 )]
191 Vs12,
192 Vs14,
194 Vs15,
196 Vs16,
198 Vs17,
200}
201
202#[allow(clippy::disallowed_methods)]
207pub fn find_vs_version() -> Result<VsVers, String> {
208 fn has_msbuild_version(version: &str) -> bool {
209 impl_::has_msbuild_version(version, &StdEnvGetter)
210 }
211
212 match std::env::var("VisualStudioVersion") {
213 Ok(version) => match &version[..] {
214 "17.0" => Ok(VsVers::Vs17),
215 "16.0" => Ok(VsVers::Vs16),
216 "15.0" => Ok(VsVers::Vs15),
217 "14.0" => Ok(VsVers::Vs14),
218 vers => Err(format!(
219 "\n\n\
220 unsupported or unknown VisualStudio version: {}\n\
221 if another version is installed consider running \
222 the appropriate vcvars script before building this \
223 crate\n\
224 ",
225 vers
226 )),
227 },
228 _ => {
229 if has_msbuild_version("17.0") {
232 Ok(VsVers::Vs17)
233 } else if has_msbuild_version("16.0") {
234 Ok(VsVers::Vs16)
235 } else if has_msbuild_version("15.0") {
236 Ok(VsVers::Vs15)
237 } else if has_msbuild_version("14.0") {
238 Ok(VsVers::Vs14)
239 } else {
240 Err("\n\n\
241 couldn't determine visual studio generator\n\
242 if VisualStudio is installed, however, consider \
243 running the appropriate vcvars script before building \
244 this crate\n\
245 "
246 .to_string())
247 }
248 }
249 }
250}
251
252#[cfg(windows)]
254mod impl_ {
255 use crate::windows::com;
256 use crate::windows::registry::{RegistryKey, LOCAL_MACHINE};
257 use crate::windows::setup_config::SetupConfiguration;
258 use crate::windows::vs_instances::{VsInstances, VswhereInstance};
259 use crate::windows::windows_sys::{
260 GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE,
261 IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK,
262 };
263 use std::convert::TryFrom;
264 use std::env;
265 use std::ffi::OsString;
266 use std::fs::File;
267 use std::io::Read;
268 use std::iter;
269 use std::mem;
270 use std::path::{Path, PathBuf};
271 use std::process::Command;
272 use std::str::FromStr;
273 use std::sync::atomic::{AtomicBool, Ordering};
274 use std::sync::Once;
275
276 use super::{EnvGetter, TargetArch, MSVC_FAMILY};
277 use crate::Tool;
278
279 struct MsvcTool {
280 tool: PathBuf,
281 libs: Vec<PathBuf>,
282 path: Vec<PathBuf>,
283 include: Vec<PathBuf>,
284 }
285
286 struct LibraryHandle(HMODULE);
287
288 impl LibraryHandle {
289 fn new(name: &[u8]) -> Option<Self> {
290 let handle = unsafe { LoadLibraryA(name.as_ptr() as _) };
291 (!handle.is_null()).then_some(Self(handle))
292 }
293
294 unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> {
303 let symbol = GetProcAddress(self.0, name.as_ptr() as _);
304 symbol.map(|symbol| mem::transmute_copy(&symbol))
305 }
306 }
307
308 type GetMachineTypeAttributesFuncType =
309 unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32;
310 const _: () = {
311 let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes;
315 };
316
317 fn is_amd64_emulation_supported_inner() -> Option<bool> {
318 let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?;
320 let get_machine_type_attributes = unsafe {
322 kernel32
323 .get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0")
324 }?;
325 let mut attributes = Default::default();
326 if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK
327 {
328 Some((attributes & UserEnabled) != 0)
329 } else {
330 Some(false)
331 }
332 }
333
334 fn is_amd64_emulation_supported() -> bool {
335 static LOAD_VALUE: Once = Once::new();
337 static IS_SUPPORTED: AtomicBool = AtomicBool::new(false);
338
339 LOAD_VALUE.call_once(|| {
341 IS_SUPPORTED.store(
342 is_amd64_emulation_supported_inner().unwrap_or(false),
343 Ordering::Relaxed,
344 );
345 });
346 IS_SUPPORTED.load(Ordering::Relaxed)
347 }
348
349 impl MsvcTool {
350 fn new(tool: PathBuf) -> MsvcTool {
351 MsvcTool {
352 tool,
353 libs: Vec::new(),
354 path: Vec::new(),
355 include: Vec::new(),
356 }
357 }
358
359 fn into_tool(self, env_getter: &dyn EnvGetter) -> Tool {
360 let MsvcTool {
361 tool,
362 libs,
363 path,
364 include,
365 } = self;
366 let mut tool = Tool::with_family(tool, MSVC_FAMILY);
367 add_env(&mut tool, "LIB", libs, env_getter);
368 add_env(&mut tool, "PATH", path, env_getter);
369 add_env(&mut tool, "INCLUDE", include, env_getter);
370 tool
371 }
372 }
373
374 fn is_vscmd_target(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
377 is_vscmd_target_env(target, env_getter).or_else(|| is_vscmd_target_cl(target, env_getter))
378 }
379
380 fn is_vscmd_target_env(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
383 let vscmd_arch = env_getter.get_env("VSCMD_ARG_TGT_ARCH")?;
384 Some(target.as_vs_arch() == vscmd_arch.as_ref())
385 }
386
387 fn is_vscmd_target_cl(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
390 let cmd_target = vscmd_target_cl(env_getter)?;
391 Some(target.as_vs_arch() == cmd_target)
392 }
393
394 fn vscmd_target_cl(env_getter: &dyn EnvGetter) -> Option<&'static str> {
397 let cl_exe = env_getter.get_env("PATH").and_then(|path| {
398 env::split_paths(&path)
399 .map(|p| p.join("cl.exe"))
400 .find(|p| p.exists())
401 })?;
402 let mut cl = Command::new(cl_exe);
403 cl.stderr(std::process::Stdio::piped())
404 .stdout(std::process::Stdio::null());
405
406 let out = cl.output().ok()?;
407 let cl_arch = out
408 .stderr
409 .split(|&b| b == b'\n' || b == b'\r')
410 .next()?
411 .rsplit(|&b| b == b' ')
412 .next()?;
413
414 match cl_arch {
415 b"x64" => Some("x64"),
416 b"x86" => Some("x86"),
417 b"ARM64" => Some("arm64"),
418 b"ARM" => Some("arm"),
419 _ => None,
420 }
421 }
422
423 pub(super) fn find_msvc_environment(
425 tool: &str,
426 target: TargetArch,
427 env_getter: &dyn EnvGetter,
428 ) -> Option<Tool> {
429 if env_getter.get_env("VCINSTALLDIR").is_none()
434 && env_getter.get_env("VSTEL_MSBuildProjectFullPath").is_none()
435 {
436 return None;
437 }
438
439 if is_vscmd_target(target, env_getter) == Some(false) {
442 let vs_install_dir: PathBuf = env_getter.get_env("VSINSTALLDIR")?.into();
444 tool_from_vs15plus_instance(tool, target, &vs_install_dir, env_getter)
445 } else {
446 env_getter
448 .get_env("PATH")
449 .and_then(|path| {
450 env::split_paths(&path)
451 .map(|p| p.join(tool))
452 .find(|p| p.exists())
453 })
454 .map(|path| Tool::with_family(path, MSVC_FAMILY))
455 }
456 }
457
458 fn find_msbuild_vs17(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
459 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17", env_getter)
460 }
461
462 #[allow(bare_trait_objects)]
463 fn vs16plus_instances(
464 target: TargetArch,
465 version: &'static str,
466 env_getter: &dyn EnvGetter,
467 ) -> Box<Iterator<Item = PathBuf>> {
468 let instances = if let Some(instances) = vs15plus_instances(target, env_getter) {
469 instances
470 } else {
471 return Box::new(iter::empty());
472 };
473 Box::new(instances.into_iter().filter_map(move |instance| {
474 let installation_name = instance.installation_name()?;
475 if installation_name.starts_with(&format!("VisualStudio/{}.", version))
476 || installation_name.starts_with(&format!("VisualStudioPreview/{}.", version))
477 {
478 Some(instance.installation_path()?)
479 } else {
480 None
481 }
482 }))
483 }
484
485 fn find_tool_in_vs16plus_path(
486 tool: &str,
487 target: TargetArch,
488 version: &'static str,
489 env_getter: &dyn EnvGetter,
490 ) -> Option<Tool> {
491 vs16plus_instances(target, version, env_getter)
492 .filter_map(|path| {
493 let path = path.join(tool);
494 if !path.is_file() {
495 return None;
496 }
497 let mut tool = Tool::with_family(path, MSVC_FAMILY);
498 if target == TargetArch::X64 {
499 tool.env.push(("Platform".into(), "X64".into()));
500 }
501 if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
502 tool.env.push(("Platform".into(), "ARM64".into()));
503 }
504 Some(tool)
505 })
506 .next()
507 }
508
509 fn find_msbuild_vs16(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
510 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16", env_getter)
511 }
512
513 fn vs15plus_instances(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<VsInstances> {
526 vs15plus_instances_using_com()
527 .or_else(|| vs15plus_instances_using_vswhere(target, env_getter))
528 }
529
530 fn vs15plus_instances_using_com() -> Option<VsInstances> {
531 com::initialize().ok()?;
532
533 let config = SetupConfiguration::new().ok()?;
534 let enum_setup_instances = config.enum_all_instances().ok()?;
535
536 Some(VsInstances::ComBased(enum_setup_instances))
537 }
538
539 fn vs15plus_instances_using_vswhere(
540 target: TargetArch,
541 env_getter: &dyn EnvGetter,
542 ) -> Option<VsInstances> {
543 let program_files_path = env_getter
544 .get_env("ProgramFiles(x86)")
545 .or_else(|| env_getter.get_env("ProgramFiles"))?;
546
547 let program_files_path = Path::new(program_files_path.as_ref());
548
549 let vswhere_path =
550 program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe");
551
552 if !vswhere_path.exists() {
553 return None;
554 }
555
556 let tools_arch = match target {
557 TargetArch::X86 | TargetArch::X64 => Some("x86.x64"),
558 TargetArch::Arm => Some("ARM"),
559 TargetArch::Arm64 | TargetArch::Arm64ec => Some("ARM64"),
560 };
561
562 let vswhere_output = Command::new(vswhere_path)
563 .args([
564 "-latest",
565 "-products",
566 "*",
567 "-requires",
568 &format!("Microsoft.VisualStudio.Component.VC.Tools.{}", tools_arch?),
569 "-format",
570 "text",
571 "-nologo",
572 ])
573 .stderr(std::process::Stdio::inherit())
574 .output()
575 .ok()?;
576
577 let vs_instances =
578 VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?);
579
580 Some(vs_instances)
581 }
582
583 fn parse_version(version: &str) -> Option<Vec<u16>> {
586 version
587 .split('.')
588 .map(|chunk| u16::from_str(chunk).ok())
589 .collect()
590 }
591
592 pub(super) fn find_msvc_15plus(
593 tool: &str,
594 target: TargetArch,
595 env_getter: &dyn EnvGetter,
596 ) -> Option<Tool> {
597 let iter = vs15plus_instances(target, env_getter)?;
598 iter.into_iter()
599 .filter_map(|instance| {
600 let version = parse_version(&instance.installation_version()?)?;
601 let instance_path = instance.installation_path()?;
602 let tool = tool_from_vs15plus_instance(tool, target, &instance_path, env_getter)?;
603 Some((version, tool))
604 })
605 .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version))
606 .map(|(_version, tool)| tool)
607 }
608
609 fn find_tool_in_vs15_path(
617 tool: &str,
618 target: TargetArch,
619 env_getter: &dyn EnvGetter,
620 ) -> Option<Tool> {
621 let mut path = match vs15plus_instances(target, env_getter) {
622 Some(instances) => instances
623 .into_iter()
624 .filter_map(|instance| instance.installation_path())
625 .map(|path| path.join(tool))
626 .find(|path| path.is_file()),
627 None => None,
628 };
629
630 if path.is_none() {
631 let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7";
632 path = LOCAL_MACHINE
633 .open(key.as_ref())
634 .ok()
635 .and_then(|key| key.query_str("15.0").ok())
636 .map(|path| PathBuf::from(path).join(tool))
637 .and_then(|path| if path.is_file() { Some(path) } else { None });
638 }
639
640 path.map(|path| {
641 let mut tool = Tool::with_family(path, MSVC_FAMILY);
642 if target == TargetArch::X64 {
643 tool.env.push(("Platform".into(), "X64".into()));
644 } else if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
645 tool.env.push(("Platform".into(), "ARM64".into()));
646 }
647 tool
648 })
649 }
650
651 fn tool_from_vs15plus_instance(
652 tool: &str,
653 target: TargetArch,
654 instance_path: &Path,
655 env_getter: &dyn EnvGetter,
656 ) -> Option<Tool> {
657 let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) =
658 vs15plus_vc_paths(target, instance_path, env_getter)?;
659 let tool_path = bin_path.join(tool);
660 if !tool_path.exists() {
661 return None;
662 };
663
664 let mut tool = MsvcTool::new(tool_path);
665 tool.path.push(bin_path.clone());
666 tool.path.push(host_dylib_path);
667 if let Some(alt_lib_path) = alt_lib_path {
668 tool.libs.push(alt_lib_path);
669 }
670 tool.libs.push(lib_path);
671 tool.include.push(include_path);
672
673 if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &root_path) {
674 tool.libs.push(atl_lib_path);
675 tool.include.push(atl_include_path);
676 }
677
678 add_sdks(&mut tool, target, env_getter)?;
679
680 Some(tool.into_tool(env_getter))
681 }
682
683 fn vs15plus_vc_paths(
684 target_arch: TargetArch,
685 instance_path: &Path,
686 env_getter: &dyn EnvGetter,
687 ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> {
688 let version = vs15plus_vc_read_version(instance_path)?;
689
690 let hosts = match host_arch() {
691 X86 => &["X86"],
692 X86_64 => &["X64"],
693 AARCH64 => {
698 if is_amd64_emulation_supported() {
699 &["ARM64", "X64", "X86"][..]
700 } else {
701 &["ARM64", "X86"]
702 }
703 }
704 _ => return None,
705 };
706 let target_dir = target_arch.as_vs_arch();
707 let path = instance_path.join(r"VC\Tools\MSVC").join(version);
709 let (host_path, host) = hosts.iter().find_map(|&x| {
711 let candidate = path.join("bin").join(format!("Host{}", x));
712 if candidate.join(target_dir).exists() {
713 Some((candidate, x))
714 } else {
715 None
716 }
717 })?;
718 let bin_path = host_path.join(target_dir);
721 let host_dylib_path = host_path.join(host.to_lowercase());
725 let lib_fragment = if use_spectre_mitigated_libs(env_getter) {
726 r"lib\spectre"
727 } else {
728 "lib"
729 };
730 let lib_path = path.join(lib_fragment).join(target_dir);
731 let alt_lib_path =
732 (target_arch == TargetArch::Arm64ec).then(|| path.join(lib_fragment).join("arm64ec"));
733 let include_path = path.join("include");
734 Some((
735 path,
736 bin_path,
737 host_dylib_path,
738 lib_path,
739 alt_lib_path,
740 include_path,
741 ))
742 }
743
744 fn vs15plus_vc_read_version(dir: &Path) -> Option<String> {
745 let mut version_path: PathBuf =
747 dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
748 let mut version_file = if let Ok(f) = File::open(&version_path) {
749 f
750 } else {
751 let mut version_file = String::new();
756 version_path.pop();
757 for file in version_path.read_dir().ok()? {
758 let name = file.ok()?.file_name();
759 let name = name.to_str()?;
760 if name.starts_with("Microsoft.VCToolsVersion.v")
761 && name.ends_with(".default.txt")
762 && name > &version_file
763 {
764 version_file.replace_range(.., name);
765 }
766 }
767 if version_file.is_empty() {
768 return None;
769 }
770 version_path.push(version_file);
771 File::open(version_path).ok()?
772 };
773
774 let mut version = String::new();
776 version_file.read_to_string(&mut version).ok()?;
777 version.truncate(version.trim_end().len());
778 Some(version)
779 }
780
781 fn use_spectre_mitigated_libs(env_getter: &dyn EnvGetter) -> bool {
782 env_getter
783 .get_env("VSCMD_ARG_VCVARS_SPECTRE")
784 .map(|env| env.as_ref() == "spectre")
785 .unwrap_or_default()
786 }
787
788 fn atl_paths(target: TargetArch, path: &Path) -> Option<(PathBuf, PathBuf)> {
789 let atl_path = path.join("atlmfc");
790 let sub = target.as_vs_arch();
791 if atl_path.exists() {
792 Some((atl_path.join("lib").join(sub), atl_path.join("include")))
793 } else {
794 None
795 }
796 }
797
798 pub(super) fn find_msvc_14(
801 tool: &str,
802 target: TargetArch,
803 env_getter: &dyn EnvGetter,
804 ) -> Option<Tool> {
805 let vcdir = get_vc_dir("14.0")?;
806 let mut tool = get_tool(tool, &vcdir, target)?;
807 add_sdks(&mut tool, target, env_getter)?;
808 Some(tool.into_tool(env_getter))
809 }
810
811 fn add_sdks(tool: &mut MsvcTool, target: TargetArch, env_getter: &dyn EnvGetter) -> Option<()> {
812 let sub = target.as_vs_arch();
813 let (ucrt, ucrt_version) = get_ucrt_dir()?;
814
815 let host = match host_arch() {
816 X86 => "x86",
817 X86_64 => "x64",
818 AARCH64 => "arm64",
819 _ => return None,
820 };
821
822 tool.path
823 .push(ucrt.join("bin").join(&ucrt_version).join(host));
824
825 let ucrt_include = ucrt.join("include").join(&ucrt_version);
826 tool.include.push(ucrt_include.join("ucrt"));
827
828 let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
829 tool.libs.push(ucrt_lib.join("ucrt").join(sub));
830
831 if let Some((sdk, version)) = get_sdk10_dir(env_getter) {
832 tool.path.push(sdk.join("bin").join(host));
833 let sdk_lib = sdk.join("lib").join(&version);
834 tool.libs.push(sdk_lib.join("um").join(sub));
835 let sdk_include = sdk.join("include").join(&version);
836 tool.include.push(sdk_include.join("um"));
837 tool.include.push(sdk_include.join("cppwinrt"));
838 tool.include.push(sdk_include.join("winrt"));
839 tool.include.push(sdk_include.join("shared"));
840 } else if let Some(sdk) = get_sdk81_dir() {
841 tool.path.push(sdk.join("bin").join(host));
842 let sdk_lib = sdk.join("lib").join("winv6.3");
843 tool.libs.push(sdk_lib.join("um").join(sub));
844 let sdk_include = sdk.join("include");
845 tool.include.push(sdk_include.join("um"));
846 tool.include.push(sdk_include.join("winrt"));
847 tool.include.push(sdk_include.join("shared"));
848 }
849
850 Some(())
851 }
852
853 fn add_env(
854 tool: &mut Tool,
855 env: &'static str,
856 paths: Vec<PathBuf>,
857 env_getter: &dyn EnvGetter,
858 ) {
859 let prev = env_getter.get_env(env);
860 let prev = prev.as_ref().map(AsRef::as_ref).unwrap_or_default();
861 let prev = env::split_paths(&prev);
862 let new = paths.into_iter().chain(prev);
863 tool.env
864 .push((env.to_string().into(), env::join_paths(new).unwrap()));
865 }
866
867 fn get_tool(tool: &str, path: &Path, target: TargetArch) -> Option<MsvcTool> {
870 bin_subdir(target)
871 .into_iter()
872 .map(|(sub, host)| {
873 (
874 path.join("bin").join(sub).join(tool),
875 path.join("bin").join(host),
876 )
877 })
878 .filter(|(path, _)| path.is_file())
879 .map(|(path, host)| {
880 let mut tool = MsvcTool::new(path);
881 tool.path.push(host);
882 tool
883 })
884 .filter_map(|mut tool| {
885 let sub = vc_lib_subdir(target);
886 tool.libs.push(path.join("lib").join(sub));
887 tool.include.push(path.join("include"));
888 let atlmfc_path = path.join("atlmfc");
889 if atlmfc_path.exists() {
890 tool.libs.push(atlmfc_path.join("lib").join(sub));
891 tool.include.push(atlmfc_path.join("include"));
892 }
893 Some(tool)
894 })
895 .next()
896 }
897
898 fn get_vc_dir(ver: &str) -> Option<PathBuf> {
901 let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
902 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
903 let path = key.query_str(ver).ok()?;
904 Some(path.into())
905 }
906
907 fn get_ucrt_dir() -> Option<(PathBuf, String)> {
914 let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
915 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
916 let root = key.query_str("KitsRoot10").ok()?;
917 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
918 let max_libdir = readdir
919 .filter_map(|dir| dir.ok())
920 .map(|dir| dir.path())
921 .filter(|dir| {
922 dir.components()
923 .last()
924 .and_then(|c| c.as_os_str().to_str())
925 .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
926 .unwrap_or(false)
927 })
928 .max()?;
929 let version = max_libdir.components().last().unwrap();
930 let version = version.as_os_str().to_str().unwrap().to_string();
931 Some((root.into(), version))
932 }
933
934 fn get_sdk10_dir(env_getter: &dyn EnvGetter) -> Option<(PathBuf, String)> {
946 if let (Some(root), Some(version)) = (
947 env_getter.get_env("WindowsSdkDir"),
948 env_getter
949 .get_env("WindowsSDKVersion")
950 .as_ref()
951 .and_then(|version| version.as_ref().to_str()),
952 ) {
953 return Some((
954 PathBuf::from(root),
955 version.trim_end_matches('\\').to_string(),
956 ));
957 }
958
959 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
960 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
961 let root = key.query_str("InstallationFolder").ok()?;
962 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
963 let mut dirs = readdir
964 .filter_map(|dir| dir.ok())
965 .map(|dir| dir.path())
966 .collect::<Vec<_>>();
967 dirs.sort();
968 let dir = dirs
969 .into_iter()
970 .rev()
971 .find(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())?;
972 let version = dir.components().last().unwrap();
973 let version = version.as_os_str().to_str().unwrap().to_string();
974 Some((root.into(), version))
975 }
976
977 fn get_sdk81_dir() -> Option<PathBuf> {
982 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
983 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
984 let root = key.query_str("InstallationFolder").ok()?;
985 Some(root.into())
986 }
987
988 const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
989 const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
990 const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12;
991 const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
992 const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
993 const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64;
994
995 fn bin_subdir(target: TargetArch) -> Vec<(&'static str, &'static str)> {
1008 match (target, host_arch()) {
1009 (TargetArch::X86, X86) => vec![("", "")],
1010 (TargetArch::X86, X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
1011 (TargetArch::X64, X86) => vec![("x86_amd64", "")],
1012 (TargetArch::X64, X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
1013 (TargetArch::Arm, X86) => vec![("x86_arm", "")],
1014 (TargetArch::Arm, X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
1015 _ => vec![],
1016 }
1017 }
1018
1019 fn vc_lib_subdir(target: TargetArch) -> &'static str {
1021 match target {
1022 TargetArch::X86 => "",
1023 TargetArch::X64 => "amd64",
1024 TargetArch::Arm => "arm",
1025 TargetArch::Arm64 | TargetArch::Arm64ec => "arm64",
1026 }
1027 }
1028
1029 #[allow(bad_style)]
1030 fn host_arch() -> u16 {
1031 type DWORD = u32;
1032 type WORD = u16;
1033 type LPVOID = *mut u8;
1034 type DWORD_PTR = usize;
1035
1036 #[repr(C)]
1037 struct SYSTEM_INFO {
1038 wProcessorArchitecture: WORD,
1039 _wReserved: WORD,
1040 _dwPageSize: DWORD,
1041 _lpMinimumApplicationAddress: LPVOID,
1042 _lpMaximumApplicationAddress: LPVOID,
1043 _dwActiveProcessorMask: DWORD_PTR,
1044 _dwNumberOfProcessors: DWORD,
1045 _dwProcessorType: DWORD,
1046 _dwAllocationGranularity: DWORD,
1047 _wProcessorLevel: WORD,
1048 _wProcessorRevision: WORD,
1049 }
1050
1051 extern "system" {
1052 fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
1053 }
1054
1055 unsafe {
1056 let mut info = mem::zeroed();
1057 GetNativeSystemInfo(&mut info);
1058 info.wProcessorArchitecture
1059 }
1060 }
1061
1062 fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
1067 let mut max_vers = 0;
1068 let mut max_key = None;
1069 for subkey in key.iter().filter_map(|k| k.ok()) {
1070 let val = subkey
1071 .to_str()
1072 .and_then(|s| s.trim_start_matches('v').replace('.', "").parse().ok());
1073 let val = match val {
1074 Some(s) => s,
1075 None => continue,
1076 };
1077 if val > max_vers {
1078 if let Ok(k) = key.open(&subkey) {
1079 max_vers = val;
1080 max_key = Some((subkey, k));
1081 }
1082 }
1083 }
1084 max_key
1085 }
1086
1087 #[inline(always)]
1088 pub(super) fn has_msbuild_version(version: &str, env_getter: &dyn EnvGetter) -> bool {
1089 match version {
1090 "17.0" => {
1091 find_msbuild_vs17(TargetArch::X64, env_getter).is_some()
1092 || find_msbuild_vs17(TargetArch::X86, env_getter).is_some()
1093 || find_msbuild_vs17(TargetArch::Arm64, env_getter).is_some()
1094 }
1095 "16.0" => {
1096 find_msbuild_vs16(TargetArch::X64, env_getter).is_some()
1097 || find_msbuild_vs16(TargetArch::X86, env_getter).is_some()
1098 || find_msbuild_vs16(TargetArch::Arm64, env_getter).is_some()
1099 }
1100 "15.0" => {
1101 find_msbuild_vs15(TargetArch::X64, env_getter).is_some()
1102 || find_msbuild_vs15(TargetArch::X86, env_getter).is_some()
1103 || find_msbuild_vs15(TargetArch::Arm64, env_getter).is_some()
1104 }
1105 "14.0" => LOCAL_MACHINE
1106 .open(&OsString::from(format!(
1107 "SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}",
1108 version
1109 )))
1110 .is_ok(),
1111 _ => false,
1112 }
1113 }
1114
1115 pub(super) fn find_devenv(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1116 find_devenv_vs15(target, env_getter)
1117 }
1118
1119 fn find_devenv_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1120 find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target, env_getter)
1121 }
1122
1123 pub(super) fn find_msbuild(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1125 if let Some(r) = find_msbuild_vs17(target, env_getter) {
1127 Some(r)
1128 } else if let Some(r) = find_msbuild_vs16(target, env_getter) {
1129 return Some(r);
1130 } else if let Some(r) = find_msbuild_vs15(target, env_getter) {
1131 return Some(r);
1132 } else {
1133 find_old_msbuild(target)
1134 }
1135 }
1136
1137 fn find_msbuild_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1138 find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target, env_getter)
1139 }
1140
1141 fn find_old_msbuild(target: TargetArch) -> Option<Tool> {
1142 let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
1143 LOCAL_MACHINE
1144 .open(key.as_ref())
1145 .ok()
1146 .and_then(|key| {
1147 max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
1148 })
1149 .map(|path| {
1150 let mut path = PathBuf::from(path);
1151 path.push("MSBuild.exe");
1152 let mut tool = Tool::with_family(path, MSVC_FAMILY);
1153 if target == TargetArch::X64 {
1154 tool.env.push(("Platform".into(), "X64".into()));
1155 }
1156 tool
1157 })
1158 }
1159}
1160
1161#[cfg(not(windows))]
1163mod impl_ {
1164 use std::{env, ffi::OsStr};
1165
1166 use super::{EnvGetter, TargetArch, MSVC_FAMILY};
1167 use crate::Tool;
1168
1169 #[inline(always)]
1172 pub(super) fn find_msbuild(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1173 None
1174 }
1175
1176 #[inline(always)]
1179 pub(super) fn find_devenv(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1180 None
1181 }
1182
1183 pub(super) fn find_msvc_environment(
1185 tool: &str,
1186 _target: TargetArch,
1187 env_getter: &dyn EnvGetter,
1188 ) -> Option<Tool> {
1189 let vc_install_dir = env_getter.get_env("VCINSTALLDIR")?;
1191 let vs_install_dir = env_getter.get_env("VSINSTALLDIR")?;
1192
1193 let get_tool = |install_dir: &OsStr| {
1194 env::split_paths(install_dir)
1195 .map(|p| p.join(tool))
1196 .find(|p| p.exists())
1197 .map(|path| Tool::with_family(path, MSVC_FAMILY))
1198 };
1199
1200 get_tool(vc_install_dir.as_ref())
1202 .or_else(|| get_tool(vs_install_dir.as_ref()))
1204 .or_else(|| {
1206 env_getter
1207 .get_env("PATH")
1208 .as_ref()
1209 .map(|path| path.as_ref())
1210 .and_then(get_tool)
1211 })
1212 }
1213
1214 #[inline(always)]
1215 pub(super) fn find_msvc_15plus(
1216 _tool: &str,
1217 _target: TargetArch,
1218 _: &dyn EnvGetter,
1219 ) -> Option<Tool> {
1220 None
1221 }
1222
1223 #[inline(always)]
1226 pub(super) fn find_msvc_14(
1227 _tool: &str,
1228 _target: TargetArch,
1229 _: &dyn EnvGetter,
1230 ) -> Option<Tool> {
1231 None
1232 }
1233
1234 #[inline(always)]
1235 pub(super) fn has_msbuild_version(_version: &str, _: &dyn EnvGetter) -> bool {
1236 false
1237 }
1238}