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;
27
28#[derive(Copy, Clone, PartialEq, Eq)]
30enum TargetArch {
31 X86,
32 X64,
33 Arm,
34 Arm64,
35 Arm64ec,
36}
37impl TargetArch {
38 fn new(arch: &str) -> Option<Self> {
40 match arch {
42 "x64" | "x86_64" => Some(Self::X64),
43 "arm64" | "aarch64" => Some(Self::Arm64),
44 "arm64ec" => Some(Self::Arm64ec),
45 "x86" | "i686" | "i586" => Some(Self::X86),
46 "arm" | "thumbv7a" => Some(Self::Arm),
47 _ => None,
48 }
49 }
50
51 #[cfg(windows)]
52 fn as_vs_arch(&self) -> &'static str {
54 match self {
55 Self::X64 => "x64",
56 Self::Arm64 | Self::Arm64ec => "arm64",
57 Self::X86 => "x86",
58 Self::Arm => "arm",
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
64#[non_exhaustive]
65pub enum Env {
66 Owned(OsString),
67 Arced(Arc<OsStr>),
68}
69
70impl AsRef<OsStr> for Env {
71 fn as_ref(&self) -> &OsStr {
72 self.deref()
73 }
74}
75
76impl Deref for Env {
77 type Target = OsStr;
78
79 fn deref(&self) -> &Self::Target {
80 match self {
81 Env::Owned(os_str) => os_str,
82 Env::Arced(os_str) => os_str,
83 }
84 }
85}
86
87impl From<Env> for PathBuf {
88 fn from(env: Env) -> Self {
89 match env {
90 Env::Owned(os_str) => PathBuf::from(os_str),
91 Env::Arced(os_str) => PathBuf::from(os_str.deref()),
92 }
93 }
94}
95
96pub trait EnvGetter {
97 fn get_env(&self, name: &'static str) -> Option<Env>;
98}
99
100struct StdEnvGetter;
101
102impl EnvGetter for StdEnvGetter {
103 #[allow(clippy::disallowed_methods)]
104 fn get_env(&self, name: &'static str) -> Option<Env> {
105 env::var_os(name).map(Env::Owned)
106 }
107}
108
109pub fn find(arch_or_target: &str, tool: &str) -> Option<Command> {
141 find_tool(arch_or_target, tool).map(|c| c.to_command())
142}
143
144pub fn find_tool(arch_or_target: &str, tool: &str) -> Option<Tool> {
148 let full_arch = if let Some((full_arch, rest)) = arch_or_target.split_once("-") {
149 if !rest.contains("msvc") {
152 return None;
153 }
154 full_arch
155 } else {
156 arch_or_target
157 };
158 find_tool_with_env(full_arch, tool, &StdEnvGetter)
159}
160
161pub fn find_tool_with_env(full_arch: &str, tool: &str, env_getter: &dyn EnvGetter) -> Option<Tool> {
162 let target = TargetArch::new(full_arch)?;
164
165 if tool.contains("msbuild") {
168 return impl_::find_msbuild(target, env_getter);
169 }
170
171 if tool.contains("devenv") {
174 return impl_::find_devenv(target, env_getter);
175 }
176
177 if ["clang", "lldb", "llvm", "ld", "lld"]
180 .iter()
181 .any(|&t| tool.contains(t))
182 {
183 return impl_::find_llvm_tool(tool, target, env_getter);
184 }
185
186 impl_::find_msvc_environment(tool, target, env_getter)
194 .or_else(|| impl_::find_msvc_15plus(tool, target, env_getter))
195 .or_else(|| impl_::find_msvc_14(tool, target, env_getter))
196}
197
198#[derive(Debug, PartialEq, Eq, Copy, Clone)]
200#[non_exhaustive]
201pub enum VsVers {
202 #[deprecated(
204 note = "Visual Studio 12 is no longer supported. cc will never return this value."
205 )]
206 Vs12,
207 Vs14,
209 Vs15,
211 Vs16,
213 Vs17,
215 Vs18,
217}
218
219#[allow(clippy::disallowed_methods)]
224pub fn find_vs_version() -> Result<VsVers, String> {
225 fn has_msbuild_version(version: &str) -> bool {
226 impl_::has_msbuild_version(version, &StdEnvGetter)
227 }
228
229 match std::env::var("VisualStudioVersion") {
230 Ok(version) => match &version[..] {
231 "18.0" => Ok(VsVers::Vs18),
232 "17.0" => Ok(VsVers::Vs17),
233 "16.0" => Ok(VsVers::Vs16),
234 "15.0" => Ok(VsVers::Vs15),
235 "14.0" => Ok(VsVers::Vs14),
236 vers => Err(format!(
237 "\n\n\
238 unsupported or unknown VisualStudio version: {vers}\n\
239 if another version is installed consider running \
240 the appropriate vcvars script before building this \
241 crate\n\
242 "
243 )),
244 },
245 _ => {
246 if has_msbuild_version("18.0") {
249 Ok(VsVers::Vs18)
250 } else if has_msbuild_version("17.0") {
251 Ok(VsVers::Vs17)
252 } else if has_msbuild_version("16.0") {
253 Ok(VsVers::Vs16)
254 } else if has_msbuild_version("15.0") {
255 Ok(VsVers::Vs15)
256 } else if has_msbuild_version("14.0") {
257 Ok(VsVers::Vs14)
258 } else {
259 Err("\n\n\
260 couldn't determine visual studio generator\n\
261 if VisualStudio is installed, however, consider \
262 running the appropriate vcvars script before building \
263 this crate\n\
264 "
265 .to_string())
266 }
267 }
268 }
269}
270
271pub fn get_ucrt_dir() -> Option<(PathBuf, String)> {
278 impl_::get_ucrt_dir()
279}
280
281#[cfg(windows)]
283mod impl_ {
284 use crate::com;
285 use crate::registry::{RegistryKey, LOCAL_MACHINE};
286 use crate::setup_config::SetupConfiguration;
287 use crate::vs_instances::{VsInstances, VswhereInstance};
288 use crate::windows_sys::{
289 GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE,
290 IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK,
291 };
292 use std::convert::TryFrom;
293 use std::env;
294 use std::ffi::OsString;
295 use std::fs::File;
296 use std::io::Read;
297 use std::iter;
298 use std::mem;
299 use std::path::{Path, PathBuf};
300 use std::process::Command;
301 use std::str::FromStr;
302 use std::sync::atomic::{AtomicBool, Ordering};
303 use std::sync::Once;
304
305 use super::{EnvGetter, TargetArch};
306 use crate::Tool;
307
308 struct MsvcTool {
309 tool: PathBuf,
310 libs: Vec<PathBuf>,
311 path: Vec<PathBuf>,
312 include: Vec<PathBuf>,
313 }
314
315 #[derive(Default)]
316 struct SdkInfo {
317 libs: Vec<PathBuf>,
318 path: Vec<PathBuf>,
319 include: Vec<PathBuf>,
320 }
321
322 struct LibraryHandle(HMODULE);
323
324 impl LibraryHandle {
325 fn new(name: &[u8]) -> Option<Self> {
326 let handle = unsafe { LoadLibraryA(name.as_ptr() as _) };
327 (!handle.is_null()).then_some(Self(handle))
328 }
329
330 unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> {
339 let symbol = GetProcAddress(self.0, name.as_ptr() as _);
340 symbol.map(|symbol| mem::transmute_copy(&symbol))
341 }
342 }
343
344 type GetMachineTypeAttributesFuncType =
345 unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32;
346 const _: () = {
347 let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes;
351 };
352
353 fn is_amd64_emulation_supported_inner() -> Option<bool> {
354 let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?;
356 let get_machine_type_attributes = unsafe {
358 kernel32
359 .get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0")
360 }?;
361 let mut attributes = Default::default();
362 if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK
363 {
364 Some((attributes & UserEnabled) != 0)
365 } else {
366 Some(false)
367 }
368 }
369
370 fn is_amd64_emulation_supported() -> bool {
371 static LOAD_VALUE: Once = Once::new();
373 static IS_SUPPORTED: AtomicBool = AtomicBool::new(false);
374
375 LOAD_VALUE.call_once(|| {
377 IS_SUPPORTED.store(
378 is_amd64_emulation_supported_inner().unwrap_or(false),
379 Ordering::Relaxed,
380 );
381 });
382 IS_SUPPORTED.load(Ordering::Relaxed)
383 }
384
385 impl MsvcTool {
386 fn new(tool: PathBuf) -> MsvcTool {
387 MsvcTool {
388 tool,
389 libs: Vec::new(),
390 path: Vec::new(),
391 include: Vec::new(),
392 }
393 }
394
395 fn add_sdk(&mut self, sdk_info: SdkInfo) {
396 self.libs.extend(sdk_info.libs);
397 self.path.extend(sdk_info.path);
398 self.include.extend(sdk_info.include);
399 }
400
401 fn into_tool(self, env_getter: &dyn EnvGetter) -> Tool {
402 let MsvcTool {
403 tool,
404 libs,
405 path,
406 include,
407 } = self;
408 let mut tool = Tool {
409 tool,
410 is_clang_cl: false,
411 env: Vec::new(),
412 };
413 add_env(&mut tool, "LIB", libs, env_getter);
414 add_env(&mut tool, "PATH", path, env_getter);
415 add_env(&mut tool, "INCLUDE", include, env_getter);
416 tool
417 }
418 }
419
420 impl SdkInfo {
421 fn find_tool(&self, tool: &str) -> Option<PathBuf> {
422 self.path.iter().map(|p| p.join(tool)).find(|p| p.exists())
423 }
424 }
425
426 fn is_vscmd_target(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
429 is_vscmd_target_env(target, env_getter).or_else(|| is_vscmd_target_cl(target, env_getter))
430 }
431
432 fn is_vscmd_target_env(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
435 let vscmd_arch = env_getter.get_env("VSCMD_ARG_TGT_ARCH")?;
436 Some(target.as_vs_arch() == vscmd_arch.as_ref())
437 }
438
439 fn is_vscmd_target_cl(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
442 let cmd_target = vscmd_target_cl(env_getter)?;
443 Some(target.as_vs_arch() == cmd_target)
444 }
445
446 fn vscmd_target_cl(env_getter: &dyn EnvGetter) -> Option<&'static str> {
449 let cl_exe = env_getter.get_env("PATH").and_then(|path| {
450 env::split_paths(&path)
451 .map(|p| p.join("cl.exe"))
452 .find(|p| p.exists())
453 })?;
454 let mut cl = Command::new(cl_exe);
455 cl.stderr(std::process::Stdio::piped())
456 .stdout(std::process::Stdio::null());
457
458 let out = cl.output().ok()?;
459 let cl_arch = out
460 .stderr
461 .split(|&b| b == b'\n' || b == b'\r')
462 .next()?
463 .rsplit(|&b| b == b' ')
464 .next()?;
465
466 match cl_arch {
467 b"x64" => Some("x64"),
468 b"x86" => Some("x86"),
469 b"ARM64" => Some("arm64"),
470 b"ARM" => Some("arm"),
471 _ => None,
472 }
473 }
474
475 pub(super) fn find_msvc_environment(
477 tool: &str,
478 target: TargetArch,
479 env_getter: &dyn EnvGetter,
480 ) -> Option<Tool> {
481 if env_getter.get_env("VCINSTALLDIR").is_none()
486 && env_getter.get_env("VSTEL_MSBuildProjectFullPath").is_none()
487 {
488 return None;
489 }
490
491 if is_vscmd_target(target, env_getter) == Some(false) {
494 let vs_install_dir: PathBuf = env_getter.get_env("VSINSTALLDIR")?.into();
496 tool_from_vs15plus_instance(tool, target, &vs_install_dir, env_getter)
497 } else {
498 env_getter
500 .get_env("PATH")
501 .and_then(|path| {
502 env::split_paths(&path)
503 .map(|p| p.join(tool))
504 .find(|p| p.exists())
505 })
506 .map(|path| Tool {
507 tool: path,
508 is_clang_cl: false,
509 env: Vec::new(),
510 })
511 }
512 }
513
514 fn find_msbuild_vs18(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
515 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "18", env_getter)
516 }
517
518 fn find_msbuild_vs17(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
519 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17", env_getter)
520 }
521
522 #[allow(bare_trait_objects)]
523 fn vs16plus_instances(
524 target: TargetArch,
525 version: &'static str,
526 env_getter: &dyn EnvGetter,
527 ) -> Box<Iterator<Item = PathBuf>> {
528 let instances = if let Some(instances) = vs15plus_instances(target, env_getter) {
529 instances
530 } else {
531 return Box::new(iter::empty());
532 };
533 Box::new(instances.into_iter().filter_map(move |instance| {
534 let installation_name = instance.installation_name()?;
535 if installation_name.starts_with(&format!("VisualStudio/{}.", version))
536 || installation_name.starts_with(&format!("VisualStudioPreview/{}.", version))
537 {
538 Some(instance.installation_path()?)
539 } else {
540 None
541 }
542 }))
543 }
544
545 fn find_tool_in_vs16plus_path(
546 tool: &str,
547 target: TargetArch,
548 version: &'static str,
549 env_getter: &dyn EnvGetter,
550 ) -> Option<Tool> {
551 vs16plus_instances(target, version, env_getter)
552 .filter_map(|path| {
553 let path = path.join(tool);
554 if !path.is_file() {
555 return None;
556 }
557 let mut tool = Tool {
558 tool: path,
559 is_clang_cl: false,
560 env: Vec::new(),
561 };
562 if target == TargetArch::X64 {
563 tool.env.push(("Platform".into(), "X64".into()));
564 }
565 if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
566 tool.env.push(("Platform".into(), "ARM64".into()));
567 }
568 Some(tool)
569 })
570 .next()
571 }
572
573 fn find_msbuild_vs16(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
574 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16", env_getter)
575 }
576
577 pub(super) fn find_llvm_tool(
578 tool: &str,
579 target: TargetArch,
580 env_getter: &dyn EnvGetter,
581 ) -> Option<Tool> {
582 find_llvm_tool_vs17plus(tool, target, env_getter, "18")
583 .or_else(|| find_llvm_tool_vs17plus(tool, target, env_getter, "17"))
584 }
585
586 fn find_llvm_tool_vs17plus(
587 tool: &str,
588 target: TargetArch,
589 env_getter: &dyn EnvGetter,
590 version: &'static str,
591 ) -> Option<Tool> {
592 vs16plus_instances(target, version, env_getter)
593 .filter_map(|mut base_path| {
594 base_path.push(r"VC\Tools\LLVM");
595 let host_folder = match host_arch() {
596 X86 => "",
599 X86_64 => "x64",
600 AARCH64 => "ARM64",
601 _ => return None,
602 };
603 if host_folder != "" {
604 base_path.push(host_folder);
606 }
607 base_path.push("bin");
609 base_path.push(tool);
610 let is_clang_cl = tool.contains("clang-cl");
611 base_path.is_file().then(|| Tool {
612 tool: base_path,
613 is_clang_cl,
614 env: Vec::new(),
615 })
616 })
617 .next()
618 }
619
620 fn vs15plus_instances(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<VsInstances> {
633 vs15plus_instances_using_com()
634 .or_else(|| vs15plus_instances_using_vswhere(target, env_getter))
635 }
636
637 fn vs15plus_instances_using_com() -> Option<VsInstances> {
638 com::initialize().ok()?;
639
640 let config = SetupConfiguration::new().ok()?;
641 let enum_setup_instances = config.enum_all_instances().ok()?;
642
643 Some(VsInstances::ComBased(enum_setup_instances))
644 }
645
646 fn vs15plus_instances_using_vswhere(
647 target: TargetArch,
648 env_getter: &dyn EnvGetter,
649 ) -> Option<VsInstances> {
650 let program_files_path = env_getter
651 .get_env("ProgramFiles(x86)")
652 .or_else(|| env_getter.get_env("ProgramFiles"))?;
653
654 let program_files_path = Path::new(program_files_path.as_ref());
655
656 let vswhere_path =
657 program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe");
658
659 if !vswhere_path.exists() {
660 return None;
661 }
662
663 let tools_arch = match target {
664 TargetArch::X86 | TargetArch::X64 => Some("x86.x64"),
665 TargetArch::Arm => Some("ARM"),
666 TargetArch::Arm64 | TargetArch::Arm64ec => Some("ARM64"),
667 };
668
669 let vswhere_output = Command::new(vswhere_path)
670 .args([
671 "-latest",
672 "-products",
673 "*",
674 "-requires",
675 &format!("Microsoft.VisualStudio.Component.VC.Tools.{}", tools_arch?),
676 "-format",
677 "text",
678 "-nologo",
679 ])
680 .stderr(std::process::Stdio::inherit())
681 .output()
682 .ok()?;
683
684 let vs_instances =
685 VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?);
686
687 Some(vs_instances)
688 }
689
690 fn parse_version(version: &str) -> Option<[u16; 4]> {
693 let mut iter = version.split('.').map(u16::from_str).fuse();
694 let mut get_next_number = move || match iter.next() {
695 Some(Ok(version_part)) => Some(version_part),
696 Some(Err(_)) => None,
697 None => Some(0),
698 };
699 Some([
700 get_next_number()?,
701 get_next_number()?,
702 get_next_number()?,
703 get_next_number()?,
704 ])
705 }
706
707 pub(super) fn find_msvc_15plus(
708 tool: &str,
709 target: TargetArch,
710 env_getter: &dyn EnvGetter,
711 ) -> Option<Tool> {
712 let iter = vs15plus_instances(target, env_getter)?;
713 iter.into_iter()
714 .filter_map(|instance| {
715 let version = parse_version(&instance.installation_version()?)?;
716 let instance_path = instance.installation_path()?;
717 let tool = tool_from_vs15plus_instance(tool, target, &instance_path, env_getter)?;
718 Some((version, tool))
719 })
720 .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version))
721 .map(|(_version, tool)| tool)
722 }
723
724 fn find_tool_in_vs15_path(
732 tool: &str,
733 target: TargetArch,
734 env_getter: &dyn EnvGetter,
735 ) -> Option<Tool> {
736 let mut path = match vs15plus_instances(target, env_getter) {
737 Some(instances) => instances
738 .into_iter()
739 .filter_map(|instance| instance.installation_path())
740 .map(|path| path.join(tool))
741 .find(|path| path.is_file()),
742 None => None,
743 };
744
745 if path.is_none() {
746 let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7";
747 path = LOCAL_MACHINE
748 .open(key.as_ref())
749 .ok()
750 .and_then(|key| key.query_str("15.0").ok())
751 .map(|path| PathBuf::from(path).join(tool))
752 .and_then(|path| if path.is_file() { Some(path) } else { None });
753 }
754
755 path.map(|path| {
756 let mut tool = Tool {
757 tool: path,
758 is_clang_cl: false,
759 env: Vec::new(),
760 };
761 if target == TargetArch::X64 {
762 tool.env.push(("Platform".into(), "X64".into()));
763 } else if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
764 tool.env.push(("Platform".into(), "ARM64".into()));
765 }
766 tool
767 })
768 }
769
770 fn tool_from_vs15plus_instance(
771 tool: &str,
772 target: TargetArch,
773 instance_path: &Path,
774 env_getter: &dyn EnvGetter,
775 ) -> Option<Tool> {
776 let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) =
777 vs15plus_vc_paths(target, instance_path, env_getter)?;
778 let sdk_info = get_sdks(target, env_getter)?;
779 let mut tool_path = bin_path.join(tool);
780 if !tool_path.exists() {
781 tool_path = sdk_info.find_tool(tool)?;
782 };
783
784 let mut tool = MsvcTool::new(tool_path);
785 tool.path.push(bin_path.clone());
786 tool.path.push(host_dylib_path);
787 if let Some(alt_lib_path) = alt_lib_path {
788 tool.libs.push(alt_lib_path);
789 }
790 tool.libs.push(lib_path);
791 tool.include.push(include_path);
792
793 if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &root_path) {
794 tool.libs.push(atl_lib_path);
795 tool.include.push(atl_include_path);
796 }
797
798 tool.add_sdk(sdk_info);
799
800 Some(tool.into_tool(env_getter))
801 }
802
803 fn vs15plus_vc_paths(
804 target_arch: TargetArch,
805 instance_path: &Path,
806 env_getter: &dyn EnvGetter,
807 ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> {
808 let version = vs15plus_vc_read_version(instance_path, env_getter)?;
809
810 let hosts = match host_arch() {
811 X86 => &["X86"],
812 X86_64 => &["X64"],
813 AARCH64 => {
818 if is_amd64_emulation_supported() {
819 &["ARM64", "X64", "X86"][..]
820 } else {
821 &["ARM64", "X86"]
822 }
823 }
824 _ => return None,
825 };
826 let target_dir = target_arch.as_vs_arch();
827 let path = instance_path.join(r"VC\Tools\MSVC").join(version);
829 let (host_path, host) = hosts.iter().find_map(|&x| {
831 let candidate = path.join("bin").join(format!("Host{}", x));
832 if candidate.join(target_dir).exists() {
833 Some((candidate, x))
834 } else {
835 None
836 }
837 })?;
838 let bin_path = host_path.join(target_dir);
841 let host_dylib_path = host_path.join(host.to_lowercase());
845 let lib_fragment = if use_spectre_mitigated_libs(env_getter) {
846 r"lib\spectre"
847 } else {
848 "lib"
849 };
850 let lib_path = path.join(lib_fragment).join(target_dir);
851 let alt_lib_path =
852 (target_arch == TargetArch::Arm64ec).then(|| path.join(lib_fragment).join("arm64ec"));
853 let include_path = path.join("include");
854 Some((
855 path,
856 bin_path,
857 host_dylib_path,
858 lib_path,
859 alt_lib_path,
860 include_path,
861 ))
862 }
863
864 fn vs15plus_vc_read_version(dir: &Path, env_getter: &dyn EnvGetter) -> Option<String> {
865 if let Some(version) = env_getter.get_env("VCToolsVersion") {
866 return version.to_str().map(ToString::to_string);
869 }
870
871 let mut version_path: PathBuf =
873 dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
874 let mut version_file = if let Ok(f) = File::open(&version_path) {
875 f
876 } else {
877 let mut version_file = String::new();
882 version_path.pop();
883 for file in version_path.read_dir().ok()? {
884 let name = file.ok()?.file_name();
885 let name = name.to_str()?;
886 if name.starts_with("Microsoft.VCToolsVersion.v")
887 && name.ends_with(".default.txt")
888 && name > &version_file
889 {
890 version_file.replace_range(.., name);
891 }
892 }
893 if version_file.is_empty() {
894 let tools_dir: PathBuf = dir.join(r"VC\Tools\MSVC");
896 return tools_dir
897 .read_dir()
898 .ok()?
899 .filter_map(|file| {
900 let file = file.ok()?;
901 let name = file.file_name().into_string().ok()?;
902
903 file.path().join("bin").exists().then(|| {
904 let version = parse_version(&name);
905 (name, version)
906 })
907 })
908 .max_by(|(_, a), (_, b)| a.cmp(b))
909 .map(|(version, _)| version);
910 }
911 version_path.push(version_file);
912 File::open(version_path).ok()?
913 };
914
915 let mut version = String::new();
917 version_file.read_to_string(&mut version).ok()?;
918 version.truncate(version.trim_end().len());
919 Some(version)
920 }
921
922 fn use_spectre_mitigated_libs(env_getter: &dyn EnvGetter) -> bool {
923 env_getter
924 .get_env("VSCMD_ARG_VCVARS_SPECTRE")
925 .map(|env| env.as_ref() == "spectre")
926 .unwrap_or_default()
927 }
928
929 fn atl_paths(target: TargetArch, path: &Path) -> Option<(PathBuf, PathBuf)> {
930 let atl_path = path.join("atlmfc");
931 let sub = target.as_vs_arch();
932 if atl_path.exists() {
933 Some((atl_path.join("lib").join(sub), atl_path.join("include")))
934 } else {
935 None
936 }
937 }
938
939 pub(super) fn find_msvc_14(
942 tool: &str,
943 target: TargetArch,
944 env_getter: &dyn EnvGetter,
945 ) -> Option<Tool> {
946 if env_getter.get_env("VCToolsVersion").is_some() {
947 return None;
949 }
950
951 let vcdir = get_vc_dir("14.0")?;
952 let sdk_info = get_sdks(target, env_getter)?;
953 let mut tool = get_tool(tool, &vcdir, target, &sdk_info)?;
954 tool.add_sdk(sdk_info);
955 Some(tool.into_tool(env_getter))
956 }
957
958 fn get_sdks(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<SdkInfo> {
959 let sub = target.as_vs_arch();
960 let (ucrt, ucrt_version) = get_ucrt_dir()?;
961
962 let host = match host_arch() {
963 X86 => "x86",
964 X86_64 => "x64",
965 AARCH64 => "arm64",
966 _ => return None,
967 };
968
969 let mut info = SdkInfo::default();
970
971 info.path
972 .push(ucrt.join("bin").join(&ucrt_version).join(host));
973
974 let ucrt_include = ucrt.join("include").join(&ucrt_version);
975 info.include.push(ucrt_include.join("ucrt"));
976
977 let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
978 info.libs.push(ucrt_lib.join("ucrt").join(sub));
979
980 if let Some((sdk, version)) = get_sdk10_dir(env_getter) {
981 info.path.push(sdk.join("bin").join(host));
982 let sdk_lib = sdk.join("lib").join(&version);
983 info.libs.push(sdk_lib.join("um").join(sub));
984 let sdk_include = sdk.join("include").join(&version);
985 info.include.push(sdk_include.join("um"));
986 info.include.push(sdk_include.join("cppwinrt"));
987 info.include.push(sdk_include.join("winrt"));
988 info.include.push(sdk_include.join("shared"));
989 } else if let Some(sdk) = get_sdk81_dir() {
990 info.path.push(sdk.join("bin").join(host));
991 let sdk_lib = sdk.join("lib").join("winv6.3");
992 info.libs.push(sdk_lib.join("um").join(sub));
993 let sdk_include = sdk.join("include");
994 info.include.push(sdk_include.join("um"));
995 info.include.push(sdk_include.join("winrt"));
996 info.include.push(sdk_include.join("shared"));
997 }
998
999 Some(info)
1000 }
1001
1002 fn add_env(
1003 tool: &mut Tool,
1004 env: &'static str,
1005 paths: Vec<PathBuf>,
1006 env_getter: &dyn EnvGetter,
1007 ) {
1008 let prev = env_getter.get_env(env);
1009 let prev = prev.as_ref().map(AsRef::as_ref).unwrap_or_default();
1010 let prev = env::split_paths(&prev);
1011 let new = paths.into_iter().chain(prev);
1012 tool.env
1013 .push((env.to_string().into(), env::join_paths(new).unwrap()));
1014 }
1015
1016 fn get_tool(
1019 tool: &str,
1020 path: &Path,
1021 target: TargetArch,
1022 sdk_info: &SdkInfo,
1023 ) -> Option<MsvcTool> {
1024 bin_subdir(target)
1025 .into_iter()
1026 .map(|(sub, host)| {
1027 (
1028 path.join("bin").join(sub).join(tool),
1029 Some(path.join("bin").join(host)),
1030 )
1031 })
1032 .filter(|(path, _)| path.is_file())
1033 .chain(iter::once_with(|| Some((sdk_info.find_tool(tool)?, None))).flatten())
1034 .map(|(tool_path, host)| {
1035 let mut tool = MsvcTool::new(tool_path);
1036 tool.path.extend(host);
1037 let sub = vc_lib_subdir(target);
1038 tool.libs.push(path.join("lib").join(sub));
1039 tool.include.push(path.join("include"));
1040 let atlmfc_path = path.join("atlmfc");
1041 if atlmfc_path.exists() {
1042 tool.libs.push(atlmfc_path.join("lib").join(sub));
1043 tool.include.push(atlmfc_path.join("include"));
1044 }
1045 tool
1046 })
1047 .next()
1048 }
1049
1050 fn get_vc_dir(ver: &str) -> Option<PathBuf> {
1053 let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
1054 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1055 let path = key.query_str(ver).ok()?;
1056 Some(path.into())
1057 }
1058
1059 pub(super) fn get_ucrt_dir() -> Option<(PathBuf, String)> {
1066 let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
1067 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1068 let root = key.query_str("KitsRoot10").ok()?;
1069 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
1070 let max_libdir = readdir
1071 .filter_map(|dir| dir.ok())
1072 .map(|dir| dir.path())
1073 .filter(|dir| {
1074 dir.components()
1075 .last()
1076 .and_then(|c| c.as_os_str().to_str())
1077 .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
1078 .unwrap_or(false)
1079 })
1080 .max()?;
1081 let version = max_libdir.components().last().unwrap();
1082 let version = version.as_os_str().to_str().unwrap().to_string();
1083 Some((root.into(), version))
1084 }
1085
1086 fn get_sdk10_dir(env_getter: &dyn EnvGetter) -> Option<(PathBuf, String)> {
1098 if let (Some(root), Some(version)) = (
1099 env_getter.get_env("WindowsSdkDir"),
1100 env_getter
1101 .get_env("WindowsSDKVersion")
1102 .as_ref()
1103 .and_then(|version| version.as_ref().to_str()),
1104 ) {
1105 return Some((
1106 PathBuf::from(root),
1107 version.trim_end_matches('\\').to_string(),
1108 ));
1109 }
1110
1111 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
1112 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1113 let root = key.query_str("InstallationFolder").ok()?;
1114 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
1115 let mut dirs = readdir
1116 .filter_map(|dir| dir.ok())
1117 .map(|dir| dir.path())
1118 .collect::<Vec<_>>();
1119 dirs.sort();
1120 let dir = dirs
1121 .into_iter()
1122 .rev()
1123 .find(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())?;
1124 let version = dir.components().last().unwrap();
1125 let version = version.as_os_str().to_str().unwrap().to_string();
1126 Some((root.into(), version))
1127 }
1128
1129 fn get_sdk81_dir() -> Option<PathBuf> {
1134 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
1135 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1136 let root = key.query_str("InstallationFolder").ok()?;
1137 Some(root.into())
1138 }
1139
1140 const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
1141 const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
1142 const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12;
1143 const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
1144 const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
1145 const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64;
1146
1147 fn bin_subdir(target: TargetArch) -> Vec<(&'static str, &'static str)> {
1160 match (target, host_arch()) {
1161 (TargetArch::X86, X86) => vec![("", "")],
1162 (TargetArch::X86, X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
1163 (TargetArch::X64, X86) => vec![("x86_amd64", "")],
1164 (TargetArch::X64, X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
1165 (TargetArch::Arm, X86) => vec![("x86_arm", "")],
1166 (TargetArch::Arm, X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
1167 _ => vec![],
1168 }
1169 }
1170
1171 fn vc_lib_subdir(target: TargetArch) -> &'static str {
1173 match target {
1174 TargetArch::X86 => "",
1175 TargetArch::X64 => "amd64",
1176 TargetArch::Arm => "arm",
1177 TargetArch::Arm64 | TargetArch::Arm64ec => "arm64",
1178 }
1179 }
1180
1181 #[allow(bad_style)]
1182 fn host_arch() -> u16 {
1183 type DWORD = u32;
1184 type WORD = u16;
1185 type LPVOID = *mut u8;
1186 type DWORD_PTR = usize;
1187
1188 #[repr(C)]
1189 struct SYSTEM_INFO {
1190 wProcessorArchitecture: WORD,
1191 _wReserved: WORD,
1192 _dwPageSize: DWORD,
1193 _lpMinimumApplicationAddress: LPVOID,
1194 _lpMaximumApplicationAddress: LPVOID,
1195 _dwActiveProcessorMask: DWORD_PTR,
1196 _dwNumberOfProcessors: DWORD,
1197 _dwProcessorType: DWORD,
1198 _dwAllocationGranularity: DWORD,
1199 _wProcessorLevel: WORD,
1200 _wProcessorRevision: WORD,
1201 }
1202
1203 extern "system" {
1204 fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
1205 }
1206
1207 unsafe {
1208 let mut info = mem::zeroed();
1209 GetNativeSystemInfo(&mut info);
1210 info.wProcessorArchitecture
1211 }
1212 }
1213
1214 #[cfg(test)]
1215 mod tests {
1216 use super::*;
1217 use std::path::Path;
1218 use crate::find_tools::find;
1220
1221 fn host_arch_to_string(host_arch_value: u16) -> &'static str {
1222 match host_arch_value {
1223 X86 => "x86",
1224 X86_64 => "x64",
1225 AARCH64 => "arm64",
1226 _ => panic!("Unsupported host architecture: {}", host_arch_value),
1227 }
1228 }
1229
1230 #[test]
1231 fn test_find_cl_exe() {
1232 let target_architectures = ["x64", "x86", "arm64"];
1237 let mut found_any = false;
1238
1239 let host_arch_value = host_arch();
1241 let host_name = host_arch_to_string(host_arch_value);
1242
1243 for &target_arch in &target_architectures {
1244 if let Some(cmd) = find(target_arch, "cl.exe") {
1245 assert!(
1247 !cmd.get_program().is_empty(),
1248 "cl.exe program path should not be empty"
1249 );
1250 assert!(
1251 Path::new(cmd.get_program()).exists(),
1252 "cl.exe should exist at: {:?}",
1253 cmd.get_program()
1254 );
1255
1256 let path_str = cmd.get_program().to_string_lossy();
1259 let path_str_lower = path_str.to_lowercase();
1260 let expected_host_target_path =
1261 format!("\\bin\\host{host_name}\\{target_arch}");
1262 let expected_host_target_path_unix =
1263 expected_host_target_path.replace("\\", "/");
1264
1265 assert!(
1266 path_str_lower.contains(&expected_host_target_path) || path_str_lower.contains(&expected_host_target_path_unix),
1267 "cl.exe path should contain host-target combination (case-insensitive) '{}' for {} host targeting {}, but found: {}",
1268 expected_host_target_path,
1269 host_name,
1270 target_arch,
1271 path_str
1272 );
1273
1274 found_any = true;
1275 }
1276 }
1277
1278 assert!(found_any, "Expected to find cl.exe for at least one target architecture (x64, x86, or arm64) on Windows CI with Visual Studio installed");
1279 }
1280
1281 #[test]
1282 #[cfg(not(disable_clang_cl_tests))]
1283 fn test_find_llvm_tools() {
1284 use crate::find_tools::StdEnvGetter;
1286
1287 let target_arch = TargetArch::new("x64").expect("Should support x64 architecture");
1291 let llvm_tools = ["clang.exe", "clang++.exe", "lld.exe", "llvm-ar.exe"];
1292
1293 let host_arch_value = host_arch();
1295 let expected_host_path = match host_arch_value {
1296 X86 => "LLVM\\bin", X86_64 => "LLVM\\x64\\bin", AARCH64 => "LLVM\\ARM64\\bin", _ => panic!("Unsupported host architecture: {}", host_arch_value),
1300 };
1301
1302 let host_name = host_arch_to_string(host_arch_value);
1303
1304 let mut found_tools_count = 0;
1305
1306 for &tool in &llvm_tools {
1307 let env_getter = StdEnvGetter;
1309 let result = find_llvm_tool(tool, target_arch, &env_getter);
1310
1311 match result {
1312 Some(found_tool) => {
1313 found_tools_count += 1;
1314
1315 assert!(
1317 !found_tool.path().as_os_str().is_empty(),
1318 "Found LLVM tool '{}' should have a non-empty path",
1319 tool
1320 );
1321
1322 assert!(
1324 found_tool.path().exists(),
1325 "LLVM tool '{}' path should exist: {:?}",
1326 tool,
1327 found_tool.path()
1328 );
1329
1330 let path_str = found_tool.path().to_string_lossy();
1332 assert!(
1333 path_str.contains(tool.trim_end_matches(".exe")),
1334 "Tool path '{}' should contain tool name '{}'",
1335 path_str,
1336 tool
1337 );
1338
1339 assert!(
1341 path_str.contains(expected_host_path) || path_str.contains(&expected_host_path.replace("\\", "/")),
1342 "LLVM tool should be in host-specific VS LLVM directory '{}' for {} host, but found: {}",
1343 expected_host_path,
1344 host_name,
1345 path_str
1346 );
1347 }
1348 None => {}
1349 }
1350 }
1351
1352 assert!(
1354 found_tools_count > 0,
1355 "Expected to find at least one LLVM tool on CI with Visual Studio + Clang installed for {} host. Found: {}",
1356 host_name,
1357 found_tools_count
1358 );
1359 }
1360 }
1361
1362 fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
1367 let mut max_vers = 0;
1368 let mut max_key = None;
1369 for subkey in key.iter().filter_map(|k| k.ok()) {
1370 let val = subkey
1371 .to_str()
1372 .and_then(|s| s.trim_start_matches('v').replace('.', "").parse().ok());
1373 let val = match val {
1374 Some(s) => s,
1375 None => continue,
1376 };
1377 if val > max_vers {
1378 if let Ok(k) = key.open(&subkey) {
1379 max_vers = val;
1380 max_key = Some((subkey, k));
1381 }
1382 }
1383 }
1384 max_key
1385 }
1386
1387 #[inline(always)]
1388 pub(super) fn has_msbuild_version(version: &str, env_getter: &dyn EnvGetter) -> bool {
1389 match version {
1390 "18.0" => {
1391 find_msbuild_vs18(TargetArch::X64, env_getter).is_some()
1392 || find_msbuild_vs18(TargetArch::X86, env_getter).is_some()
1393 || find_msbuild_vs18(TargetArch::Arm64, env_getter).is_some()
1394 }
1395 "17.0" => {
1396 find_msbuild_vs17(TargetArch::X64, env_getter).is_some()
1397 || find_msbuild_vs17(TargetArch::X86, env_getter).is_some()
1398 || find_msbuild_vs17(TargetArch::Arm64, env_getter).is_some()
1399 }
1400 "16.0" => {
1401 find_msbuild_vs16(TargetArch::X64, env_getter).is_some()
1402 || find_msbuild_vs16(TargetArch::X86, env_getter).is_some()
1403 || find_msbuild_vs16(TargetArch::Arm64, env_getter).is_some()
1404 }
1405 "15.0" => {
1406 find_msbuild_vs15(TargetArch::X64, env_getter).is_some()
1407 || find_msbuild_vs15(TargetArch::X86, env_getter).is_some()
1408 || find_msbuild_vs15(TargetArch::Arm64, env_getter).is_some()
1409 }
1410 "14.0" => LOCAL_MACHINE
1411 .open(&OsString::from(format!(
1412 "SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}",
1413 version
1414 )))
1415 .is_ok(),
1416 _ => false,
1417 }
1418 }
1419
1420 pub(super) fn find_devenv(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1421 find_devenv_vs15(target, env_getter)
1422 }
1423
1424 fn find_devenv_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1425 find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target, env_getter)
1426 }
1427
1428 pub(super) fn find_msbuild(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1430 if let Some(r) = find_msbuild_vs18(target, env_getter) {
1432 Some(r)
1433 } else if let Some(r) = find_msbuild_vs17(target, env_getter) {
1434 Some(r)
1435 } else if let Some(r) = find_msbuild_vs16(target, env_getter) {
1436 return Some(r);
1437 } else if let Some(r) = find_msbuild_vs15(target, env_getter) {
1438 return Some(r);
1439 } else {
1440 find_old_msbuild(target)
1441 }
1442 }
1443
1444 fn find_msbuild_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1445 find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target, env_getter)
1446 }
1447
1448 fn find_old_msbuild(target: TargetArch) -> Option<Tool> {
1449 let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
1450 LOCAL_MACHINE
1451 .open(key.as_ref())
1452 .ok()
1453 .and_then(|key| {
1454 max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
1455 })
1456 .map(|path| {
1457 let mut path = PathBuf::from(path);
1458 path.push("MSBuild.exe");
1459 let mut tool = Tool {
1460 tool: path,
1461 is_clang_cl: false,
1462 env: Vec::new(),
1463 };
1464 if target == TargetArch::X64 {
1465 tool.env.push(("Platform".into(), "X64".into()));
1466 }
1467 tool
1468 })
1469 }
1470}
1471
1472#[cfg(not(windows))]
1474mod impl_ {
1475 use std::{env, ffi::OsStr, path::PathBuf};
1476
1477 use super::{EnvGetter, TargetArch};
1478 use crate::Tool;
1479
1480 #[inline(always)]
1483 pub(super) fn find_msbuild(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1484 None
1485 }
1486
1487 #[inline(always)]
1490 pub(super) fn find_devenv(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1491 None
1492 }
1493
1494 #[inline(always)]
1496 pub(super) fn find_llvm_tool(
1497 _tool: &str,
1498 _target: TargetArch,
1499 _: &dyn EnvGetter,
1500 ) -> Option<Tool> {
1501 None
1502 }
1503
1504 pub(super) fn find_msvc_environment(
1506 tool: &str,
1507 _target: TargetArch,
1508 env_getter: &dyn EnvGetter,
1509 ) -> Option<Tool> {
1510 let vc_install_dir = env_getter.get_env("VCINSTALLDIR")?;
1512 let vs_install_dir = env_getter.get_env("VSINSTALLDIR")?;
1513
1514 let get_tool = |install_dir: &OsStr| {
1515 env::split_paths(install_dir)
1516 .map(|p| p.join(tool))
1517 .find(|p| p.exists())
1518 .map(|path| Tool {
1519 tool: path,
1520 is_clang_cl: false,
1521 env: Vec::new(),
1522 })
1523 };
1524
1525 get_tool(vc_install_dir.as_ref())
1527 .or_else(|| get_tool(vs_install_dir.as_ref()))
1529 .or_else(|| {
1531 env_getter
1532 .get_env("PATH")
1533 .as_ref()
1534 .map(|path| path.as_ref())
1535 .and_then(get_tool)
1536 })
1537 }
1538
1539 #[inline(always)]
1540 pub(super) fn find_msvc_15plus(
1541 _tool: &str,
1542 _target: TargetArch,
1543 _: &dyn EnvGetter,
1544 ) -> Option<Tool> {
1545 None
1546 }
1547
1548 #[inline(always)]
1551 pub(super) fn find_msvc_14(
1552 _tool: &str,
1553 _target: TargetArch,
1554 _: &dyn EnvGetter,
1555 ) -> Option<Tool> {
1556 None
1557 }
1558
1559 #[inline(always)]
1560 pub(super) fn has_msbuild_version(_version: &str, _: &dyn EnvGetter) -> bool {
1561 false
1562 }
1563
1564 #[inline(always)]
1565 pub(super) fn get_ucrt_dir() -> Option<(PathBuf, String)> {
1566 None
1567 }
1568}