1#![allow(missing_docs)]
16#![allow(clippy::upper_case_acronyms)]
17
18use std::{
19 env,
20 ffi::{OsStr, OsString},
21 ops::Deref,
22 path::{Path, PathBuf},
23 process::Command,
24 sync::Arc,
25};
26
27use crate::Tool;
28
29#[derive(Copy, Clone, PartialEq, Eq)]
31enum TargetArch {
32 X86,
33 X64,
34 Arm,
35 Arm64,
36 Arm64ec,
37}
38impl TargetArch {
39 fn new(arch: &str) -> Option<Self> {
41 match arch {
43 "x64" | "x86_64" => Some(Self::X64),
44 "arm64" | "aarch64" => Some(Self::Arm64),
45 "arm64ec" => Some(Self::Arm64ec),
46 "x86" | "i686" | "i586" => Some(Self::X86),
47 "arm" | "thumbv7a" => Some(Self::Arm),
48 _ => None,
49 }
50 }
51
52 #[cfg(windows)]
53 fn as_vs_arch(&self) -> &'static str {
55 match self {
56 Self::X64 => "x64",
57 Self::Arm64 | Self::Arm64ec => "arm64",
58 Self::X86 => "x86",
59 Self::Arm => "arm",
60 }
61 }
62}
63
64#[derive(Debug, Clone)]
65#[non_exhaustive]
66pub 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 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> {
142 find_tool(arch_or_target, tool).map(|c| c.to_command())
143}
144
145pub fn find_tool(arch_or_target: &str, tool: &str) -> Option<Tool> {
149 let full_arch = if let Some((full_arch, rest)) = arch_or_target.split_once("-") {
150 if !rest.contains("msvc") {
153 return None;
154 }
155 full_arch
156 } else {
157 arch_or_target
158 };
159 find_tool_with_env(full_arch, tool, &StdEnvGetter)
160}
161
162pub fn find_tool_with_env(full_arch: &str, tool: &str, env_getter: &dyn EnvGetter) -> Option<Tool> {
163 let target = TargetArch::new(full_arch)?;
165
166 if tool.contains("msbuild") {
169 return impl_::find_msbuild(target, env_getter);
170 }
171
172 if tool.contains("devenv") {
175 return impl_::find_devenv(target, env_getter);
176 }
177
178 if ["clang", "lldb", "llvm", "ld", "lld"]
181 .iter()
182 .any(|&t| tool.contains(t))
183 {
184 return impl_::find_llvm_tool(tool, target, env_getter);
185 }
186
187 impl_::find_msvc_environment(tool, target, env_getter)
195 .or_else(|| impl_::find_msvc_15plus(tool, target, env_getter))
196 .or_else(|| impl_::find_msvc_14(tool, target, env_getter))
197}
198
199#[derive(Default)]
201pub struct Sdk {
202 libs: Vec<PathBuf>,
203 path: Vec<PathBuf>,
204 include: Vec<PathBuf>,
205 version: String,
206}
207
208impl Sdk {
209 pub fn libs(&self) -> impl Iterator<Item = &Path> {
211 self.libs.iter().map(PathBuf::as_path)
212 }
213
214 pub fn path(&self) -> impl Iterator<Item = &Path> {
216 self.path.iter().map(PathBuf::as_path)
217 }
218
219 pub fn include(&self) -> impl Iterator<Item = &Path> {
221 self.include.iter().map(PathBuf::as_path)
222 }
223
224 pub fn sdk_version(&self) -> &str {
226 &self.version
227 }
228}
229
230pub fn find_windows_sdk(full_arch: &str) -> Option<Sdk> {
235 let target = TargetArch::new(full_arch)?;
236 impl_::get_sdks(target, &StdEnvGetter)
237}
238
239#[derive(Debug, PartialEq, Eq, Copy, Clone)]
241#[non_exhaustive]
242pub enum VsVers {
243 #[deprecated(
245 note = "Visual Studio 12 is no longer supported. cc will never return this value."
246 )]
247 Vs12,
248 Vs14,
250 Vs15,
252 Vs16,
254 Vs17,
256 Vs18,
258}
259
260#[allow(clippy::disallowed_methods)]
265pub fn find_vs_version() -> Result<VsVers, String> {
266 fn has_msbuild_version(version: &str) -> bool {
267 impl_::has_msbuild_version(version, &StdEnvGetter)
268 }
269
270 match std::env::var("VisualStudioVersion") {
271 Ok(version) => match &version[..] {
272 "18.0" => Ok(VsVers::Vs18),
273 "17.0" => Ok(VsVers::Vs17),
274 "16.0" => Ok(VsVers::Vs16),
275 "15.0" => Ok(VsVers::Vs15),
276 "14.0" => Ok(VsVers::Vs14),
277 vers => Err(format!(
278 "\n\n\
279 unsupported or unknown VisualStudio version: {vers}\n\
280 if another version is installed consider running \
281 the appropriate vcvars script before building this \
282 crate\n\
283 "
284 )),
285 },
286 _ => {
287 if has_msbuild_version("18.0") {
290 Ok(VsVers::Vs18)
291 } else if has_msbuild_version("17.0") {
292 Ok(VsVers::Vs17)
293 } else if has_msbuild_version("16.0") {
294 Ok(VsVers::Vs16)
295 } else if has_msbuild_version("15.0") {
296 Ok(VsVers::Vs15)
297 } else if has_msbuild_version("14.0") {
298 Ok(VsVers::Vs14)
299 } else {
300 Err("\n\n\
301 couldn't determine visual studio generator\n\
302 if VisualStudio is installed, however, consider \
303 running the appropriate vcvars script before building \
304 this crate\n\
305 "
306 .to_string())
307 }
308 }
309 }
310}
311
312pub fn get_ucrt_dir() -> Option<(PathBuf, String)> {
319 impl_::get_ucrt_dir()
320}
321
322#[cfg(windows)]
324mod impl_ {
325 use crate::com;
326 use crate::registry::{RegistryKey, LOCAL_MACHINE};
327 use crate::setup_config::SetupConfiguration;
328 use crate::vs_instances::{VsInstances, VswhereInstance};
329 use crate::windows_sys::{
330 GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE,
331 IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK,
332 };
333 use std::convert::TryFrom;
334 use std::env;
335 use std::ffi::OsString;
336 use std::fs::File;
337 use std::io::Read;
338 use std::iter;
339 use std::mem;
340 use std::path::{Path, PathBuf};
341 use std::process::Command;
342 use std::str::FromStr;
343 use std::sync::atomic::{AtomicBool, Ordering};
344 use std::sync::Once;
345
346 use super::{EnvGetter, Sdk, TargetArch};
347 use crate::Tool;
348
349 struct MsvcTool {
350 tool: PathBuf,
351 libs: Vec<PathBuf>,
352 path: Vec<PathBuf>,
353 include: Vec<PathBuf>,
354 }
355
356 struct LibraryHandle(HMODULE);
357
358 impl LibraryHandle {
359 fn new(name: &[u8]) -> Option<Self> {
360 let handle = unsafe { LoadLibraryA(name.as_ptr().cast()) };
361 (!handle.is_null()).then_some(Self(handle))
362 }
363
364 unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> {
373 let symbol = GetProcAddress(self.0, name.as_ptr().cast());
374 symbol.map(|symbol| mem::transmute_copy(&symbol))
375 }
376 }
377
378 type GetMachineTypeAttributesFuncType =
379 unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32;
380 const _: () = {
381 let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes;
385 };
386
387 fn is_amd64_emulation_supported_inner() -> Option<bool> {
388 let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?;
390 let get_machine_type_attributes = unsafe {
392 kernel32
393 .get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0")
394 }?;
395 let mut attributes = Default::default();
396 if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK
397 {
398 Some((attributes & UserEnabled) != 0)
399 } else {
400 Some(false)
401 }
402 }
403
404 fn is_amd64_emulation_supported() -> bool {
405 static LOAD_VALUE: Once = Once::new();
407 static IS_SUPPORTED: AtomicBool = AtomicBool::new(false);
408
409 LOAD_VALUE.call_once(|| {
411 IS_SUPPORTED.store(
412 is_amd64_emulation_supported_inner().unwrap_or(false),
413 Ordering::Relaxed,
414 );
415 });
416 IS_SUPPORTED.load(Ordering::Relaxed)
417 }
418
419 impl MsvcTool {
420 fn new(tool: PathBuf) -> MsvcTool {
421 MsvcTool {
422 tool,
423 libs: Vec::new(),
424 path: Vec::new(),
425 include: Vec::new(),
426 }
427 }
428
429 fn add_sdk(&mut self, sdk_info: Sdk) {
430 self.libs.extend(sdk_info.libs);
431 self.path.extend(sdk_info.path);
432 self.include.extend(sdk_info.include);
433 }
434
435 fn into_tool(self, env_getter: &dyn EnvGetter) -> Tool {
436 let MsvcTool {
437 tool,
438 libs,
439 path,
440 include,
441 } = self;
442 let mut tool = Tool {
443 tool,
444 is_clang_cl: false,
445 env: Vec::new(),
446 };
447 add_env(&mut tool, "LIB", libs, env_getter);
448 add_env(&mut tool, "PATH", path, env_getter);
449 add_env(&mut tool, "INCLUDE", include, env_getter);
450 tool
451 }
452 }
453
454 impl Sdk {
455 fn find_tool(&self, tool: &str) -> Option<PathBuf> {
456 self.path.iter().map(|p| p.join(tool)).find_map(|mut p| {
457 (p.exists() || (p.set_extension(env::consts::EXE_SUFFIX) && p.exists()))
458 .then_some(p)
459 })
460 }
461 }
462
463 fn is_vscmd_target(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
466 is_vscmd_target_env(target, env_getter).or_else(|| is_vscmd_target_cl(target, env_getter))
467 }
468
469 fn is_vscmd_target_env(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
472 let vscmd_arch = env_getter.get_env("VSCMD_ARG_TGT_ARCH")?;
473 Some(target.as_vs_arch() == vscmd_arch.as_ref())
474 }
475
476 fn is_vscmd_target_cl(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
479 let cmd_target = vscmd_target_cl(env_getter)?;
480 Some(target.as_vs_arch() == cmd_target)
481 }
482
483 fn vscmd_target_cl(env_getter: &dyn EnvGetter) -> Option<&'static str> {
486 let cl_exe = env_getter.get_env("PATH").and_then(|path| {
487 env::split_paths(&path)
488 .map(|p| p.join("cl.exe"))
489 .find(|p| p.exists())
490 })?;
491 let mut cl = Command::new(cl_exe);
492 cl.stderr(std::process::Stdio::piped())
493 .stdout(std::process::Stdio::null());
494
495 let out = cl.output().ok()?;
496 let cl_arch = out
497 .stderr
498 .split(|&b| b == b'\n' || b == b'\r')
499 .next()?
500 .rsplit(|&b| b == b' ')
501 .next()?;
502
503 match cl_arch {
504 b"x64" => Some("x64"),
505 b"x86" => Some("x86"),
506 b"ARM64" => Some("arm64"),
507 b"ARM" => Some("arm"),
508 _ => None,
509 }
510 }
511
512 pub(super) fn find_msvc_environment(
514 tool: &str,
515 target: TargetArch,
516 env_getter: &dyn EnvGetter,
517 ) -> Option<Tool> {
518 if env_getter.get_env("VCINSTALLDIR").is_none()
523 && env_getter.get_env("VSTEL_MSBuildProjectFullPath").is_none()
524 {
525 return None;
526 }
527
528 if is_vscmd_target(target, env_getter) == Some(false) {
531 let vs_install_dir: PathBuf = env_getter.get_env("VSINSTALLDIR")?.into();
533 tool_from_vs15plus_instance(tool, target, &vs_install_dir, env_getter)
534 } else {
535 env_getter
537 .get_env("PATH")
538 .and_then(|path| {
539 env::split_paths(&path)
540 .map(|p| p.join(tool))
541 .find(|p| p.exists())
542 })
543 .map(|path| Tool {
544 tool: path,
545 is_clang_cl: false,
546 env: Vec::new(),
547 })
548 }
549 }
550
551 fn find_msbuild_vs18(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
552 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "18", env_getter)
553 }
554
555 fn find_msbuild_vs17(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
556 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17", env_getter)
557 }
558
559 #[allow(bare_trait_objects)]
560 fn vs16plus_instances(
561 target: TargetArch,
562 version: &'static str,
563 env_getter: &dyn EnvGetter,
564 ) -> Box<dyn Iterator<Item = PathBuf>> {
565 let Some(instances) = vs15plus_instances(target, env_getter) else {
566 return Box::new(iter::empty());
567 };
568 Box::new(instances.into_iter().filter_map(move |instance| {
569 let installation_name = instance.installation_name()?;
570 if installation_name.starts_with(&format!("VisualStudio/{}.", version))
571 || installation_name.starts_with(&format!("VisualStudioPreview/{}.", version))
572 {
573 Some(instance.installation_path()?)
574 } else {
575 None
576 }
577 }))
578 }
579
580 fn find_tool_in_vs16plus_path(
581 tool: &str,
582 target: TargetArch,
583 version: &'static str,
584 env_getter: &dyn EnvGetter,
585 ) -> Option<Tool> {
586 vs16plus_instances(target, version, env_getter)
587 .filter_map(|path| {
588 let path = path.join(tool);
589 if !path.is_file() {
590 return None;
591 }
592 let mut tool = Tool {
593 tool: path,
594 is_clang_cl: false,
595 env: Vec::new(),
596 };
597 if target == TargetArch::X64 {
598 tool.env.push(("Platform".into(), "X64".into()));
599 }
600 if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
601 tool.env.push(("Platform".into(), "ARM64".into()));
602 }
603 Some(tool)
604 })
605 .next()
606 }
607
608 fn find_msbuild_vs16(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
609 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16", env_getter)
610 }
611
612 pub(super) fn find_llvm_tool(
613 tool: &str,
614 target: TargetArch,
615 env_getter: &dyn EnvGetter,
616 ) -> Option<Tool> {
617 find_llvm_tool_vs17plus(tool, target, env_getter, "18")
618 .or_else(|| find_llvm_tool_vs17plus(tool, target, env_getter, "17"))
619 }
620
621 fn find_llvm_tool_vs17plus(
622 tool: &str,
623 target: TargetArch,
624 env_getter: &dyn EnvGetter,
625 version: &'static str,
626 ) -> Option<Tool> {
627 vs16plus_instances(target, version, env_getter)
628 .filter_map(|mut base_path| {
629 base_path.push(r"VC\Tools\LLVM");
630 let host_folder = match host_arch() {
631 X86 => "",
634 X86_64 => "x64",
635 AARCH64 => "ARM64",
636 _ => return None,
637 };
638 if !host_folder.is_empty() {
639 base_path.push(host_folder);
641 }
642 base_path.push("bin");
644 base_path.push(tool);
645 let is_clang_cl = tool.contains("clang-cl");
646 base_path.is_file().then(|| Tool {
647 tool: base_path,
648 is_clang_cl,
649 env: Vec::new(),
650 })
651 })
652 .next()
653 }
654
655 fn vs15plus_instances(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<VsInstances> {
668 vs15plus_instances_using_com()
669 .or_else(|| vs15plus_instances_using_vswhere(target, env_getter))
670 }
671
672 fn vs15plus_instances_using_com() -> Option<VsInstances> {
673 com::initialize().ok()?;
674
675 let config = SetupConfiguration::new().ok()?;
676 let enum_setup_instances = config.enum_all_instances().ok()?;
677
678 Some(VsInstances::ComBased(enum_setup_instances))
679 }
680
681 fn vs15plus_instances_using_vswhere(
682 target: TargetArch,
683 env_getter: &dyn EnvGetter,
684 ) -> Option<VsInstances> {
685 let program_files_path = env_getter
686 .get_env("ProgramFiles(x86)")
687 .or_else(|| env_getter.get_env("ProgramFiles"))?;
688
689 let program_files_path = Path::new(program_files_path.as_ref());
690
691 let vswhere_path =
692 program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe");
693
694 if !vswhere_path.exists() {
695 return None;
696 }
697
698 let tools_arch = match target {
699 TargetArch::X86 | TargetArch::X64 => Some("x86.x64"),
700 TargetArch::Arm => Some("ARM"),
701 TargetArch::Arm64 | TargetArch::Arm64ec => Some("ARM64"),
702 };
703
704 let vswhere_output = Command::new(vswhere_path)
705 .args([
706 "-latest",
707 "-products",
708 "*",
709 "-requires",
710 &format!("Microsoft.VisualStudio.Component.VC.Tools.{}", tools_arch?),
711 "-format",
712 "text",
713 "-nologo",
714 ])
715 .stderr(std::process::Stdio::inherit())
716 .output()
717 .ok()?;
718
719 let vs_instances =
720 VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?);
721
722 Some(vs_instances)
723 }
724
725 fn parse_version(version: &str) -> Option<[u16; 4]> {
728 let mut iter = version.split('.').map(u16::from_str).fuse();
729 let mut get_next_number = move || match iter.next() {
730 Some(Ok(version_part)) => Some(version_part),
731 Some(Err(_)) => None,
732 None => Some(0),
733 };
734 Some([
735 get_next_number()?,
736 get_next_number()?,
737 get_next_number()?,
738 get_next_number()?,
739 ])
740 }
741
742 pub(super) fn find_msvc_15plus(
743 tool: &str,
744 target: TargetArch,
745 env_getter: &dyn EnvGetter,
746 ) -> Option<Tool> {
747 let iter = vs15plus_instances(target, env_getter)?;
748 iter.into_iter()
749 .filter_map(|instance| {
750 let version = parse_version(&instance.installation_version()?)?;
751 let instance_path = instance.installation_path()?;
752 let tool = tool_from_vs15plus_instance(tool, target, &instance_path, env_getter)?;
753 Some((version, tool))
754 })
755 .max_by_key(|(version, _)| *version)
756 .map(|(_version, tool)| tool)
757 }
758
759 fn find_tool_in_vs15_path(
767 tool: &str,
768 target: TargetArch,
769 env_getter: &dyn EnvGetter,
770 ) -> Option<Tool> {
771 let mut path = match vs15plus_instances(target, env_getter) {
772 Some(instances) => instances
773 .into_iter()
774 .filter_map(|instance| instance.installation_path())
775 .map(|path| path.join(tool))
776 .find(|path| path.is_file()),
777 None => None,
778 };
779
780 if path.is_none() {
781 let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7";
782 path = LOCAL_MACHINE
783 .open(key.as_ref())
784 .ok()
785 .and_then(|key| key.query_str("15.0").ok())
786 .map(|path| PathBuf::from(path).join(tool))
787 .filter(|path| path.is_file());
788 }
789
790 path.map(|path| {
791 let mut tool = Tool {
792 tool: path,
793 is_clang_cl: false,
794 env: Vec::new(),
795 };
796 if target == TargetArch::X64 {
797 tool.env.push(("Platform".into(), "X64".into()));
798 } else if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
799 tool.env.push(("Platform".into(), "ARM64".into()));
800 }
801 tool
802 })
803 }
804
805 fn tool_from_vs15plus_instance(
806 tool: &str,
807 target: TargetArch,
808 instance_path: &Path,
809 env_getter: &dyn EnvGetter,
810 ) -> Option<Tool> {
811 let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) =
812 vs15plus_vc_paths(target, instance_path, env_getter)?;
813 let sdk_info = get_sdks(target, env_getter)?;
814 let mut tool_path = bin_path.join(tool);
815 if !tool_path.exists() {
816 tool_path = sdk_info.find_tool(tool)?;
817 };
818
819 let mut tool = MsvcTool::new(tool_path);
820 tool.path.push(bin_path.clone());
821 tool.path.push(host_dylib_path);
822 if let Some(alt_lib_path) = alt_lib_path {
823 tool.libs.push(alt_lib_path);
824 }
825 tool.libs.push(lib_path);
826 tool.include.push(include_path);
827
828 if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &root_path) {
829 tool.libs.push(atl_lib_path);
830 tool.include.push(atl_include_path);
831 }
832
833 tool.add_sdk(sdk_info);
834
835 Some(tool.into_tool(env_getter))
836 }
837
838 fn vs15plus_vc_paths(
839 target_arch: TargetArch,
840 instance_path: &Path,
841 env_getter: &dyn EnvGetter,
842 ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> {
843 let version = vs15plus_vc_read_version(instance_path, env_getter)?;
844
845 let hosts = match host_arch() {
846 X86 => &["X86"],
847 X86_64 => &["X64"],
848 AARCH64 => {
853 if is_amd64_emulation_supported() {
854 &["ARM64", "X64", "X86"][..]
855 } else {
856 &["ARM64", "X86"]
857 }
858 }
859 _ => return None,
860 };
861 let target_dir = target_arch.as_vs_arch();
862 let path = instance_path.join(r"VC\Tools\MSVC").join(version);
864 let (host_path, host) = hosts.iter().find_map(|&x| {
866 let candidate = path.join("bin").join(format!("Host{}", x));
867 if candidate.join(target_dir).exists() {
868 Some((candidate, x))
869 } else {
870 None
871 }
872 })?;
873 let bin_path = host_path.join(target_dir);
876 let host_dylib_path = host_path.join(host.to_lowercase());
880 let lib_fragment = if use_spectre_mitigated_libs(env_getter) {
881 r"lib\spectre"
882 } else {
883 "lib"
884 };
885 let lib_path = path.join(lib_fragment).join(target_dir);
886 let alt_lib_path =
887 (target_arch == TargetArch::Arm64ec).then(|| path.join(lib_fragment).join("arm64ec"));
888 let include_path = path.join("include");
889 Some((
890 path,
891 bin_path,
892 host_dylib_path,
893 lib_path,
894 alt_lib_path,
895 include_path,
896 ))
897 }
898
899 fn vs15plus_vc_read_version(dir: &Path, env_getter: &dyn EnvGetter) -> Option<String> {
900 if let Some(version) = env_getter.get_env("VCToolsVersion") {
901 return version.to_str().map(ToString::to_string);
904 }
905
906 let mut version_path: PathBuf =
908 dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
909 let mut version_file = if let Ok(f) = File::open(&version_path) {
910 f
911 } else {
912 let mut version_file = String::new();
917 version_path.pop();
918 for file in version_path.read_dir().ok()? {
919 let name = file.ok()?.file_name();
920 let name = name.to_str()?;
921 if name.starts_with("Microsoft.VCToolsVersion.v")
922 && name.ends_with(".default.txt")
923 && name > &version_file
924 {
925 version_file.replace_range(.., name);
926 }
927 }
928 if version_file.is_empty() {
929 let tools_dir: PathBuf = dir.join(r"VC\Tools\MSVC");
931 return tools_dir
932 .read_dir()
933 .ok()?
934 .filter_map(|file| {
935 let file = file.ok()?;
936 let name = file.file_name().into_string().ok()?;
937
938 file.path().join("bin").exists().then(|| {
939 let version = parse_version(&name);
940 (name, version)
941 })
942 })
943 .max_by_key(|(_, version)| *version)
944 .map(|(name, _)| name);
945 }
946 version_path.push(version_file);
947 File::open(version_path).ok()?
948 };
949
950 let mut version = String::new();
952 version_file.read_to_string(&mut version).ok()?;
953 version.truncate(version.trim_end().len());
954 Some(version)
955 }
956
957 fn use_spectre_mitigated_libs(env_getter: &dyn EnvGetter) -> bool {
958 env_getter
959 .get_env("VSCMD_ARG_VCVARS_SPECTRE")
960 .map(|env| env.as_ref() == "spectre")
961 .unwrap_or_default()
962 }
963
964 fn atl_paths(target: TargetArch, path: &Path) -> Option<(PathBuf, PathBuf)> {
965 let atl_path = path.join("atlmfc");
966 let sub = target.as_vs_arch();
967 if atl_path.exists() {
968 Some((atl_path.join("lib").join(sub), atl_path.join("include")))
969 } else {
970 None
971 }
972 }
973
974 pub(super) fn find_msvc_14(
977 tool: &str,
978 target: TargetArch,
979 env_getter: &dyn EnvGetter,
980 ) -> Option<Tool> {
981 if env_getter.get_env("VCToolsVersion").is_some() {
982 return None;
984 }
985
986 let vcdir = get_vc_dir("14.0")?;
987 let sdk_info = get_sdks(target, env_getter)?;
988 let mut tool = get_tool(tool, &vcdir, target, &sdk_info)?;
989 tool.add_sdk(sdk_info);
990 Some(tool.into_tool(env_getter))
991 }
992
993 pub(super) fn get_sdks(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Sdk> {
994 let sub = target.as_vs_arch();
995 let (ucrt, ucrt_version) = get_ucrt_dir()?;
996
997 let host = match host_arch() {
998 X86 => "x86",
999 X86_64 => "x64",
1000 AARCH64 => "arm64",
1001 _ => return None,
1002 };
1003
1004 let mut info = Sdk::default();
1005
1006 info.path
1007 .push(ucrt.join("bin").join(&ucrt_version).join(host));
1008
1009 let ucrt_include = ucrt.join("include").join(&ucrt_version);
1010 info.include.push(ucrt_include.join("ucrt"));
1011
1012 let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
1013 info.libs.push(ucrt_lib.join("ucrt").join(sub));
1014
1015 if let Some((sdk, version)) = get_sdk10_dir(env_getter) {
1016 info.path.push(sdk.join("bin").join(host));
1017 let sdk_lib = sdk.join("lib").join(&version);
1018 info.libs.push(sdk_lib.join("um").join(sub));
1019 let sdk_include = sdk.join("include").join(&version);
1020 info.include.push(sdk_include.join("um"));
1021 info.include.push(sdk_include.join("cppwinrt"));
1022 info.include.push(sdk_include.join("winrt"));
1023 info.include.push(sdk_include.join("shared"));
1024 info.version = version;
1025 } else if let Some(sdk) = get_sdk81_dir() {
1026 info.path.push(sdk.join("bin").join(host));
1027 let sdk_lib = sdk.join("lib").join("winv6.3");
1028 info.libs.push(sdk_lib.join("um").join(sub));
1029 let sdk_include = sdk.join("include");
1030 info.include.push(sdk_include.join("um"));
1031 info.include.push(sdk_include.join("winrt"));
1032 info.include.push(sdk_include.join("shared"));
1033 info.version = "8.1".into();
1034 }
1035
1036 Some(info)
1037 }
1038
1039 fn add_env(
1040 tool: &mut Tool,
1041 env: &'static str,
1042 paths: Vec<PathBuf>,
1043 env_getter: &dyn EnvGetter,
1044 ) {
1045 let prev = env_getter.get_env(env);
1046 let prev = prev.as_ref().map(AsRef::as_ref).unwrap_or_default();
1047 let prev = env::split_paths(&prev);
1048 let new = paths.into_iter().chain(prev);
1049 tool.env
1050 .push((env.to_string().into(), env::join_paths(new).unwrap()));
1051 }
1052
1053 fn get_tool(tool: &str, path: &Path, target: TargetArch, sdk_info: &Sdk) -> Option<MsvcTool> {
1056 bin_subdir(target)
1057 .into_iter()
1058 .map(|(sub, host)| {
1059 (
1060 path.join("bin").join(sub).join(tool),
1061 Some(path.join("bin").join(host)),
1062 )
1063 })
1064 .filter(|(path, _)| path.is_file())
1065 .chain(iter::once_with(|| Some((sdk_info.find_tool(tool)?, None))).flatten())
1066 .map(|(tool_path, host)| {
1067 let mut tool = MsvcTool::new(tool_path);
1068 tool.path.extend(host);
1069 let sub = vc_lib_subdir(target);
1070 tool.libs.push(path.join("lib").join(sub));
1071 tool.include.push(path.join("include"));
1072 let atlmfc_path = path.join("atlmfc");
1073 if atlmfc_path.exists() {
1074 tool.libs.push(atlmfc_path.join("lib").join(sub));
1075 tool.include.push(atlmfc_path.join("include"));
1076 }
1077 tool
1078 })
1079 .next()
1080 }
1081
1082 fn get_vc_dir(ver: &str) -> Option<PathBuf> {
1085 let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
1086 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1087 let path = key.query_str(ver).ok()?;
1088 Some(path.into())
1089 }
1090
1091 pub(super) fn get_ucrt_dir() -> Option<(PathBuf, String)> {
1098 let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
1099 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1100 let root = key.query_str("KitsRoot10").ok()?;
1101 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
1102 let max_libdir = readdir
1103 .filter_map(|dir| dir.ok())
1104 .map(|dir| dir.path())
1105 .filter(|dir| {
1106 dir.components()
1107 .next_back()
1108 .and_then(|c| c.as_os_str().to_str())
1109 .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
1110 .unwrap_or(false)
1111 })
1112 .max()?;
1113 let version = max_libdir.components().next_back().unwrap();
1114 let version = version.as_os_str().to_str().unwrap().to_string();
1115 Some((root.into(), version))
1116 }
1117
1118 fn get_sdk10_dir(env_getter: &dyn EnvGetter) -> Option<(PathBuf, String)> {
1130 if let (Some(root), Some(version)) = (
1131 env_getter.get_env("WindowsSdkDir"),
1132 env_getter
1133 .get_env("WindowsSDKVersion")
1134 .as_ref()
1135 .and_then(|version| version.as_ref().to_str()),
1136 ) {
1137 return Some((
1138 PathBuf::from(root),
1139 version.trim_end_matches('\\').to_string(),
1140 ));
1141 }
1142
1143 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
1144 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1145 let root = key.query_str("InstallationFolder").ok()?;
1146 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
1147 let mut dirs = readdir
1148 .filter_map(|dir| dir.ok())
1149 .map(|dir| dir.path())
1150 .collect::<Vec<_>>();
1151 dirs.sort();
1152 let dir = dirs
1153 .into_iter()
1154 .rev()
1155 .find(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())?;
1156 let version = dir.components().next_back().unwrap();
1157 let version = version.as_os_str().to_str().unwrap().to_string();
1158 Some((root.into(), version))
1159 }
1160
1161 fn get_sdk81_dir() -> Option<PathBuf> {
1166 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
1167 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1168 let root = key.query_str("InstallationFolder").ok()?;
1169 Some(root.into())
1170 }
1171
1172 const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
1173 const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
1174 const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12;
1175 const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
1176 const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
1177 const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64;
1178
1179 fn bin_subdir(target: TargetArch) -> Vec<(&'static str, &'static str)> {
1192 match (target, host_arch()) {
1193 (TargetArch::X86, X86) => vec![("", "")],
1194 (TargetArch::X86, X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
1195 (TargetArch::X64, X86) => vec![("x86_amd64", "")],
1196 (TargetArch::X64, X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
1197 (TargetArch::Arm, X86) => vec![("x86_arm", "")],
1198 (TargetArch::Arm, X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
1199 _ => vec![],
1200 }
1201 }
1202
1203 fn vc_lib_subdir(target: TargetArch) -> &'static str {
1205 match target {
1206 TargetArch::X86 => "",
1207 TargetArch::X64 => "amd64",
1208 TargetArch::Arm => "arm",
1209 TargetArch::Arm64 | TargetArch::Arm64ec => "arm64",
1210 }
1211 }
1212
1213 #[allow(bad_style)]
1214 fn host_arch() -> u16 {
1215 type DWORD = u32;
1216 type WORD = u16;
1217 type LPVOID = *mut u8;
1218 type DWORD_PTR = usize;
1219
1220 #[repr(C)]
1221 struct SYSTEM_INFO {
1222 wProcessorArchitecture: WORD,
1223 _wReserved: WORD,
1224 _dwPageSize: DWORD,
1225 _lpMinimumApplicationAddress: LPVOID,
1226 _lpMaximumApplicationAddress: LPVOID,
1227 _dwActiveProcessorMask: DWORD_PTR,
1228 _dwNumberOfProcessors: DWORD,
1229 _dwProcessorType: DWORD,
1230 _dwAllocationGranularity: DWORD,
1231 _wProcessorLevel: WORD,
1232 _wProcessorRevision: WORD,
1233 }
1234
1235 extern "system" {
1236 fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
1237 }
1238
1239 unsafe {
1240 let mut info = mem::zeroed();
1241 GetNativeSystemInfo(&mut info);
1242 info.wProcessorArchitecture
1243 }
1244 }
1245
1246 fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
1251 let mut max_vers = 0;
1252 let mut max_key = None;
1253 for subkey in key.iter().filter_map(|k| k.ok()) {
1254 let val = subkey
1255 .to_str()
1256 .and_then(|s| s.trim_start_matches('v').replace('.', "").parse().ok());
1257 let Some(val) = val else { continue };
1258 if val > max_vers {
1259 if let Ok(k) = key.open(&subkey) {
1260 max_vers = val;
1261 max_key = Some((subkey, k));
1262 }
1263 }
1264 }
1265 max_key
1266 }
1267
1268 #[inline(always)]
1269 pub(super) fn has_msbuild_version(version: &str, env_getter: &dyn EnvGetter) -> bool {
1270 match version {
1271 "18.0" => {
1272 find_msbuild_vs18(TargetArch::X64, env_getter).is_some()
1273 || find_msbuild_vs18(TargetArch::X86, env_getter).is_some()
1274 || find_msbuild_vs18(TargetArch::Arm64, env_getter).is_some()
1275 }
1276 "17.0" => {
1277 find_msbuild_vs17(TargetArch::X64, env_getter).is_some()
1278 || find_msbuild_vs17(TargetArch::X86, env_getter).is_some()
1279 || find_msbuild_vs17(TargetArch::Arm64, env_getter).is_some()
1280 }
1281 "16.0" => {
1282 find_msbuild_vs16(TargetArch::X64, env_getter).is_some()
1283 || find_msbuild_vs16(TargetArch::X86, env_getter).is_some()
1284 || find_msbuild_vs16(TargetArch::Arm64, env_getter).is_some()
1285 }
1286 "15.0" => {
1287 find_msbuild_vs15(TargetArch::X64, env_getter).is_some()
1288 || find_msbuild_vs15(TargetArch::X86, env_getter).is_some()
1289 || find_msbuild_vs15(TargetArch::Arm64, env_getter).is_some()
1290 }
1291 "14.0" => LOCAL_MACHINE
1292 .open(&OsString::from(format!(
1293 "SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}",
1294 version
1295 )))
1296 .is_ok(),
1297 _ => false,
1298 }
1299 }
1300
1301 pub(super) fn find_devenv(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1302 find_devenv_vs15(target, env_getter)
1303 }
1304
1305 fn find_devenv_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1306 find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target, env_getter)
1307 }
1308
1309 pub(super) fn find_msbuild(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1311 if let Some(r) = find_msbuild_vs18(target, env_getter) {
1313 Some(r)
1314 } else if let Some(r) = find_msbuild_vs17(target, env_getter) {
1315 Some(r)
1316 } else if let Some(r) = find_msbuild_vs16(target, env_getter) {
1317 Some(r)
1318 } else if let Some(r) = find_msbuild_vs15(target, env_getter) {
1319 Some(r)
1320 } else {
1321 find_old_msbuild(target)
1322 }
1323 }
1324
1325 fn find_msbuild_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1326 find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target, env_getter)
1327 }
1328
1329 fn find_old_msbuild(target: TargetArch) -> Option<Tool> {
1330 let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
1331 LOCAL_MACHINE
1332 .open(key.as_ref())
1333 .ok()
1334 .and_then(|key| {
1335 max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
1336 })
1337 .map(|path| {
1338 let mut path = PathBuf::from(path);
1339 path.push("MSBuild.exe");
1340 let mut tool = Tool {
1341 tool: path,
1342 is_clang_cl: false,
1343 env: Vec::new(),
1344 };
1345 if target == TargetArch::X64 {
1346 tool.env.push(("Platform".into(), "X64".into()));
1347 }
1348 tool
1349 })
1350 }
1351
1352 #[cfg(test)]
1353 mod tests {
1354 use super::*;
1355 use std::path::Path;
1356 use crate::find_tools::find;
1358
1359 fn host_arch_to_string(host_arch_value: u16) -> &'static str {
1360 match host_arch_value {
1361 X86 => "x86",
1362 X86_64 => "x64",
1363 AARCH64 => "arm64",
1364 _ => panic!("Unsupported host architecture: {}", host_arch_value),
1365 }
1366 }
1367
1368 #[test]
1369 fn test_find_cl_exe() {
1370 let target_architectures = ["x64", "x86", "arm64"];
1375 let mut found_any = false;
1376
1377 let host_arch_value = host_arch();
1379 let host_name = host_arch_to_string(host_arch_value);
1380
1381 for &target_arch in &target_architectures {
1382 if let Some(cmd) = find(target_arch, "cl.exe") {
1383 assert!(
1385 !cmd.get_program().is_empty(),
1386 "cl.exe program path should not be empty"
1387 );
1388 assert!(
1389 Path::new(cmd.get_program()).exists(),
1390 "cl.exe should exist at: {:?}",
1391 cmd.get_program()
1392 );
1393
1394 let path_str = cmd.get_program().to_string_lossy();
1397 let path_str_lower = path_str.to_lowercase();
1398 let expected_host_target_path =
1399 format!("\\bin\\host{host_name}\\{target_arch}");
1400 let expected_host_target_path_unix =
1401 expected_host_target_path.replace("\\", "/");
1402
1403 assert!(
1404 path_str_lower.contains(&expected_host_target_path) || path_str_lower.contains(&expected_host_target_path_unix),
1405 "cl.exe path should contain host-target combination (case-insensitive) '{}' for {} host targeting {}, but found: {}",
1406 expected_host_target_path,
1407 host_name,
1408 target_arch,
1409 path_str
1410 );
1411
1412 found_any = true;
1413 }
1414 }
1415
1416 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");
1417 }
1418
1419 #[test]
1420 #[cfg(not(disable_clang_cl_tests))]
1421 fn test_find_llvm_tools() {
1422 use crate::find_tools::StdEnvGetter;
1424
1425 let target_arch = TargetArch::new("x64").expect("Should support x64 architecture");
1429 let llvm_tools = ["clang.exe", "clang++.exe", "lld.exe", "llvm-ar.exe"];
1430
1431 let host_arch_value = host_arch();
1433 let expected_host_path = match host_arch_value {
1434 X86 => "LLVM\\bin", X86_64 => "LLVM\\x64\\bin", AARCH64 => "LLVM\\ARM64\\bin", _ => panic!("Unsupported host architecture: {}", host_arch_value),
1438 };
1439
1440 let host_name = host_arch_to_string(host_arch_value);
1441
1442 let mut found_tools_count = 0;
1443
1444 for &tool in &llvm_tools {
1445 let env_getter = StdEnvGetter;
1447 let result = find_llvm_tool(tool, target_arch, &env_getter);
1448
1449 if let Some(found_tool) = result {
1450 found_tools_count += 1;
1451
1452 assert!(
1454 !found_tool.path().as_os_str().is_empty(),
1455 "Found LLVM tool '{}' should have a non-empty path",
1456 tool
1457 );
1458
1459 assert!(
1461 found_tool.path().exists(),
1462 "LLVM tool '{}' path should exist: {:?}",
1463 tool,
1464 found_tool.path()
1465 );
1466
1467 let path_str = found_tool.path().to_string_lossy();
1469 assert!(
1470 path_str.contains(tool.trim_end_matches(".exe")),
1471 "Tool path '{}' should contain tool name '{}'",
1472 path_str,
1473 tool
1474 );
1475
1476 assert!(
1478 path_str.contains(expected_host_path) || path_str.contains(&expected_host_path.replace("\\", "/")),
1479 "LLVM tool should be in host-specific VS LLVM directory '{}' for {} host, but found: {}",
1480 expected_host_path,
1481 host_name,
1482 path_str
1483 );
1484 }
1485 }
1486
1487 assert!(
1489 found_tools_count > 0,
1490 "Expected to find at least one LLVM tool on CI with Visual Studio + Clang installed for {} host. Found: {}",
1491 host_name,
1492 found_tools_count
1493 );
1494 }
1495 }
1496}
1497
1498#[cfg(not(windows))]
1500mod impl_ {
1501 use std::{env, ffi::OsStr, path::PathBuf};
1502
1503 use super::{EnvGetter, Sdk, TargetArch};
1504 use crate::Tool;
1505
1506 #[inline(always)]
1509 pub(super) fn find_msbuild(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1510 None
1511 }
1512
1513 #[inline(always)]
1516 pub(super) fn find_devenv(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1517 None
1518 }
1519
1520 #[inline(always)]
1522 pub(super) fn find_llvm_tool(
1523 _tool: &str,
1524 _target: TargetArch,
1525 _: &dyn EnvGetter,
1526 ) -> Option<Tool> {
1527 None
1528 }
1529
1530 pub(super) fn find_msvc_environment(
1532 tool: &str,
1533 _target: TargetArch,
1534 env_getter: &dyn EnvGetter,
1535 ) -> Option<Tool> {
1536 let vc_install_dir = env_getter.get_env("VCINSTALLDIR")?;
1538 let vs_install_dir = env_getter.get_env("VSINSTALLDIR")?;
1539
1540 let get_tool = |install_dir: &OsStr| {
1541 env::split_paths(install_dir)
1542 .map(|p| p.join(tool))
1543 .find(|p| p.exists())
1544 .map(|path| Tool {
1545 tool: path,
1546 is_clang_cl: false,
1547 env: Vec::new(),
1548 })
1549 };
1550
1551 get_tool(vc_install_dir.as_ref())
1553 .or_else(|| get_tool(vs_install_dir.as_ref()))
1555 .or_else(|| {
1557 env_getter
1558 .get_env("PATH")
1559 .as_ref()
1560 .map(|path| path.as_ref())
1561 .and_then(get_tool)
1562 })
1563 }
1564
1565 #[inline(always)]
1566 pub(super) fn find_msvc_15plus(
1567 _tool: &str,
1568 _target: TargetArch,
1569 _: &dyn EnvGetter,
1570 ) -> Option<Tool> {
1571 None
1572 }
1573
1574 #[inline(always)]
1577 pub(super) fn find_msvc_14(
1578 _tool: &str,
1579 _target: TargetArch,
1580 _: &dyn EnvGetter,
1581 ) -> Option<Tool> {
1582 None
1583 }
1584
1585 #[inline(always)]
1586 pub(super) fn has_msbuild_version(_version: &str, _: &dyn EnvGetter) -> bool {
1587 false
1588 }
1589
1590 #[inline(always)]
1591 pub(super) fn get_ucrt_dir() -> Option<(PathBuf, String)> {
1592 None
1593 }
1594
1595 #[inline(always)]
1596 pub(super) fn get_sdks(_target: TargetArch, _env_getter: &dyn EnvGetter) -> Option<Sdk> {
1597 None
1598 }
1599}