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