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> {
134 find_tool(arch_or_target, tool).map(|c| c.to_command())
135}
136
137pub fn find_tool(arch_or_target: &str, tool: &str) -> Option<Tool> {
141 let full_arch = if let Some((full_arch, rest)) = arch_or_target.split_once("-") {
142 if !rest.contains("msvc") {
145 return None;
146 }
147 full_arch
148 } else {
149 arch_or_target
150 };
151 find_tool_with_env(full_arch, tool, &StdEnvGetter)
152}
153
154pub fn find_tool_with_env(full_arch: &str, tool: &str, env_getter: &dyn EnvGetter) -> Option<Tool> {
155 let target = TargetArch::new(full_arch)?;
157
158 if tool.contains("msbuild") {
161 return impl_::find_msbuild(target, env_getter);
162 }
163
164 if tool.contains("devenv") {
167 return impl_::find_devenv(target, env_getter);
168 }
169
170 if ["clang", "lldb", "llvm", "ld", "lld"]
173 .iter()
174 .any(|&t| tool.contains(t))
175 {
176 return impl_::find_llvm_tool(tool, target, env_getter);
177 }
178
179 impl_::find_msvc_environment(tool, target, env_getter)
187 .or_else(|| impl_::find_msvc_15plus(tool, target, env_getter))
188 .or_else(|| impl_::find_msvc_14(tool, target, env_getter))
189}
190
191#[derive(Debug, PartialEq, Eq, Copy, Clone)]
193#[non_exhaustive]
194pub enum VsVers {
195 #[deprecated(
197 note = "Visual Studio 12 is no longer supported. cc will never return this value."
198 )]
199 Vs12,
200 Vs14,
202 Vs15,
204 Vs16,
206 Vs17,
208}
209
210#[allow(clippy::disallowed_methods)]
215pub fn find_vs_version() -> Result<VsVers, String> {
216 fn has_msbuild_version(version: &str) -> bool {
217 impl_::has_msbuild_version(version, &StdEnvGetter)
218 }
219
220 match std::env::var("VisualStudioVersion") {
221 Ok(version) => match &version[..] {
222 "17.0" => Ok(VsVers::Vs17),
223 "16.0" => Ok(VsVers::Vs16),
224 "15.0" => Ok(VsVers::Vs15),
225 "14.0" => Ok(VsVers::Vs14),
226 vers => Err(format!(
227 "\n\n\
228 unsupported or unknown VisualStudio version: {vers}\n\
229 if another version is installed consider running \
230 the appropriate vcvars script before building this \
231 crate\n\
232 "
233 )),
234 },
235 _ => {
236 if has_msbuild_version("17.0") {
239 Ok(VsVers::Vs17)
240 } else if has_msbuild_version("16.0") {
241 Ok(VsVers::Vs16)
242 } else if has_msbuild_version("15.0") {
243 Ok(VsVers::Vs15)
244 } else if has_msbuild_version("14.0") {
245 Ok(VsVers::Vs14)
246 } else {
247 Err("\n\n\
248 couldn't determine visual studio generator\n\
249 if VisualStudio is installed, however, consider \
250 running the appropriate vcvars script before building \
251 this crate\n\
252 "
253 .to_string())
254 }
255 }
256 }
257}
258
259pub fn get_ucrt_dir() -> Option<(PathBuf, String)> {
266 impl_::get_ucrt_dir()
267}
268
269#[cfg(windows)]
271mod impl_ {
272 use crate::com;
273 use crate::registry::{RegistryKey, LOCAL_MACHINE};
274 use crate::setup_config::SetupConfiguration;
275 use crate::vs_instances::{VsInstances, VswhereInstance};
276 use crate::windows_sys::{
277 GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE,
278 IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK,
279 };
280 use std::convert::TryFrom;
281 use std::env;
282 use std::ffi::OsString;
283 use std::fs::File;
284 use std::io::Read;
285 use std::iter;
286 use std::mem;
287 use std::path::{Path, PathBuf};
288 use std::process::Command;
289 use std::str::FromStr;
290 use std::sync::atomic::{AtomicBool, Ordering};
291 use std::sync::Once;
292
293 use super::{EnvGetter, TargetArch};
294 use crate::Tool;
295
296 struct MsvcTool {
297 tool: PathBuf,
298 libs: Vec<PathBuf>,
299 path: Vec<PathBuf>,
300 include: Vec<PathBuf>,
301 }
302
303 struct LibraryHandle(HMODULE);
304
305 impl LibraryHandle {
306 fn new(name: &[u8]) -> Option<Self> {
307 let handle = unsafe { LoadLibraryA(name.as_ptr() as _) };
308 (!handle.is_null()).then_some(Self(handle))
309 }
310
311 unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> {
320 let symbol = GetProcAddress(self.0, name.as_ptr() as _);
321 symbol.map(|symbol| mem::transmute_copy(&symbol))
322 }
323 }
324
325 type GetMachineTypeAttributesFuncType =
326 unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32;
327 const _: () = {
328 let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes;
332 };
333
334 fn is_amd64_emulation_supported_inner() -> Option<bool> {
335 let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?;
337 let get_machine_type_attributes = unsafe {
339 kernel32
340 .get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0")
341 }?;
342 let mut attributes = Default::default();
343 if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK
344 {
345 Some((attributes & UserEnabled) != 0)
346 } else {
347 Some(false)
348 }
349 }
350
351 fn is_amd64_emulation_supported() -> bool {
352 static LOAD_VALUE: Once = Once::new();
354 static IS_SUPPORTED: AtomicBool = AtomicBool::new(false);
355
356 LOAD_VALUE.call_once(|| {
358 IS_SUPPORTED.store(
359 is_amd64_emulation_supported_inner().unwrap_or(false),
360 Ordering::Relaxed,
361 );
362 });
363 IS_SUPPORTED.load(Ordering::Relaxed)
364 }
365
366 impl MsvcTool {
367 fn new(tool: PathBuf) -> MsvcTool {
368 MsvcTool {
369 tool,
370 libs: Vec::new(),
371 path: Vec::new(),
372 include: Vec::new(),
373 }
374 }
375
376 fn into_tool(self, env_getter: &dyn EnvGetter) -> Tool {
377 let MsvcTool {
378 tool,
379 libs,
380 path,
381 include,
382 } = self;
383 let mut tool = Tool {
384 tool,
385 is_clang_cl: false,
386 env: Vec::new(),
387 };
388 add_env(&mut tool, "LIB", libs, env_getter);
389 add_env(&mut tool, "PATH", path, env_getter);
390 add_env(&mut tool, "INCLUDE", include, env_getter);
391 tool
392 }
393 }
394
395 fn is_vscmd_target(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
398 is_vscmd_target_env(target, env_getter).or_else(|| is_vscmd_target_cl(target, env_getter))
399 }
400
401 fn is_vscmd_target_env(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
404 let vscmd_arch = env_getter.get_env("VSCMD_ARG_TGT_ARCH")?;
405 Some(target.as_vs_arch() == vscmd_arch.as_ref())
406 }
407
408 fn is_vscmd_target_cl(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
411 let cmd_target = vscmd_target_cl(env_getter)?;
412 Some(target.as_vs_arch() == cmd_target)
413 }
414
415 fn vscmd_target_cl(env_getter: &dyn EnvGetter) -> Option<&'static str> {
418 let cl_exe = env_getter.get_env("PATH").and_then(|path| {
419 env::split_paths(&path)
420 .map(|p| p.join("cl.exe"))
421 .find(|p| p.exists())
422 })?;
423 let mut cl = Command::new(cl_exe);
424 cl.stderr(std::process::Stdio::piped())
425 .stdout(std::process::Stdio::null());
426
427 let out = cl.output().ok()?;
428 let cl_arch = out
429 .stderr
430 .split(|&b| b == b'\n' || b == b'\r')
431 .next()?
432 .rsplit(|&b| b == b' ')
433 .next()?;
434
435 match cl_arch {
436 b"x64" => Some("x64"),
437 b"x86" => Some("x86"),
438 b"ARM64" => Some("arm64"),
439 b"ARM" => Some("arm"),
440 _ => None,
441 }
442 }
443
444 pub(super) fn find_msvc_environment(
446 tool: &str,
447 target: TargetArch,
448 env_getter: &dyn EnvGetter,
449 ) -> Option<Tool> {
450 if env_getter.get_env("VCINSTALLDIR").is_none()
455 && env_getter.get_env("VSTEL_MSBuildProjectFullPath").is_none()
456 {
457 return None;
458 }
459
460 if is_vscmd_target(target, env_getter) == Some(false) {
463 let vs_install_dir: PathBuf = env_getter.get_env("VSINSTALLDIR")?.into();
465 tool_from_vs15plus_instance(tool, target, &vs_install_dir, env_getter)
466 } else {
467 env_getter
469 .get_env("PATH")
470 .and_then(|path| {
471 env::split_paths(&path)
472 .map(|p| p.join(tool))
473 .find(|p| p.exists())
474 })
475 .map(|path| Tool {
476 tool: path,
477 is_clang_cl: false,
478 env: Vec::new(),
479 })
480 }
481 }
482
483 fn find_msbuild_vs17(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
484 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17", env_getter)
485 }
486
487 #[allow(bare_trait_objects)]
488 fn vs16plus_instances(
489 target: TargetArch,
490 version: &'static str,
491 env_getter: &dyn EnvGetter,
492 ) -> Box<Iterator<Item = PathBuf>> {
493 let instances = if let Some(instances) = vs15plus_instances(target, env_getter) {
494 instances
495 } else {
496 return Box::new(iter::empty());
497 };
498 Box::new(instances.into_iter().filter_map(move |instance| {
499 let installation_name = instance.installation_name()?;
500 if installation_name.starts_with(&format!("VisualStudio/{}.", version))
501 || installation_name.starts_with(&format!("VisualStudioPreview/{}.", version))
502 {
503 Some(instance.installation_path()?)
504 } else {
505 None
506 }
507 }))
508 }
509
510 fn find_tool_in_vs16plus_path(
511 tool: &str,
512 target: TargetArch,
513 version: &'static str,
514 env_getter: &dyn EnvGetter,
515 ) -> Option<Tool> {
516 vs16plus_instances(target, version, env_getter)
517 .filter_map(|path| {
518 let path = path.join(tool);
519 if !path.is_file() {
520 return None;
521 }
522 let mut tool = Tool {
523 tool: path,
524 is_clang_cl: false,
525 env: Vec::new(),
526 };
527 if target == TargetArch::X64 {
528 tool.env.push(("Platform".into(), "X64".into()));
529 }
530 if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
531 tool.env.push(("Platform".into(), "ARM64".into()));
532 }
533 Some(tool)
534 })
535 .next()
536 }
537
538 fn find_msbuild_vs16(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
539 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16", env_getter)
540 }
541
542 pub(super) fn find_llvm_tool(
543 tool: &str,
544 target: TargetArch,
545 env_getter: &dyn EnvGetter,
546 ) -> Option<Tool> {
547 find_llvm_tool_vs17(tool, target, env_getter)
548 }
549
550 fn find_llvm_tool_vs17(
551 tool: &str,
552 target: TargetArch,
553 env_getter: &dyn EnvGetter,
554 ) -> Option<Tool> {
555 vs16plus_instances(target, "17", env_getter)
556 .filter_map(|mut base_path| {
557 base_path.push(r"VC\Tools\LLVM");
558 let host_folder = match host_arch() {
559 X86 => "",
562 X86_64 => "x64",
563 AARCH64 => "ARM64",
564 _ => return None,
565 };
566 if host_folder != "" {
567 base_path.push(host_folder);
569 }
570 base_path.push("bin");
572 base_path.push(tool);
573 let is_clang_cl = tool.contains("clang-cl");
574 base_path.is_file().then(|| Tool {
575 tool: base_path,
576 is_clang_cl,
577 env: Vec::new(),
578 })
579 })
580 .next()
581 }
582
583 fn vs15plus_instances(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<VsInstances> {
596 vs15plus_instances_using_com()
597 .or_else(|| vs15plus_instances_using_vswhere(target, env_getter))
598 }
599
600 fn vs15plus_instances_using_com() -> Option<VsInstances> {
601 com::initialize().ok()?;
602
603 let config = SetupConfiguration::new().ok()?;
604 let enum_setup_instances = config.enum_all_instances().ok()?;
605
606 Some(VsInstances::ComBased(enum_setup_instances))
607 }
608
609 fn vs15plus_instances_using_vswhere(
610 target: TargetArch,
611 env_getter: &dyn EnvGetter,
612 ) -> Option<VsInstances> {
613 let program_files_path = env_getter
614 .get_env("ProgramFiles(x86)")
615 .or_else(|| env_getter.get_env("ProgramFiles"))?;
616
617 let program_files_path = Path::new(program_files_path.as_ref());
618
619 let vswhere_path =
620 program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe");
621
622 if !vswhere_path.exists() {
623 return None;
624 }
625
626 let tools_arch = match target {
627 TargetArch::X86 | TargetArch::X64 => Some("x86.x64"),
628 TargetArch::Arm => Some("ARM"),
629 TargetArch::Arm64 | TargetArch::Arm64ec => Some("ARM64"),
630 };
631
632 let vswhere_output = Command::new(vswhere_path)
633 .args([
634 "-latest",
635 "-products",
636 "*",
637 "-requires",
638 &format!("Microsoft.VisualStudio.Component.VC.Tools.{}", tools_arch?),
639 "-format",
640 "text",
641 "-nologo",
642 ])
643 .stderr(std::process::Stdio::inherit())
644 .output()
645 .ok()?;
646
647 let vs_instances =
648 VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?);
649
650 Some(vs_instances)
651 }
652
653 fn parse_version(version: &str) -> Option<[u16; 4]> {
656 let mut iter = version.split('.').map(u16::from_str).fuse();
657 let mut get_next_number = move || match iter.next() {
658 Some(Ok(version_part)) => Some(version_part),
659 Some(Err(_)) => None,
660 None => Some(0),
661 };
662 Some([
663 get_next_number()?,
664 get_next_number()?,
665 get_next_number()?,
666 get_next_number()?,
667 ])
668 }
669
670 pub(super) fn find_msvc_15plus(
671 tool: &str,
672 target: TargetArch,
673 env_getter: &dyn EnvGetter,
674 ) -> Option<Tool> {
675 let iter = vs15plus_instances(target, env_getter)?;
676 iter.into_iter()
677 .filter_map(|instance| {
678 let version = parse_version(&instance.installation_version()?)?;
679 let instance_path = instance.installation_path()?;
680 let tool = tool_from_vs15plus_instance(tool, target, &instance_path, env_getter)?;
681 Some((version, tool))
682 })
683 .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version))
684 .map(|(_version, tool)| tool)
685 }
686
687 fn find_tool_in_vs15_path(
695 tool: &str,
696 target: TargetArch,
697 env_getter: &dyn EnvGetter,
698 ) -> Option<Tool> {
699 let mut path = match vs15plus_instances(target, env_getter) {
700 Some(instances) => instances
701 .into_iter()
702 .filter_map(|instance| instance.installation_path())
703 .map(|path| path.join(tool))
704 .find(|path| path.is_file()),
705 None => None,
706 };
707
708 if path.is_none() {
709 let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7";
710 path = LOCAL_MACHINE
711 .open(key.as_ref())
712 .ok()
713 .and_then(|key| key.query_str("15.0").ok())
714 .map(|path| PathBuf::from(path).join(tool))
715 .and_then(|path| if path.is_file() { Some(path) } else { None });
716 }
717
718 path.map(|path| {
719 let mut tool = Tool {
720 tool: path,
721 is_clang_cl: false,
722 env: Vec::new(),
723 };
724 if target == TargetArch::X64 {
725 tool.env.push(("Platform".into(), "X64".into()));
726 } else if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
727 tool.env.push(("Platform".into(), "ARM64".into()));
728 }
729 tool
730 })
731 }
732
733 fn tool_from_vs15plus_instance(
734 tool: &str,
735 target: TargetArch,
736 instance_path: &Path,
737 env_getter: &dyn EnvGetter,
738 ) -> Option<Tool> {
739 let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) =
740 vs15plus_vc_paths(target, instance_path, env_getter)?;
741 let tool_path = bin_path.join(tool);
742 if !tool_path.exists() {
743 return None;
744 };
745
746 let mut tool = MsvcTool::new(tool_path);
747 tool.path.push(bin_path.clone());
748 tool.path.push(host_dylib_path);
749 if let Some(alt_lib_path) = alt_lib_path {
750 tool.libs.push(alt_lib_path);
751 }
752 tool.libs.push(lib_path);
753 tool.include.push(include_path);
754
755 if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &root_path) {
756 tool.libs.push(atl_lib_path);
757 tool.include.push(atl_include_path);
758 }
759
760 add_sdks(&mut tool, target, env_getter)?;
761
762 Some(tool.into_tool(env_getter))
763 }
764
765 fn vs15plus_vc_paths(
766 target_arch: TargetArch,
767 instance_path: &Path,
768 env_getter: &dyn EnvGetter,
769 ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> {
770 let version = vs15plus_vc_read_version(instance_path)?;
771
772 let hosts = match host_arch() {
773 X86 => &["X86"],
774 X86_64 => &["X64"],
775 AARCH64 => {
780 if is_amd64_emulation_supported() {
781 &["ARM64", "X64", "X86"][..]
782 } else {
783 &["ARM64", "X86"]
784 }
785 }
786 _ => return None,
787 };
788 let target_dir = target_arch.as_vs_arch();
789 let path = instance_path.join(r"VC\Tools\MSVC").join(version);
791 let (host_path, host) = hosts.iter().find_map(|&x| {
793 let candidate = path.join("bin").join(format!("Host{}", x));
794 if candidate.join(target_dir).exists() {
795 Some((candidate, x))
796 } else {
797 None
798 }
799 })?;
800 let bin_path = host_path.join(target_dir);
803 let host_dylib_path = host_path.join(host.to_lowercase());
807 let lib_fragment = if use_spectre_mitigated_libs(env_getter) {
808 r"lib\spectre"
809 } else {
810 "lib"
811 };
812 let lib_path = path.join(lib_fragment).join(target_dir);
813 let alt_lib_path =
814 (target_arch == TargetArch::Arm64ec).then(|| path.join(lib_fragment).join("arm64ec"));
815 let include_path = path.join("include");
816 Some((
817 path,
818 bin_path,
819 host_dylib_path,
820 lib_path,
821 alt_lib_path,
822 include_path,
823 ))
824 }
825
826 fn vs15plus_vc_read_version(dir: &Path) -> Option<String> {
827 let mut version_path: PathBuf =
829 dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
830 let mut version_file = if let Ok(f) = File::open(&version_path) {
831 f
832 } else {
833 let mut version_file = String::new();
838 version_path.pop();
839 for file in version_path.read_dir().ok()? {
840 let name = file.ok()?.file_name();
841 let name = name.to_str()?;
842 if name.starts_with("Microsoft.VCToolsVersion.v")
843 && name.ends_with(".default.txt")
844 && name > &version_file
845 {
846 version_file.replace_range(.., name);
847 }
848 }
849 if version_file.is_empty() {
850 let tools_dir: PathBuf = dir.join(r"VC\Tools\MSVC");
852 return tools_dir
853 .read_dir()
854 .ok()?
855 .filter_map(|file| {
856 let file = file.ok()?;
857 let name = file.file_name().into_string().ok()?;
858
859 file.path().join("bin").exists().then(|| {
860 let version = parse_version(&name);
861 (name, version)
862 })
863 })
864 .max_by(|(_, a), (_, b)| a.cmp(b))
865 .map(|(version, _)| version);
866 }
867 version_path.push(version_file);
868 File::open(version_path).ok()?
869 };
870
871 let mut version = String::new();
873 version_file.read_to_string(&mut version).ok()?;
874 version.truncate(version.trim_end().len());
875 Some(version)
876 }
877
878 fn use_spectre_mitigated_libs(env_getter: &dyn EnvGetter) -> bool {
879 env_getter
880 .get_env("VSCMD_ARG_VCVARS_SPECTRE")
881 .map(|env| env.as_ref() == "spectre")
882 .unwrap_or_default()
883 }
884
885 fn atl_paths(target: TargetArch, path: &Path) -> Option<(PathBuf, PathBuf)> {
886 let atl_path = path.join("atlmfc");
887 let sub = target.as_vs_arch();
888 if atl_path.exists() {
889 Some((atl_path.join("lib").join(sub), atl_path.join("include")))
890 } else {
891 None
892 }
893 }
894
895 pub(super) fn find_msvc_14(
898 tool: &str,
899 target: TargetArch,
900 env_getter: &dyn EnvGetter,
901 ) -> Option<Tool> {
902 let vcdir = get_vc_dir("14.0")?;
903 let mut tool = get_tool(tool, &vcdir, target)?;
904 add_sdks(&mut tool, target, env_getter)?;
905 Some(tool.into_tool(env_getter))
906 }
907
908 fn add_sdks(tool: &mut MsvcTool, target: TargetArch, env_getter: &dyn EnvGetter) -> Option<()> {
909 let sub = target.as_vs_arch();
910 let (ucrt, ucrt_version) = get_ucrt_dir()?;
911
912 let host = match host_arch() {
913 X86 => "x86",
914 X86_64 => "x64",
915 AARCH64 => "arm64",
916 _ => return None,
917 };
918
919 tool.path
920 .push(ucrt.join("bin").join(&ucrt_version).join(host));
921
922 let ucrt_include = ucrt.join("include").join(&ucrt_version);
923 tool.include.push(ucrt_include.join("ucrt"));
924
925 let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
926 tool.libs.push(ucrt_lib.join("ucrt").join(sub));
927
928 if let Some((sdk, version)) = get_sdk10_dir(env_getter) {
929 tool.path.push(sdk.join("bin").join(host));
930 let sdk_lib = sdk.join("lib").join(&version);
931 tool.libs.push(sdk_lib.join("um").join(sub));
932 let sdk_include = sdk.join("include").join(&version);
933 tool.include.push(sdk_include.join("um"));
934 tool.include.push(sdk_include.join("cppwinrt"));
935 tool.include.push(sdk_include.join("winrt"));
936 tool.include.push(sdk_include.join("shared"));
937 } else if let Some(sdk) = get_sdk81_dir() {
938 tool.path.push(sdk.join("bin").join(host));
939 let sdk_lib = sdk.join("lib").join("winv6.3");
940 tool.libs.push(sdk_lib.join("um").join(sub));
941 let sdk_include = sdk.join("include");
942 tool.include.push(sdk_include.join("um"));
943 tool.include.push(sdk_include.join("winrt"));
944 tool.include.push(sdk_include.join("shared"));
945 }
946
947 Some(())
948 }
949
950 fn add_env(
951 tool: &mut Tool,
952 env: &'static str,
953 paths: Vec<PathBuf>,
954 env_getter: &dyn EnvGetter,
955 ) {
956 let prev = env_getter.get_env(env);
957 let prev = prev.as_ref().map(AsRef::as_ref).unwrap_or_default();
958 let prev = env::split_paths(&prev);
959 let new = paths.into_iter().chain(prev);
960 tool.env
961 .push((env.to_string().into(), env::join_paths(new).unwrap()));
962 }
963
964 fn get_tool(tool: &str, path: &Path, target: TargetArch) -> Option<MsvcTool> {
967 bin_subdir(target)
968 .into_iter()
969 .map(|(sub, host)| {
970 (
971 path.join("bin").join(sub).join(tool),
972 path.join("bin").join(host),
973 )
974 })
975 .filter(|(path, _)| path.is_file())
976 .map(|(path, host)| {
977 let mut tool = MsvcTool::new(path);
978 tool.path.push(host);
979 tool
980 })
981 .filter_map(|mut tool| {
982 let sub = vc_lib_subdir(target);
983 tool.libs.push(path.join("lib").join(sub));
984 tool.include.push(path.join("include"));
985 let atlmfc_path = path.join("atlmfc");
986 if atlmfc_path.exists() {
987 tool.libs.push(atlmfc_path.join("lib").join(sub));
988 tool.include.push(atlmfc_path.join("include"));
989 }
990 Some(tool)
991 })
992 .next()
993 }
994
995 fn get_vc_dir(ver: &str) -> Option<PathBuf> {
998 let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
999 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1000 let path = key.query_str(ver).ok()?;
1001 Some(path.into())
1002 }
1003
1004 pub(super) fn get_ucrt_dir() -> Option<(PathBuf, String)> {
1011 let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
1012 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1013 let root = key.query_str("KitsRoot10").ok()?;
1014 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
1015 let max_libdir = readdir
1016 .filter_map(|dir| dir.ok())
1017 .map(|dir| dir.path())
1018 .filter(|dir| {
1019 dir.components()
1020 .last()
1021 .and_then(|c| c.as_os_str().to_str())
1022 .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
1023 .unwrap_or(false)
1024 })
1025 .max()?;
1026 let version = max_libdir.components().last().unwrap();
1027 let version = version.as_os_str().to_str().unwrap().to_string();
1028 Some((root.into(), version))
1029 }
1030
1031 fn get_sdk10_dir(env_getter: &dyn EnvGetter) -> Option<(PathBuf, String)> {
1043 if let (Some(root), Some(version)) = (
1044 env_getter.get_env("WindowsSdkDir"),
1045 env_getter
1046 .get_env("WindowsSDKVersion")
1047 .as_ref()
1048 .and_then(|version| version.as_ref().to_str()),
1049 ) {
1050 return Some((
1051 PathBuf::from(root),
1052 version.trim_end_matches('\\').to_string(),
1053 ));
1054 }
1055
1056 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
1057 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1058 let root = key.query_str("InstallationFolder").ok()?;
1059 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
1060 let mut dirs = readdir
1061 .filter_map(|dir| dir.ok())
1062 .map(|dir| dir.path())
1063 .collect::<Vec<_>>();
1064 dirs.sort();
1065 let dir = dirs
1066 .into_iter()
1067 .rev()
1068 .find(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())?;
1069 let version = dir.components().last().unwrap();
1070 let version = version.as_os_str().to_str().unwrap().to_string();
1071 Some((root.into(), version))
1072 }
1073
1074 fn get_sdk81_dir() -> Option<PathBuf> {
1079 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
1080 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1081 let root = key.query_str("InstallationFolder").ok()?;
1082 Some(root.into())
1083 }
1084
1085 const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
1086 const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
1087 const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12;
1088 const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
1089 const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
1090 const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64;
1091
1092 fn bin_subdir(target: TargetArch) -> Vec<(&'static str, &'static str)> {
1105 match (target, host_arch()) {
1106 (TargetArch::X86, X86) => vec![("", "")],
1107 (TargetArch::X86, X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
1108 (TargetArch::X64, X86) => vec![("x86_amd64", "")],
1109 (TargetArch::X64, X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
1110 (TargetArch::Arm, X86) => vec![("x86_arm", "")],
1111 (TargetArch::Arm, X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
1112 _ => vec![],
1113 }
1114 }
1115
1116 fn vc_lib_subdir(target: TargetArch) -> &'static str {
1118 match target {
1119 TargetArch::X86 => "",
1120 TargetArch::X64 => "amd64",
1121 TargetArch::Arm => "arm",
1122 TargetArch::Arm64 | TargetArch::Arm64ec => "arm64",
1123 }
1124 }
1125
1126 #[allow(bad_style)]
1127 fn host_arch() -> u16 {
1128 type DWORD = u32;
1129 type WORD = u16;
1130 type LPVOID = *mut u8;
1131 type DWORD_PTR = usize;
1132
1133 #[repr(C)]
1134 struct SYSTEM_INFO {
1135 wProcessorArchitecture: WORD,
1136 _wReserved: WORD,
1137 _dwPageSize: DWORD,
1138 _lpMinimumApplicationAddress: LPVOID,
1139 _lpMaximumApplicationAddress: LPVOID,
1140 _dwActiveProcessorMask: DWORD_PTR,
1141 _dwNumberOfProcessors: DWORD,
1142 _dwProcessorType: DWORD,
1143 _dwAllocationGranularity: DWORD,
1144 _wProcessorLevel: WORD,
1145 _wProcessorRevision: WORD,
1146 }
1147
1148 extern "system" {
1149 fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
1150 }
1151
1152 unsafe {
1153 let mut info = mem::zeroed();
1154 GetNativeSystemInfo(&mut info);
1155 info.wProcessorArchitecture
1156 }
1157 }
1158
1159 #[cfg(test)]
1160 mod tests {
1161 use super::*;
1162 use std::path::Path;
1163 use crate::find_tools::find;
1165
1166 fn host_arch_to_string(host_arch_value: u16) -> &'static str {
1167 match host_arch_value {
1168 X86 => "x86",
1169 X86_64 => "x64",
1170 AARCH64 => "arm64",
1171 _ => panic!("Unsupported host architecture: {}", host_arch_value),
1172 }
1173 }
1174
1175 #[test]
1176 fn test_find_cl_exe() {
1177 let target_architectures = ["x64", "x86", "arm64"];
1182 let mut found_any = false;
1183
1184 let host_arch_value = host_arch();
1186 let host_name = host_arch_to_string(host_arch_value);
1187
1188 for &target_arch in &target_architectures {
1189 if let Some(cmd) = find(target_arch, "cl.exe") {
1190 assert!(
1192 !cmd.get_program().is_empty(),
1193 "cl.exe program path should not be empty"
1194 );
1195 assert!(
1196 Path::new(cmd.get_program()).exists(),
1197 "cl.exe should exist at: {:?}",
1198 cmd.get_program()
1199 );
1200
1201 let path_str = cmd.get_program().to_string_lossy();
1204 let path_str_lower = path_str.to_lowercase();
1205 let expected_host_target_path =
1206 format!("\\bin\\host{host_name}\\{target_arch}");
1207 let expected_host_target_path_unix =
1208 expected_host_target_path.replace("\\", "/");
1209
1210 assert!(
1211 path_str_lower.contains(&expected_host_target_path) || path_str_lower.contains(&expected_host_target_path_unix),
1212 "cl.exe path should contain host-target combination (case-insensitive) '{}' for {} host targeting {}, but found: {}",
1213 expected_host_target_path,
1214 host_name,
1215 target_arch,
1216 path_str
1217 );
1218
1219 found_any = true;
1220 }
1221 }
1222
1223 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");
1224 }
1225
1226 #[test]
1227 #[cfg(not(disable_clang_cl_tests))]
1228 fn test_find_llvm_tools() {
1229 use crate::find_tools::StdEnvGetter;
1231
1232 let target_arch = TargetArch::new("x64").expect("Should support x64 architecture");
1236 let llvm_tools = ["clang.exe", "clang++.exe", "lld.exe", "llvm-ar.exe"];
1237
1238 let host_arch_value = host_arch();
1240 let expected_host_path = match host_arch_value {
1241 X86 => "LLVM\\bin", X86_64 => "LLVM\\x64\\bin", AARCH64 => "LLVM\\ARM64\\bin", _ => panic!("Unsupported host architecture: {}", host_arch_value),
1245 };
1246
1247 let host_name = host_arch_to_string(host_arch_value);
1248
1249 let mut found_tools_count = 0;
1250
1251 for &tool in &llvm_tools {
1252 let env_getter = StdEnvGetter;
1254 let result = find_llvm_tool(tool, target_arch, &env_getter);
1255
1256 match result {
1257 Some(found_tool) => {
1258 found_tools_count += 1;
1259
1260 assert!(
1262 !found_tool.path().as_os_str().is_empty(),
1263 "Found LLVM tool '{}' should have a non-empty path",
1264 tool
1265 );
1266
1267 assert!(
1269 found_tool.path().exists(),
1270 "LLVM tool '{}' path should exist: {:?}",
1271 tool,
1272 found_tool.path()
1273 );
1274
1275 let path_str = found_tool.path().to_string_lossy();
1277 assert!(
1278 path_str.contains(tool.trim_end_matches(".exe")),
1279 "Tool path '{}' should contain tool name '{}'",
1280 path_str,
1281 tool
1282 );
1283
1284 assert!(
1286 path_str.contains(expected_host_path) || path_str.contains(&expected_host_path.replace("\\", "/")),
1287 "LLVM tool should be in host-specific VS LLVM directory '{}' for {} host, but found: {}",
1288 expected_host_path,
1289 host_name,
1290 path_str
1291 );
1292 }
1293 None => {}
1294 }
1295 }
1296
1297 assert!(
1299 found_tools_count > 0,
1300 "Expected to find at least one LLVM tool on CI with Visual Studio + Clang installed for {} host. Found: {}",
1301 host_name,
1302 found_tools_count
1303 );
1304 }
1305 }
1306
1307 fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
1312 let mut max_vers = 0;
1313 let mut max_key = None;
1314 for subkey in key.iter().filter_map(|k| k.ok()) {
1315 let val = subkey
1316 .to_str()
1317 .and_then(|s| s.trim_start_matches('v').replace('.', "").parse().ok());
1318 let val = match val {
1319 Some(s) => s,
1320 None => continue,
1321 };
1322 if val > max_vers {
1323 if let Ok(k) = key.open(&subkey) {
1324 max_vers = val;
1325 max_key = Some((subkey, k));
1326 }
1327 }
1328 }
1329 max_key
1330 }
1331
1332 #[inline(always)]
1333 pub(super) fn has_msbuild_version(version: &str, env_getter: &dyn EnvGetter) -> bool {
1334 match version {
1335 "17.0" => {
1336 find_msbuild_vs17(TargetArch::X64, env_getter).is_some()
1337 || find_msbuild_vs17(TargetArch::X86, env_getter).is_some()
1338 || find_msbuild_vs17(TargetArch::Arm64, env_getter).is_some()
1339 }
1340 "16.0" => {
1341 find_msbuild_vs16(TargetArch::X64, env_getter).is_some()
1342 || find_msbuild_vs16(TargetArch::X86, env_getter).is_some()
1343 || find_msbuild_vs16(TargetArch::Arm64, env_getter).is_some()
1344 }
1345 "15.0" => {
1346 find_msbuild_vs15(TargetArch::X64, env_getter).is_some()
1347 || find_msbuild_vs15(TargetArch::X86, env_getter).is_some()
1348 || find_msbuild_vs15(TargetArch::Arm64, env_getter).is_some()
1349 }
1350 "14.0" => LOCAL_MACHINE
1351 .open(&OsString::from(format!(
1352 "SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}",
1353 version
1354 )))
1355 .is_ok(),
1356 _ => false,
1357 }
1358 }
1359
1360 pub(super) fn find_devenv(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1361 find_devenv_vs15(target, env_getter)
1362 }
1363
1364 fn find_devenv_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1365 find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target, env_getter)
1366 }
1367
1368 pub(super) fn find_msbuild(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1370 if let Some(r) = find_msbuild_vs17(target, env_getter) {
1372 Some(r)
1373 } else if let Some(r) = find_msbuild_vs16(target, env_getter) {
1374 return Some(r);
1375 } else if let Some(r) = find_msbuild_vs15(target, env_getter) {
1376 return Some(r);
1377 } else {
1378 find_old_msbuild(target)
1379 }
1380 }
1381
1382 fn find_msbuild_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1383 find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target, env_getter)
1384 }
1385
1386 fn find_old_msbuild(target: TargetArch) -> Option<Tool> {
1387 let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
1388 LOCAL_MACHINE
1389 .open(key.as_ref())
1390 .ok()
1391 .and_then(|key| {
1392 max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
1393 })
1394 .map(|path| {
1395 let mut path = PathBuf::from(path);
1396 path.push("MSBuild.exe");
1397 let mut tool = Tool {
1398 tool: path,
1399 is_clang_cl: false,
1400 env: Vec::new(),
1401 };
1402 if target == TargetArch::X64 {
1403 tool.env.push(("Platform".into(), "X64".into()));
1404 }
1405 tool
1406 })
1407 }
1408}
1409
1410#[cfg(not(windows))]
1412mod impl_ {
1413 use std::{env, ffi::OsStr, path::PathBuf};
1414
1415 use super::{EnvGetter, TargetArch};
1416 use crate::Tool;
1417
1418 #[inline(always)]
1421 pub(super) fn find_msbuild(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1422 None
1423 }
1424
1425 #[inline(always)]
1428 pub(super) fn find_devenv(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1429 None
1430 }
1431
1432 #[inline(always)]
1434 pub(super) fn find_llvm_tool(
1435 _tool: &str,
1436 _target: TargetArch,
1437 _: &dyn EnvGetter,
1438 ) -> Option<Tool> {
1439 None
1440 }
1441
1442 pub(super) fn find_msvc_environment(
1444 tool: &str,
1445 _target: TargetArch,
1446 env_getter: &dyn EnvGetter,
1447 ) -> Option<Tool> {
1448 let vc_install_dir = env_getter.get_env("VCINSTALLDIR")?;
1450 let vs_install_dir = env_getter.get_env("VSINSTALLDIR")?;
1451
1452 let get_tool = |install_dir: &OsStr| {
1453 env::split_paths(install_dir)
1454 .map(|p| p.join(tool))
1455 .find(|p| p.exists())
1456 .map(|path| Tool {
1457 tool: path,
1458 is_clang_cl: false,
1459 env: Vec::new(),
1460 })
1461 };
1462
1463 get_tool(vc_install_dir.as_ref())
1465 .or_else(|| get_tool(vs_install_dir.as_ref()))
1467 .or_else(|| {
1469 env_getter
1470 .get_env("PATH")
1471 .as_ref()
1472 .map(|path| path.as_ref())
1473 .and_then(get_tool)
1474 })
1475 }
1476
1477 #[inline(always)]
1478 pub(super) fn find_msvc_15plus(
1479 _tool: &str,
1480 _target: TargetArch,
1481 _: &dyn EnvGetter,
1482 ) -> Option<Tool> {
1483 None
1484 }
1485
1486 #[inline(always)]
1489 pub(super) fn find_msvc_14(
1490 _tool: &str,
1491 _target: TargetArch,
1492 _: &dyn EnvGetter,
1493 ) -> Option<Tool> {
1494 None
1495 }
1496
1497 #[inline(always)]
1498 pub(super) fn has_msbuild_version(_version: &str, _: &dyn EnvGetter) -> bool {
1499 false
1500 }
1501
1502 #[inline(always)]
1503 pub(super) fn get_ucrt_dir() -> Option<(PathBuf, String)> {
1504 None
1505 }
1506}