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 #[derive(Default)]
304 struct SdkInfo {
305 libs: Vec<PathBuf>,
306 path: Vec<PathBuf>,
307 include: Vec<PathBuf>,
308 }
309
310 struct LibraryHandle(HMODULE);
311
312 impl LibraryHandle {
313 fn new(name: &[u8]) -> Option<Self> {
314 let handle = unsafe { LoadLibraryA(name.as_ptr() as _) };
315 (!handle.is_null()).then_some(Self(handle))
316 }
317
318 unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> {
327 let symbol = GetProcAddress(self.0, name.as_ptr() as _);
328 symbol.map(|symbol| mem::transmute_copy(&symbol))
329 }
330 }
331
332 type GetMachineTypeAttributesFuncType =
333 unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32;
334 const _: () = {
335 let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes;
339 };
340
341 fn is_amd64_emulation_supported_inner() -> Option<bool> {
342 let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?;
344 let get_machine_type_attributes = unsafe {
346 kernel32
347 .get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0")
348 }?;
349 let mut attributes = Default::default();
350 if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK
351 {
352 Some((attributes & UserEnabled) != 0)
353 } else {
354 Some(false)
355 }
356 }
357
358 fn is_amd64_emulation_supported() -> bool {
359 static LOAD_VALUE: Once = Once::new();
361 static IS_SUPPORTED: AtomicBool = AtomicBool::new(false);
362
363 LOAD_VALUE.call_once(|| {
365 IS_SUPPORTED.store(
366 is_amd64_emulation_supported_inner().unwrap_or(false),
367 Ordering::Relaxed,
368 );
369 });
370 IS_SUPPORTED.load(Ordering::Relaxed)
371 }
372
373 impl MsvcTool {
374 fn new(tool: PathBuf) -> MsvcTool {
375 MsvcTool {
376 tool,
377 libs: Vec::new(),
378 path: Vec::new(),
379 include: Vec::new(),
380 }
381 }
382
383 fn add_sdk(&mut self, sdk_info: SdkInfo) {
384 self.libs.extend(sdk_info.libs);
385 self.path.extend(sdk_info.path);
386 self.include.extend(sdk_info.include);
387 }
388
389 fn into_tool(self, env_getter: &dyn EnvGetter) -> Tool {
390 let MsvcTool {
391 tool,
392 libs,
393 path,
394 include,
395 } = self;
396 let mut tool = Tool {
397 tool,
398 is_clang_cl: false,
399 env: Vec::new(),
400 };
401 add_env(&mut tool, "LIB", libs, env_getter);
402 add_env(&mut tool, "PATH", path, env_getter);
403 add_env(&mut tool, "INCLUDE", include, env_getter);
404 tool
405 }
406 }
407
408 impl SdkInfo {
409 fn find_tool(&self, tool: &str) -> Option<PathBuf> {
410 self.path.iter().map(|p| p.join(tool)).find(|p| p.exists())
411 }
412 }
413
414 fn is_vscmd_target(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
417 is_vscmd_target_env(target, env_getter).or_else(|| is_vscmd_target_cl(target, env_getter))
418 }
419
420 fn is_vscmd_target_env(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
423 let vscmd_arch = env_getter.get_env("VSCMD_ARG_TGT_ARCH")?;
424 Some(target.as_vs_arch() == vscmd_arch.as_ref())
425 }
426
427 fn is_vscmd_target_cl(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
430 let cmd_target = vscmd_target_cl(env_getter)?;
431 Some(target.as_vs_arch() == cmd_target)
432 }
433
434 fn vscmd_target_cl(env_getter: &dyn EnvGetter) -> Option<&'static str> {
437 let cl_exe = env_getter.get_env("PATH").and_then(|path| {
438 env::split_paths(&path)
439 .map(|p| p.join("cl.exe"))
440 .find(|p| p.exists())
441 })?;
442 let mut cl = Command::new(cl_exe);
443 cl.stderr(std::process::Stdio::piped())
444 .stdout(std::process::Stdio::null());
445
446 let out = cl.output().ok()?;
447 let cl_arch = out
448 .stderr
449 .split(|&b| b == b'\n' || b == b'\r')
450 .next()?
451 .rsplit(|&b| b == b' ')
452 .next()?;
453
454 match cl_arch {
455 b"x64" => Some("x64"),
456 b"x86" => Some("x86"),
457 b"ARM64" => Some("arm64"),
458 b"ARM" => Some("arm"),
459 _ => None,
460 }
461 }
462
463 pub(super) fn find_msvc_environment(
465 tool: &str,
466 target: TargetArch,
467 env_getter: &dyn EnvGetter,
468 ) -> Option<Tool> {
469 if env_getter.get_env("VCINSTALLDIR").is_none()
474 && env_getter.get_env("VSTEL_MSBuildProjectFullPath").is_none()
475 {
476 return None;
477 }
478
479 if is_vscmd_target(target, env_getter) == Some(false) {
482 let vs_install_dir: PathBuf = env_getter.get_env("VSINSTALLDIR")?.into();
484 tool_from_vs15plus_instance(tool, target, &vs_install_dir, env_getter)
485 } else {
486 env_getter
488 .get_env("PATH")
489 .and_then(|path| {
490 env::split_paths(&path)
491 .map(|p| p.join(tool))
492 .find(|p| p.exists())
493 })
494 .map(|path| Tool {
495 tool: path,
496 is_clang_cl: false,
497 env: Vec::new(),
498 })
499 }
500 }
501
502 fn find_msbuild_vs17(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
503 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17", env_getter)
504 }
505
506 #[allow(bare_trait_objects)]
507 fn vs16plus_instances(
508 target: TargetArch,
509 version: &'static str,
510 env_getter: &dyn EnvGetter,
511 ) -> Box<Iterator<Item = PathBuf>> {
512 let instances = if let Some(instances) = vs15plus_instances(target, env_getter) {
513 instances
514 } else {
515 return Box::new(iter::empty());
516 };
517 Box::new(instances.into_iter().filter_map(move |instance| {
518 let installation_name = instance.installation_name()?;
519 if installation_name.starts_with(&format!("VisualStudio/{}.", version))
520 || installation_name.starts_with(&format!("VisualStudioPreview/{}.", version))
521 {
522 Some(instance.installation_path()?)
523 } else {
524 None
525 }
526 }))
527 }
528
529 fn find_tool_in_vs16plus_path(
530 tool: &str,
531 target: TargetArch,
532 version: &'static str,
533 env_getter: &dyn EnvGetter,
534 ) -> Option<Tool> {
535 vs16plus_instances(target, version, env_getter)
536 .filter_map(|path| {
537 let path = path.join(tool);
538 if !path.is_file() {
539 return None;
540 }
541 let mut tool = Tool {
542 tool: path,
543 is_clang_cl: false,
544 env: Vec::new(),
545 };
546 if target == TargetArch::X64 {
547 tool.env.push(("Platform".into(), "X64".into()));
548 }
549 if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
550 tool.env.push(("Platform".into(), "ARM64".into()));
551 }
552 Some(tool)
553 })
554 .next()
555 }
556
557 fn find_msbuild_vs16(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
558 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16", env_getter)
559 }
560
561 pub(super) fn find_llvm_tool(
562 tool: &str,
563 target: TargetArch,
564 env_getter: &dyn EnvGetter,
565 ) -> Option<Tool> {
566 find_llvm_tool_vs17(tool, target, env_getter)
567 }
568
569 fn find_llvm_tool_vs17(
570 tool: &str,
571 target: TargetArch,
572 env_getter: &dyn EnvGetter,
573 ) -> Option<Tool> {
574 vs16plus_instances(target, "17", env_getter)
575 .filter_map(|mut base_path| {
576 base_path.push(r"VC\Tools\LLVM");
577 let host_folder = match host_arch() {
578 X86 => "",
581 X86_64 => "x64",
582 AARCH64 => "ARM64",
583 _ => return None,
584 };
585 if host_folder != "" {
586 base_path.push(host_folder);
588 }
589 base_path.push("bin");
591 base_path.push(tool);
592 let is_clang_cl = tool.contains("clang-cl");
593 base_path.is_file().then(|| Tool {
594 tool: base_path,
595 is_clang_cl,
596 env: Vec::new(),
597 })
598 })
599 .next()
600 }
601
602 fn vs15plus_instances(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<VsInstances> {
615 vs15plus_instances_using_com()
616 .or_else(|| vs15plus_instances_using_vswhere(target, env_getter))
617 }
618
619 fn vs15plus_instances_using_com() -> Option<VsInstances> {
620 com::initialize().ok()?;
621
622 let config = SetupConfiguration::new().ok()?;
623 let enum_setup_instances = config.enum_all_instances().ok()?;
624
625 Some(VsInstances::ComBased(enum_setup_instances))
626 }
627
628 fn vs15plus_instances_using_vswhere(
629 target: TargetArch,
630 env_getter: &dyn EnvGetter,
631 ) -> Option<VsInstances> {
632 let program_files_path = env_getter
633 .get_env("ProgramFiles(x86)")
634 .or_else(|| env_getter.get_env("ProgramFiles"))?;
635
636 let program_files_path = Path::new(program_files_path.as_ref());
637
638 let vswhere_path =
639 program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe");
640
641 if !vswhere_path.exists() {
642 return None;
643 }
644
645 let tools_arch = match target {
646 TargetArch::X86 | TargetArch::X64 => Some("x86.x64"),
647 TargetArch::Arm => Some("ARM"),
648 TargetArch::Arm64 | TargetArch::Arm64ec => Some("ARM64"),
649 };
650
651 let vswhere_output = Command::new(vswhere_path)
652 .args([
653 "-latest",
654 "-products",
655 "*",
656 "-requires",
657 &format!("Microsoft.VisualStudio.Component.VC.Tools.{}", tools_arch?),
658 "-format",
659 "text",
660 "-nologo",
661 ])
662 .stderr(std::process::Stdio::inherit())
663 .output()
664 .ok()?;
665
666 let vs_instances =
667 VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?);
668
669 Some(vs_instances)
670 }
671
672 fn parse_version(version: &str) -> Option<[u16; 4]> {
675 let mut iter = version.split('.').map(u16::from_str).fuse();
676 let mut get_next_number = move || match iter.next() {
677 Some(Ok(version_part)) => Some(version_part),
678 Some(Err(_)) => None,
679 None => Some(0),
680 };
681 Some([
682 get_next_number()?,
683 get_next_number()?,
684 get_next_number()?,
685 get_next_number()?,
686 ])
687 }
688
689 pub(super) fn find_msvc_15plus(
690 tool: &str,
691 target: TargetArch,
692 env_getter: &dyn EnvGetter,
693 ) -> Option<Tool> {
694 let iter = vs15plus_instances(target, env_getter)?;
695 iter.into_iter()
696 .filter_map(|instance| {
697 let version = parse_version(&instance.installation_version()?)?;
698 let instance_path = instance.installation_path()?;
699 let tool = tool_from_vs15plus_instance(tool, target, &instance_path, env_getter)?;
700 Some((version, tool))
701 })
702 .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version))
703 .map(|(_version, tool)| tool)
704 }
705
706 fn find_tool_in_vs15_path(
714 tool: &str,
715 target: TargetArch,
716 env_getter: &dyn EnvGetter,
717 ) -> Option<Tool> {
718 let mut path = match vs15plus_instances(target, env_getter) {
719 Some(instances) => instances
720 .into_iter()
721 .filter_map(|instance| instance.installation_path())
722 .map(|path| path.join(tool))
723 .find(|path| path.is_file()),
724 None => None,
725 };
726
727 if path.is_none() {
728 let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7";
729 path = LOCAL_MACHINE
730 .open(key.as_ref())
731 .ok()
732 .and_then(|key| key.query_str("15.0").ok())
733 .map(|path| PathBuf::from(path).join(tool))
734 .and_then(|path| if path.is_file() { Some(path) } else { None });
735 }
736
737 path.map(|path| {
738 let mut tool = Tool {
739 tool: path,
740 is_clang_cl: false,
741 env: Vec::new(),
742 };
743 if target == TargetArch::X64 {
744 tool.env.push(("Platform".into(), "X64".into()));
745 } else if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
746 tool.env.push(("Platform".into(), "ARM64".into()));
747 }
748 tool
749 })
750 }
751
752 fn tool_from_vs15plus_instance(
753 tool: &str,
754 target: TargetArch,
755 instance_path: &Path,
756 env_getter: &dyn EnvGetter,
757 ) -> Option<Tool> {
758 let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) =
759 vs15plus_vc_paths(target, instance_path, env_getter)?;
760 let sdk_info = get_sdks(target, env_getter)?;
761 let mut tool_path = bin_path.join(tool);
762 if !tool_path.exists() {
763 tool_path = sdk_info.find_tool(tool)?;
764 };
765
766 let mut tool = MsvcTool::new(tool_path);
767 tool.path.push(bin_path.clone());
768 tool.path.push(host_dylib_path);
769 if let Some(alt_lib_path) = alt_lib_path {
770 tool.libs.push(alt_lib_path);
771 }
772 tool.libs.push(lib_path);
773 tool.include.push(include_path);
774
775 if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &root_path) {
776 tool.libs.push(atl_lib_path);
777 tool.include.push(atl_include_path);
778 }
779
780 tool.add_sdk(sdk_info);
781
782 Some(tool.into_tool(env_getter))
783 }
784
785 fn vs15plus_vc_paths(
786 target_arch: TargetArch,
787 instance_path: &Path,
788 env_getter: &dyn EnvGetter,
789 ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> {
790 let version = vs15plus_vc_read_version(instance_path)?;
791
792 let hosts = match host_arch() {
793 X86 => &["X86"],
794 X86_64 => &["X64"],
795 AARCH64 => {
800 if is_amd64_emulation_supported() {
801 &["ARM64", "X64", "X86"][..]
802 } else {
803 &["ARM64", "X86"]
804 }
805 }
806 _ => return None,
807 };
808 let target_dir = target_arch.as_vs_arch();
809 let path = instance_path.join(r"VC\Tools\MSVC").join(version);
811 let (host_path, host) = hosts.iter().find_map(|&x| {
813 let candidate = path.join("bin").join(format!("Host{}", x));
814 if candidate.join(target_dir).exists() {
815 Some((candidate, x))
816 } else {
817 None
818 }
819 })?;
820 let bin_path = host_path.join(target_dir);
823 let host_dylib_path = host_path.join(host.to_lowercase());
827 let lib_fragment = if use_spectre_mitigated_libs(env_getter) {
828 r"lib\spectre"
829 } else {
830 "lib"
831 };
832 let lib_path = path.join(lib_fragment).join(target_dir);
833 let alt_lib_path =
834 (target_arch == TargetArch::Arm64ec).then(|| path.join(lib_fragment).join("arm64ec"));
835 let include_path = path.join("include");
836 Some((
837 path,
838 bin_path,
839 host_dylib_path,
840 lib_path,
841 alt_lib_path,
842 include_path,
843 ))
844 }
845
846 fn vs15plus_vc_read_version(dir: &Path) -> Option<String> {
847 let mut version_path: PathBuf =
849 dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
850 let mut version_file = if let Ok(f) = File::open(&version_path) {
851 f
852 } else {
853 let mut version_file = String::new();
858 version_path.pop();
859 for file in version_path.read_dir().ok()? {
860 let name = file.ok()?.file_name();
861 let name = name.to_str()?;
862 if name.starts_with("Microsoft.VCToolsVersion.v")
863 && name.ends_with(".default.txt")
864 && name > &version_file
865 {
866 version_file.replace_range(.., name);
867 }
868 }
869 if version_file.is_empty() {
870 let tools_dir: PathBuf = dir.join(r"VC\Tools\MSVC");
872 return tools_dir
873 .read_dir()
874 .ok()?
875 .filter_map(|file| {
876 let file = file.ok()?;
877 let name = file.file_name().into_string().ok()?;
878
879 file.path().join("bin").exists().then(|| {
880 let version = parse_version(&name);
881 (name, version)
882 })
883 })
884 .max_by(|(_, a), (_, b)| a.cmp(b))
885 .map(|(version, _)| version);
886 }
887 version_path.push(version_file);
888 File::open(version_path).ok()?
889 };
890
891 let mut version = String::new();
893 version_file.read_to_string(&mut version).ok()?;
894 version.truncate(version.trim_end().len());
895 Some(version)
896 }
897
898 fn use_spectre_mitigated_libs(env_getter: &dyn EnvGetter) -> bool {
899 env_getter
900 .get_env("VSCMD_ARG_VCVARS_SPECTRE")
901 .map(|env| env.as_ref() == "spectre")
902 .unwrap_or_default()
903 }
904
905 fn atl_paths(target: TargetArch, path: &Path) -> Option<(PathBuf, PathBuf)> {
906 let atl_path = path.join("atlmfc");
907 let sub = target.as_vs_arch();
908 if atl_path.exists() {
909 Some((atl_path.join("lib").join(sub), atl_path.join("include")))
910 } else {
911 None
912 }
913 }
914
915 pub(super) fn find_msvc_14(
918 tool: &str,
919 target: TargetArch,
920 env_getter: &dyn EnvGetter,
921 ) -> Option<Tool> {
922 let vcdir = get_vc_dir("14.0")?;
923 let sdk_info = get_sdks(target, env_getter)?;
924 let mut tool = get_tool(tool, &vcdir, target, &sdk_info)?;
925 tool.add_sdk(sdk_info);
926 Some(tool.into_tool(env_getter))
927 }
928
929 fn get_sdks(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<SdkInfo> {
930 let sub = target.as_vs_arch();
931 let (ucrt, ucrt_version) = get_ucrt_dir()?;
932
933 let host = match host_arch() {
934 X86 => "x86",
935 X86_64 => "x64",
936 AARCH64 => "arm64",
937 _ => return None,
938 };
939
940 let mut info = SdkInfo::default();
941
942 info.path
943 .push(ucrt.join("bin").join(&ucrt_version).join(host));
944
945 let ucrt_include = ucrt.join("include").join(&ucrt_version);
946 info.include.push(ucrt_include.join("ucrt"));
947
948 let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
949 info.libs.push(ucrt_lib.join("ucrt").join(sub));
950
951 if let Some((sdk, version)) = get_sdk10_dir(env_getter) {
952 info.path.push(sdk.join("bin").join(host));
953 let sdk_lib = sdk.join("lib").join(&version);
954 info.libs.push(sdk_lib.join("um").join(sub));
955 let sdk_include = sdk.join("include").join(&version);
956 info.include.push(sdk_include.join("um"));
957 info.include.push(sdk_include.join("cppwinrt"));
958 info.include.push(sdk_include.join("winrt"));
959 info.include.push(sdk_include.join("shared"));
960 } else if let Some(sdk) = get_sdk81_dir() {
961 info.path.push(sdk.join("bin").join(host));
962 let sdk_lib = sdk.join("lib").join("winv6.3");
963 info.libs.push(sdk_lib.join("um").join(sub));
964 let sdk_include = sdk.join("include");
965 info.include.push(sdk_include.join("um"));
966 info.include.push(sdk_include.join("winrt"));
967 info.include.push(sdk_include.join("shared"));
968 }
969
970 Some(info)
971 }
972
973 fn add_env(
974 tool: &mut Tool,
975 env: &'static str,
976 paths: Vec<PathBuf>,
977 env_getter: &dyn EnvGetter,
978 ) {
979 let prev = env_getter.get_env(env);
980 let prev = prev.as_ref().map(AsRef::as_ref).unwrap_or_default();
981 let prev = env::split_paths(&prev);
982 let new = paths.into_iter().chain(prev);
983 tool.env
984 .push((env.to_string().into(), env::join_paths(new).unwrap()));
985 }
986
987 fn get_tool(
990 tool: &str,
991 path: &Path,
992 target: TargetArch,
993 sdk_info: &SdkInfo,
994 ) -> Option<MsvcTool> {
995 bin_subdir(target)
996 .into_iter()
997 .map(|(sub, host)| {
998 (
999 path.join("bin").join(sub).join(tool),
1000 Some(path.join("bin").join(host)),
1001 )
1002 })
1003 .filter(|(path, _)| path.is_file())
1004 .chain(iter::once_with(|| Some((sdk_info.find_tool(tool)?, None))).flatten())
1005 .map(|(tool_path, host)| {
1006 let mut tool = MsvcTool::new(tool_path);
1007 tool.path.extend(host);
1008 let sub = vc_lib_subdir(target);
1009 tool.libs.push(path.join("lib").join(sub));
1010 tool.include.push(path.join("include"));
1011 let atlmfc_path = path.join("atlmfc");
1012 if atlmfc_path.exists() {
1013 tool.libs.push(atlmfc_path.join("lib").join(sub));
1014 tool.include.push(atlmfc_path.join("include"));
1015 }
1016 tool
1017 })
1018 .next()
1019 }
1020
1021 fn get_vc_dir(ver: &str) -> Option<PathBuf> {
1024 let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
1025 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1026 let path = key.query_str(ver).ok()?;
1027 Some(path.into())
1028 }
1029
1030 pub(super) fn get_ucrt_dir() -> Option<(PathBuf, String)> {
1037 let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
1038 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1039 let root = key.query_str("KitsRoot10").ok()?;
1040 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
1041 let max_libdir = readdir
1042 .filter_map(|dir| dir.ok())
1043 .map(|dir| dir.path())
1044 .filter(|dir| {
1045 dir.components()
1046 .last()
1047 .and_then(|c| c.as_os_str().to_str())
1048 .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
1049 .unwrap_or(false)
1050 })
1051 .max()?;
1052 let version = max_libdir.components().last().unwrap();
1053 let version = version.as_os_str().to_str().unwrap().to_string();
1054 Some((root.into(), version))
1055 }
1056
1057 fn get_sdk10_dir(env_getter: &dyn EnvGetter) -> Option<(PathBuf, String)> {
1069 if let (Some(root), Some(version)) = (
1070 env_getter.get_env("WindowsSdkDir"),
1071 env_getter
1072 .get_env("WindowsSDKVersion")
1073 .as_ref()
1074 .and_then(|version| version.as_ref().to_str()),
1075 ) {
1076 return Some((
1077 PathBuf::from(root),
1078 version.trim_end_matches('\\').to_string(),
1079 ));
1080 }
1081
1082 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
1083 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1084 let root = key.query_str("InstallationFolder").ok()?;
1085 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
1086 let mut dirs = readdir
1087 .filter_map(|dir| dir.ok())
1088 .map(|dir| dir.path())
1089 .collect::<Vec<_>>();
1090 dirs.sort();
1091 let dir = dirs
1092 .into_iter()
1093 .rev()
1094 .find(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())?;
1095 let version = dir.components().last().unwrap();
1096 let version = version.as_os_str().to_str().unwrap().to_string();
1097 Some((root.into(), version))
1098 }
1099
1100 fn get_sdk81_dir() -> Option<PathBuf> {
1105 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
1106 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1107 let root = key.query_str("InstallationFolder").ok()?;
1108 Some(root.into())
1109 }
1110
1111 const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
1112 const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
1113 const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12;
1114 const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
1115 const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
1116 const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64;
1117
1118 fn bin_subdir(target: TargetArch) -> Vec<(&'static str, &'static str)> {
1131 match (target, host_arch()) {
1132 (TargetArch::X86, X86) => vec![("", "")],
1133 (TargetArch::X86, X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
1134 (TargetArch::X64, X86) => vec![("x86_amd64", "")],
1135 (TargetArch::X64, X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
1136 (TargetArch::Arm, X86) => vec![("x86_arm", "")],
1137 (TargetArch::Arm, X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
1138 _ => vec![],
1139 }
1140 }
1141
1142 fn vc_lib_subdir(target: TargetArch) -> &'static str {
1144 match target {
1145 TargetArch::X86 => "",
1146 TargetArch::X64 => "amd64",
1147 TargetArch::Arm => "arm",
1148 TargetArch::Arm64 | TargetArch::Arm64ec => "arm64",
1149 }
1150 }
1151
1152 #[allow(bad_style)]
1153 fn host_arch() -> u16 {
1154 type DWORD = u32;
1155 type WORD = u16;
1156 type LPVOID = *mut u8;
1157 type DWORD_PTR = usize;
1158
1159 #[repr(C)]
1160 struct SYSTEM_INFO {
1161 wProcessorArchitecture: WORD,
1162 _wReserved: WORD,
1163 _dwPageSize: DWORD,
1164 _lpMinimumApplicationAddress: LPVOID,
1165 _lpMaximumApplicationAddress: LPVOID,
1166 _dwActiveProcessorMask: DWORD_PTR,
1167 _dwNumberOfProcessors: DWORD,
1168 _dwProcessorType: DWORD,
1169 _dwAllocationGranularity: DWORD,
1170 _wProcessorLevel: WORD,
1171 _wProcessorRevision: WORD,
1172 }
1173
1174 extern "system" {
1175 fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
1176 }
1177
1178 unsafe {
1179 let mut info = mem::zeroed();
1180 GetNativeSystemInfo(&mut info);
1181 info.wProcessorArchitecture
1182 }
1183 }
1184
1185 #[cfg(test)]
1186 mod tests {
1187 use super::*;
1188 use std::path::Path;
1189 use crate::find_tools::find;
1191
1192 fn host_arch_to_string(host_arch_value: u16) -> &'static str {
1193 match host_arch_value {
1194 X86 => "x86",
1195 X86_64 => "x64",
1196 AARCH64 => "arm64",
1197 _ => panic!("Unsupported host architecture: {}", host_arch_value),
1198 }
1199 }
1200
1201 #[test]
1202 fn test_find_cl_exe() {
1203 let target_architectures = ["x64", "x86", "arm64"];
1208 let mut found_any = false;
1209
1210 let host_arch_value = host_arch();
1212 let host_name = host_arch_to_string(host_arch_value);
1213
1214 for &target_arch in &target_architectures {
1215 if let Some(cmd) = find(target_arch, "cl.exe") {
1216 assert!(
1218 !cmd.get_program().is_empty(),
1219 "cl.exe program path should not be empty"
1220 );
1221 assert!(
1222 Path::new(cmd.get_program()).exists(),
1223 "cl.exe should exist at: {:?}",
1224 cmd.get_program()
1225 );
1226
1227 let path_str = cmd.get_program().to_string_lossy();
1230 let path_str_lower = path_str.to_lowercase();
1231 let expected_host_target_path =
1232 format!("\\bin\\host{host_name}\\{target_arch}");
1233 let expected_host_target_path_unix =
1234 expected_host_target_path.replace("\\", "/");
1235
1236 assert!(
1237 path_str_lower.contains(&expected_host_target_path) || path_str_lower.contains(&expected_host_target_path_unix),
1238 "cl.exe path should contain host-target combination (case-insensitive) '{}' for {} host targeting {}, but found: {}",
1239 expected_host_target_path,
1240 host_name,
1241 target_arch,
1242 path_str
1243 );
1244
1245 found_any = true;
1246 }
1247 }
1248
1249 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");
1250 }
1251
1252 #[test]
1253 #[cfg(not(disable_clang_cl_tests))]
1254 fn test_find_llvm_tools() {
1255 use crate::find_tools::StdEnvGetter;
1257
1258 let target_arch = TargetArch::new("x64").expect("Should support x64 architecture");
1262 let llvm_tools = ["clang.exe", "clang++.exe", "lld.exe", "llvm-ar.exe"];
1263
1264 let host_arch_value = host_arch();
1266 let expected_host_path = match host_arch_value {
1267 X86 => "LLVM\\bin", X86_64 => "LLVM\\x64\\bin", AARCH64 => "LLVM\\ARM64\\bin", _ => panic!("Unsupported host architecture: {}", host_arch_value),
1271 };
1272
1273 let host_name = host_arch_to_string(host_arch_value);
1274
1275 let mut found_tools_count = 0;
1276
1277 for &tool in &llvm_tools {
1278 let env_getter = StdEnvGetter;
1280 let result = find_llvm_tool(tool, target_arch, &env_getter);
1281
1282 match result {
1283 Some(found_tool) => {
1284 found_tools_count += 1;
1285
1286 assert!(
1288 !found_tool.path().as_os_str().is_empty(),
1289 "Found LLVM tool '{}' should have a non-empty path",
1290 tool
1291 );
1292
1293 assert!(
1295 found_tool.path().exists(),
1296 "LLVM tool '{}' path should exist: {:?}",
1297 tool,
1298 found_tool.path()
1299 );
1300
1301 let path_str = found_tool.path().to_string_lossy();
1303 assert!(
1304 path_str.contains(tool.trim_end_matches(".exe")),
1305 "Tool path '{}' should contain tool name '{}'",
1306 path_str,
1307 tool
1308 );
1309
1310 assert!(
1312 path_str.contains(expected_host_path) || path_str.contains(&expected_host_path.replace("\\", "/")),
1313 "LLVM tool should be in host-specific VS LLVM directory '{}' for {} host, but found: {}",
1314 expected_host_path,
1315 host_name,
1316 path_str
1317 );
1318 }
1319 None => {}
1320 }
1321 }
1322
1323 assert!(
1325 found_tools_count > 0,
1326 "Expected to find at least one LLVM tool on CI with Visual Studio + Clang installed for {} host. Found: {}",
1327 host_name,
1328 found_tools_count
1329 );
1330 }
1331 }
1332
1333 fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
1338 let mut max_vers = 0;
1339 let mut max_key = None;
1340 for subkey in key.iter().filter_map(|k| k.ok()) {
1341 let val = subkey
1342 .to_str()
1343 .and_then(|s| s.trim_start_matches('v').replace('.', "").parse().ok());
1344 let val = match val {
1345 Some(s) => s,
1346 None => continue,
1347 };
1348 if val > max_vers {
1349 if let Ok(k) = key.open(&subkey) {
1350 max_vers = val;
1351 max_key = Some((subkey, k));
1352 }
1353 }
1354 }
1355 max_key
1356 }
1357
1358 #[inline(always)]
1359 pub(super) fn has_msbuild_version(version: &str, env_getter: &dyn EnvGetter) -> bool {
1360 match version {
1361 "17.0" => {
1362 find_msbuild_vs17(TargetArch::X64, env_getter).is_some()
1363 || find_msbuild_vs17(TargetArch::X86, env_getter).is_some()
1364 || find_msbuild_vs17(TargetArch::Arm64, env_getter).is_some()
1365 }
1366 "16.0" => {
1367 find_msbuild_vs16(TargetArch::X64, env_getter).is_some()
1368 || find_msbuild_vs16(TargetArch::X86, env_getter).is_some()
1369 || find_msbuild_vs16(TargetArch::Arm64, env_getter).is_some()
1370 }
1371 "15.0" => {
1372 find_msbuild_vs15(TargetArch::X64, env_getter).is_some()
1373 || find_msbuild_vs15(TargetArch::X86, env_getter).is_some()
1374 || find_msbuild_vs15(TargetArch::Arm64, env_getter).is_some()
1375 }
1376 "14.0" => LOCAL_MACHINE
1377 .open(&OsString::from(format!(
1378 "SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}",
1379 version
1380 )))
1381 .is_ok(),
1382 _ => false,
1383 }
1384 }
1385
1386 pub(super) fn find_devenv(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1387 find_devenv_vs15(target, env_getter)
1388 }
1389
1390 fn find_devenv_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1391 find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target, env_getter)
1392 }
1393
1394 pub(super) fn find_msbuild(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1396 if let Some(r) = find_msbuild_vs17(target, env_getter) {
1398 Some(r)
1399 } else if let Some(r) = find_msbuild_vs16(target, env_getter) {
1400 return Some(r);
1401 } else if let Some(r) = find_msbuild_vs15(target, env_getter) {
1402 return Some(r);
1403 } else {
1404 find_old_msbuild(target)
1405 }
1406 }
1407
1408 fn find_msbuild_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1409 find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target, env_getter)
1410 }
1411
1412 fn find_old_msbuild(target: TargetArch) -> Option<Tool> {
1413 let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
1414 LOCAL_MACHINE
1415 .open(key.as_ref())
1416 .ok()
1417 .and_then(|key| {
1418 max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
1419 })
1420 .map(|path| {
1421 let mut path = PathBuf::from(path);
1422 path.push("MSBuild.exe");
1423 let mut tool = Tool {
1424 tool: path,
1425 is_clang_cl: false,
1426 env: Vec::new(),
1427 };
1428 if target == TargetArch::X64 {
1429 tool.env.push(("Platform".into(), "X64".into()));
1430 }
1431 tool
1432 })
1433 }
1434}
1435
1436#[cfg(not(windows))]
1438mod impl_ {
1439 use std::{env, ffi::OsStr, path::PathBuf};
1440
1441 use super::{EnvGetter, TargetArch};
1442 use crate::Tool;
1443
1444 #[inline(always)]
1447 pub(super) fn find_msbuild(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1448 None
1449 }
1450
1451 #[inline(always)]
1454 pub(super) fn find_devenv(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1455 None
1456 }
1457
1458 #[inline(always)]
1460 pub(super) fn find_llvm_tool(
1461 _tool: &str,
1462 _target: TargetArch,
1463 _: &dyn EnvGetter,
1464 ) -> Option<Tool> {
1465 None
1466 }
1467
1468 pub(super) fn find_msvc_environment(
1470 tool: &str,
1471 _target: TargetArch,
1472 env_getter: &dyn EnvGetter,
1473 ) -> Option<Tool> {
1474 let vc_install_dir = env_getter.get_env("VCINSTALLDIR")?;
1476 let vs_install_dir = env_getter.get_env("VSINSTALLDIR")?;
1477
1478 let get_tool = |install_dir: &OsStr| {
1479 env::split_paths(install_dir)
1480 .map(|p| p.join(tool))
1481 .find(|p| p.exists())
1482 .map(|path| Tool {
1483 tool: path,
1484 is_clang_cl: false,
1485 env: Vec::new(),
1486 })
1487 };
1488
1489 get_tool(vc_install_dir.as_ref())
1491 .or_else(|| get_tool(vs_install_dir.as_ref()))
1493 .or_else(|| {
1495 env_getter
1496 .get_env("PATH")
1497 .as_ref()
1498 .map(|path| path.as_ref())
1499 .and_then(get_tool)
1500 })
1501 }
1502
1503 #[inline(always)]
1504 pub(super) fn find_msvc_15plus(
1505 _tool: &str,
1506 _target: TargetArch,
1507 _: &dyn EnvGetter,
1508 ) -> Option<Tool> {
1509 None
1510 }
1511
1512 #[inline(always)]
1515 pub(super) fn find_msvc_14(
1516 _tool: &str,
1517 _target: TargetArch,
1518 _: &dyn EnvGetter,
1519 ) -> Option<Tool> {
1520 None
1521 }
1522
1523 #[inline(always)]
1524 pub(super) fn has_msbuild_version(_version: &str, _: &dyn EnvGetter) -> bool {
1525 false
1526 }
1527
1528 #[inline(always)]
1529 pub(super) fn get_ucrt_dir() -> Option<(PathBuf, String)> {
1530 None
1531 }
1532}