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
259#[cfg(windows)]
261mod impl_ {
262 use crate::com;
263 use crate::registry::{RegistryKey, LOCAL_MACHINE};
264 use crate::setup_config::SetupConfiguration;
265 use crate::vs_instances::{VsInstances, VswhereInstance};
266 use crate::windows_sys::{
267 GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE,
268 IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK,
269 };
270 use std::convert::TryFrom;
271 use std::env;
272 use std::ffi::OsString;
273 use std::fs::File;
274 use std::io::Read;
275 use std::iter;
276 use std::mem;
277 use std::path::{Path, PathBuf};
278 use std::process::Command;
279 use std::str::FromStr;
280 use std::sync::atomic::{AtomicBool, Ordering};
281 use std::sync::Once;
282
283 use super::{EnvGetter, TargetArch};
284 use crate::Tool;
285
286 struct MsvcTool {
287 tool: PathBuf,
288 libs: Vec<PathBuf>,
289 path: Vec<PathBuf>,
290 include: Vec<PathBuf>,
291 }
292
293 struct LibraryHandle(HMODULE);
294
295 impl LibraryHandle {
296 fn new(name: &[u8]) -> Option<Self> {
297 let handle = unsafe { LoadLibraryA(name.as_ptr() as _) };
298 (!handle.is_null()).then_some(Self(handle))
299 }
300
301 unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> {
310 let symbol = GetProcAddress(self.0, name.as_ptr() as _);
311 symbol.map(|symbol| mem::transmute_copy(&symbol))
312 }
313 }
314
315 type GetMachineTypeAttributesFuncType =
316 unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32;
317 const _: () = {
318 let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes;
322 };
323
324 fn is_amd64_emulation_supported_inner() -> Option<bool> {
325 let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?;
327 let get_machine_type_attributes = unsafe {
329 kernel32
330 .get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0")
331 }?;
332 let mut attributes = Default::default();
333 if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK
334 {
335 Some((attributes & UserEnabled) != 0)
336 } else {
337 Some(false)
338 }
339 }
340
341 fn is_amd64_emulation_supported() -> bool {
342 static LOAD_VALUE: Once = Once::new();
344 static IS_SUPPORTED: AtomicBool = AtomicBool::new(false);
345
346 LOAD_VALUE.call_once(|| {
348 IS_SUPPORTED.store(
349 is_amd64_emulation_supported_inner().unwrap_or(false),
350 Ordering::Relaxed,
351 );
352 });
353 IS_SUPPORTED.load(Ordering::Relaxed)
354 }
355
356 impl MsvcTool {
357 fn new(tool: PathBuf) -> MsvcTool {
358 MsvcTool {
359 tool,
360 libs: Vec::new(),
361 path: Vec::new(),
362 include: Vec::new(),
363 }
364 }
365
366 fn into_tool(self, env_getter: &dyn EnvGetter) -> Tool {
367 let MsvcTool {
368 tool,
369 libs,
370 path,
371 include,
372 } = self;
373 let mut tool = Tool {
374 tool,
375 is_clang_cl: false,
376 env: Vec::new(),
377 };
378 add_env(&mut tool, "LIB", libs, env_getter);
379 add_env(&mut tool, "PATH", path, env_getter);
380 add_env(&mut tool, "INCLUDE", include, env_getter);
381 tool
382 }
383 }
384
385 fn is_vscmd_target(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
388 is_vscmd_target_env(target, env_getter).or_else(|| is_vscmd_target_cl(target, env_getter))
389 }
390
391 fn is_vscmd_target_env(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
394 let vscmd_arch = env_getter.get_env("VSCMD_ARG_TGT_ARCH")?;
395 Some(target.as_vs_arch() == vscmd_arch.as_ref())
396 }
397
398 fn is_vscmd_target_cl(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> {
401 let cmd_target = vscmd_target_cl(env_getter)?;
402 Some(target.as_vs_arch() == cmd_target)
403 }
404
405 fn vscmd_target_cl(env_getter: &dyn EnvGetter) -> Option<&'static str> {
408 let cl_exe = env_getter.get_env("PATH").and_then(|path| {
409 env::split_paths(&path)
410 .map(|p| p.join("cl.exe"))
411 .find(|p| p.exists())
412 })?;
413 let mut cl = Command::new(cl_exe);
414 cl.stderr(std::process::Stdio::piped())
415 .stdout(std::process::Stdio::null());
416
417 let out = cl.output().ok()?;
418 let cl_arch = out
419 .stderr
420 .split(|&b| b == b'\n' || b == b'\r')
421 .next()?
422 .rsplit(|&b| b == b' ')
423 .next()?;
424
425 match cl_arch {
426 b"x64" => Some("x64"),
427 b"x86" => Some("x86"),
428 b"ARM64" => Some("arm64"),
429 b"ARM" => Some("arm"),
430 _ => None,
431 }
432 }
433
434 pub(super) fn find_msvc_environment(
436 tool: &str,
437 target: TargetArch,
438 env_getter: &dyn EnvGetter,
439 ) -> Option<Tool> {
440 if env_getter.get_env("VCINSTALLDIR").is_none()
445 && env_getter.get_env("VSTEL_MSBuildProjectFullPath").is_none()
446 {
447 return None;
448 }
449
450 if is_vscmd_target(target, env_getter) == Some(false) {
453 let vs_install_dir: PathBuf = env_getter.get_env("VSINSTALLDIR")?.into();
455 tool_from_vs15plus_instance(tool, target, &vs_install_dir, env_getter)
456 } else {
457 env_getter
459 .get_env("PATH")
460 .and_then(|path| {
461 env::split_paths(&path)
462 .map(|p| p.join(tool))
463 .find(|p| p.exists())
464 })
465 .map(|path| Tool {
466 tool: path,
467 is_clang_cl: false,
468 env: Vec::new(),
469 })
470 }
471 }
472
473 fn find_msbuild_vs17(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
474 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17", env_getter)
475 }
476
477 #[allow(bare_trait_objects)]
478 fn vs16plus_instances(
479 target: TargetArch,
480 version: &'static str,
481 env_getter: &dyn EnvGetter,
482 ) -> Box<Iterator<Item = PathBuf>> {
483 let instances = if let Some(instances) = vs15plus_instances(target, env_getter) {
484 instances
485 } else {
486 return Box::new(iter::empty());
487 };
488 Box::new(instances.into_iter().filter_map(move |instance| {
489 let installation_name = instance.installation_name()?;
490 if installation_name.starts_with(&format!("VisualStudio/{}.", version))
491 || installation_name.starts_with(&format!("VisualStudioPreview/{}.", version))
492 {
493 Some(instance.installation_path()?)
494 } else {
495 None
496 }
497 }))
498 }
499
500 fn find_tool_in_vs16plus_path(
501 tool: &str,
502 target: TargetArch,
503 version: &'static str,
504 env_getter: &dyn EnvGetter,
505 ) -> Option<Tool> {
506 vs16plus_instances(target, version, env_getter)
507 .filter_map(|path| {
508 let path = path.join(tool);
509 if !path.is_file() {
510 return None;
511 }
512 let mut tool = Tool {
513 tool: path,
514 is_clang_cl: false,
515 env: Vec::new(),
516 };
517 if target == TargetArch::X64 {
518 tool.env.push(("Platform".into(), "X64".into()));
519 }
520 if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
521 tool.env.push(("Platform".into(), "ARM64".into()));
522 }
523 Some(tool)
524 })
525 .next()
526 }
527
528 fn find_msbuild_vs16(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
529 find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16", env_getter)
530 }
531
532 pub(super) fn find_llvm_tool(
533 tool: &str,
534 target: TargetArch,
535 env_getter: &dyn EnvGetter,
536 ) -> Option<Tool> {
537 find_llvm_tool_vs17(tool, target, env_getter)
538 }
539
540 fn find_llvm_tool_vs17(
541 tool: &str,
542 target: TargetArch,
543 env_getter: &dyn EnvGetter,
544 ) -> Option<Tool> {
545 vs16plus_instances(target, "17", env_getter)
546 .filter_map(|mut base_path| {
547 base_path.push(r"VC\Tools\LLVM");
548 let host_folder = match host_arch() {
549 X86 => "",
552 X86_64 => "x64",
553 AARCH64 => "ARM64",
554 _ => return None,
555 };
556 if host_folder != "" {
557 base_path.push(host_folder);
559 }
560 base_path.push("bin");
562 base_path.push(tool);
563 let is_clang_cl = tool.contains("clang-cl");
564 base_path.is_file().then(|| Tool {
565 tool: base_path,
566 is_clang_cl,
567 env: Vec::new(),
568 })
569 })
570 .next()
571 }
572
573 fn vs15plus_instances(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<VsInstances> {
586 vs15plus_instances_using_com()
587 .or_else(|| vs15plus_instances_using_vswhere(target, env_getter))
588 }
589
590 fn vs15plus_instances_using_com() -> Option<VsInstances> {
591 com::initialize().ok()?;
592
593 let config = SetupConfiguration::new().ok()?;
594 let enum_setup_instances = config.enum_all_instances().ok()?;
595
596 Some(VsInstances::ComBased(enum_setup_instances))
597 }
598
599 fn vs15plus_instances_using_vswhere(
600 target: TargetArch,
601 env_getter: &dyn EnvGetter,
602 ) -> Option<VsInstances> {
603 let program_files_path = env_getter
604 .get_env("ProgramFiles(x86)")
605 .or_else(|| env_getter.get_env("ProgramFiles"))?;
606
607 let program_files_path = Path::new(program_files_path.as_ref());
608
609 let vswhere_path =
610 program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe");
611
612 if !vswhere_path.exists() {
613 return None;
614 }
615
616 let tools_arch = match target {
617 TargetArch::X86 | TargetArch::X64 => Some("x86.x64"),
618 TargetArch::Arm => Some("ARM"),
619 TargetArch::Arm64 | TargetArch::Arm64ec => Some("ARM64"),
620 };
621
622 let vswhere_output = Command::new(vswhere_path)
623 .args([
624 "-latest",
625 "-products",
626 "*",
627 "-requires",
628 &format!("Microsoft.VisualStudio.Component.VC.Tools.{}", tools_arch?),
629 "-format",
630 "text",
631 "-nologo",
632 ])
633 .stderr(std::process::Stdio::inherit())
634 .output()
635 .ok()?;
636
637 let vs_instances =
638 VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?);
639
640 Some(vs_instances)
641 }
642
643 fn parse_version(version: &str) -> Option<[u16; 4]> {
646 let mut iter = version.split('.').map(u16::from_str).fuse();
647 let mut get_next_number = move || match iter.next() {
648 Some(Ok(version_part)) => Some(version_part),
649 Some(Err(_)) => None,
650 None => Some(0),
651 };
652 Some([
653 get_next_number()?,
654 get_next_number()?,
655 get_next_number()?,
656 get_next_number()?,
657 ])
658 }
659
660 pub(super) fn find_msvc_15plus(
661 tool: &str,
662 target: TargetArch,
663 env_getter: &dyn EnvGetter,
664 ) -> Option<Tool> {
665 let iter = vs15plus_instances(target, env_getter)?;
666 iter.into_iter()
667 .filter_map(|instance| {
668 let version = parse_version(&instance.installation_version()?)?;
669 let instance_path = instance.installation_path()?;
670 let tool = tool_from_vs15plus_instance(tool, target, &instance_path, env_getter)?;
671 Some((version, tool))
672 })
673 .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version))
674 .map(|(_version, tool)| tool)
675 }
676
677 fn find_tool_in_vs15_path(
685 tool: &str,
686 target: TargetArch,
687 env_getter: &dyn EnvGetter,
688 ) -> Option<Tool> {
689 let mut path = match vs15plus_instances(target, env_getter) {
690 Some(instances) => instances
691 .into_iter()
692 .filter_map(|instance| instance.installation_path())
693 .map(|path| path.join(tool))
694 .find(|path| path.is_file()),
695 None => None,
696 };
697
698 if path.is_none() {
699 let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7";
700 path = LOCAL_MACHINE
701 .open(key.as_ref())
702 .ok()
703 .and_then(|key| key.query_str("15.0").ok())
704 .map(|path| PathBuf::from(path).join(tool))
705 .and_then(|path| if path.is_file() { Some(path) } else { None });
706 }
707
708 path.map(|path| {
709 let mut tool = Tool {
710 tool: path,
711 is_clang_cl: false,
712 env: Vec::new(),
713 };
714 if target == TargetArch::X64 {
715 tool.env.push(("Platform".into(), "X64".into()));
716 } else if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) {
717 tool.env.push(("Platform".into(), "ARM64".into()));
718 }
719 tool
720 })
721 }
722
723 fn tool_from_vs15plus_instance(
724 tool: &str,
725 target: TargetArch,
726 instance_path: &Path,
727 env_getter: &dyn EnvGetter,
728 ) -> Option<Tool> {
729 let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) =
730 vs15plus_vc_paths(target, instance_path, env_getter)?;
731 let tool_path = bin_path.join(tool);
732 if !tool_path.exists() {
733 return None;
734 };
735
736 let mut tool = MsvcTool::new(tool_path);
737 tool.path.push(bin_path.clone());
738 tool.path.push(host_dylib_path);
739 if let Some(alt_lib_path) = alt_lib_path {
740 tool.libs.push(alt_lib_path);
741 }
742 tool.libs.push(lib_path);
743 tool.include.push(include_path);
744
745 if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &root_path) {
746 tool.libs.push(atl_lib_path);
747 tool.include.push(atl_include_path);
748 }
749
750 add_sdks(&mut tool, target, env_getter)?;
751
752 Some(tool.into_tool(env_getter))
753 }
754
755 fn vs15plus_vc_paths(
756 target_arch: TargetArch,
757 instance_path: &Path,
758 env_getter: &dyn EnvGetter,
759 ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> {
760 let version = vs15plus_vc_read_version(instance_path)?;
761
762 let hosts = match host_arch() {
763 X86 => &["X86"],
764 X86_64 => &["X64"],
765 AARCH64 => {
770 if is_amd64_emulation_supported() {
771 &["ARM64", "X64", "X86"][..]
772 } else {
773 &["ARM64", "X86"]
774 }
775 }
776 _ => return None,
777 };
778 let target_dir = target_arch.as_vs_arch();
779 let path = instance_path.join(r"VC\Tools\MSVC").join(version);
781 let (host_path, host) = hosts.iter().find_map(|&x| {
783 let candidate = path.join("bin").join(format!("Host{}", x));
784 if candidate.join(target_dir).exists() {
785 Some((candidate, x))
786 } else {
787 None
788 }
789 })?;
790 let bin_path = host_path.join(target_dir);
793 let host_dylib_path = host_path.join(host.to_lowercase());
797 let lib_fragment = if use_spectre_mitigated_libs(env_getter) {
798 r"lib\spectre"
799 } else {
800 "lib"
801 };
802 let lib_path = path.join(lib_fragment).join(target_dir);
803 let alt_lib_path =
804 (target_arch == TargetArch::Arm64ec).then(|| path.join(lib_fragment).join("arm64ec"));
805 let include_path = path.join("include");
806 Some((
807 path,
808 bin_path,
809 host_dylib_path,
810 lib_path,
811 alt_lib_path,
812 include_path,
813 ))
814 }
815
816 fn vs15plus_vc_read_version(dir: &Path) -> Option<String> {
817 let mut version_path: PathBuf =
819 dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
820 let mut version_file = if let Ok(f) = File::open(&version_path) {
821 f
822 } else {
823 let mut version_file = String::new();
828 version_path.pop();
829 for file in version_path.read_dir().ok()? {
830 let name = file.ok()?.file_name();
831 let name = name.to_str()?;
832 if name.starts_with("Microsoft.VCToolsVersion.v")
833 && name.ends_with(".default.txt")
834 && name > &version_file
835 {
836 version_file.replace_range(.., name);
837 }
838 }
839 if version_file.is_empty() {
840 let tools_dir: PathBuf = dir.join(r"VC\Tools\MSVC");
842 return tools_dir
843 .read_dir()
844 .ok()?
845 .filter_map(|file| {
846 let file = file.ok()?;
847 let name = file.file_name().into_string().ok()?;
848
849 file.path().join("bin").exists().then(|| {
850 let version = parse_version(&name);
851 (name, version)
852 })
853 })
854 .max_by(|(_, a), (_, b)| a.cmp(b))
855 .map(|(version, _)| version);
856 }
857 version_path.push(version_file);
858 File::open(version_path).ok()?
859 };
860
861 let mut version = String::new();
863 version_file.read_to_string(&mut version).ok()?;
864 version.truncate(version.trim_end().len());
865 Some(version)
866 }
867
868 fn use_spectre_mitigated_libs(env_getter: &dyn EnvGetter) -> bool {
869 env_getter
870 .get_env("VSCMD_ARG_VCVARS_SPECTRE")
871 .map(|env| env.as_ref() == "spectre")
872 .unwrap_or_default()
873 }
874
875 fn atl_paths(target: TargetArch, path: &Path) -> Option<(PathBuf, PathBuf)> {
876 let atl_path = path.join("atlmfc");
877 let sub = target.as_vs_arch();
878 if atl_path.exists() {
879 Some((atl_path.join("lib").join(sub), atl_path.join("include")))
880 } else {
881 None
882 }
883 }
884
885 pub(super) fn find_msvc_14(
888 tool: &str,
889 target: TargetArch,
890 env_getter: &dyn EnvGetter,
891 ) -> Option<Tool> {
892 let vcdir = get_vc_dir("14.0")?;
893 let mut tool = get_tool(tool, &vcdir, target)?;
894 add_sdks(&mut tool, target, env_getter)?;
895 Some(tool.into_tool(env_getter))
896 }
897
898 fn add_sdks(tool: &mut MsvcTool, target: TargetArch, env_getter: &dyn EnvGetter) -> Option<()> {
899 let sub = target.as_vs_arch();
900 let (ucrt, ucrt_version) = get_ucrt_dir()?;
901
902 let host = match host_arch() {
903 X86 => "x86",
904 X86_64 => "x64",
905 AARCH64 => "arm64",
906 _ => return None,
907 };
908
909 tool.path
910 .push(ucrt.join("bin").join(&ucrt_version).join(host));
911
912 let ucrt_include = ucrt.join("include").join(&ucrt_version);
913 tool.include.push(ucrt_include.join("ucrt"));
914
915 let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
916 tool.libs.push(ucrt_lib.join("ucrt").join(sub));
917
918 if let Some((sdk, version)) = get_sdk10_dir(env_getter) {
919 tool.path.push(sdk.join("bin").join(host));
920 let sdk_lib = sdk.join("lib").join(&version);
921 tool.libs.push(sdk_lib.join("um").join(sub));
922 let sdk_include = sdk.join("include").join(&version);
923 tool.include.push(sdk_include.join("um"));
924 tool.include.push(sdk_include.join("cppwinrt"));
925 tool.include.push(sdk_include.join("winrt"));
926 tool.include.push(sdk_include.join("shared"));
927 } else if let Some(sdk) = get_sdk81_dir() {
928 tool.path.push(sdk.join("bin").join(host));
929 let sdk_lib = sdk.join("lib").join("winv6.3");
930 tool.libs.push(sdk_lib.join("um").join(sub));
931 let sdk_include = sdk.join("include");
932 tool.include.push(sdk_include.join("um"));
933 tool.include.push(sdk_include.join("winrt"));
934 tool.include.push(sdk_include.join("shared"));
935 }
936
937 Some(())
938 }
939
940 fn add_env(
941 tool: &mut Tool,
942 env: &'static str,
943 paths: Vec<PathBuf>,
944 env_getter: &dyn EnvGetter,
945 ) {
946 let prev = env_getter.get_env(env);
947 let prev = prev.as_ref().map(AsRef::as_ref).unwrap_or_default();
948 let prev = env::split_paths(&prev);
949 let new = paths.into_iter().chain(prev);
950 tool.env
951 .push((env.to_string().into(), env::join_paths(new).unwrap()));
952 }
953
954 fn get_tool(tool: &str, path: &Path, target: TargetArch) -> Option<MsvcTool> {
957 bin_subdir(target)
958 .into_iter()
959 .map(|(sub, host)| {
960 (
961 path.join("bin").join(sub).join(tool),
962 path.join("bin").join(host),
963 )
964 })
965 .filter(|(path, _)| path.is_file())
966 .map(|(path, host)| {
967 let mut tool = MsvcTool::new(path);
968 tool.path.push(host);
969 tool
970 })
971 .filter_map(|mut tool| {
972 let sub = vc_lib_subdir(target);
973 tool.libs.push(path.join("lib").join(sub));
974 tool.include.push(path.join("include"));
975 let atlmfc_path = path.join("atlmfc");
976 if atlmfc_path.exists() {
977 tool.libs.push(atlmfc_path.join("lib").join(sub));
978 tool.include.push(atlmfc_path.join("include"));
979 }
980 Some(tool)
981 })
982 .next()
983 }
984
985 fn get_vc_dir(ver: &str) -> Option<PathBuf> {
988 let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
989 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
990 let path = key.query_str(ver).ok()?;
991 Some(path.into())
992 }
993
994 fn get_ucrt_dir() -> Option<(PathBuf, String)> {
1001 let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
1002 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1003 let root = key.query_str("KitsRoot10").ok()?;
1004 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
1005 let max_libdir = readdir
1006 .filter_map(|dir| dir.ok())
1007 .map(|dir| dir.path())
1008 .filter(|dir| {
1009 dir.components()
1010 .last()
1011 .and_then(|c| c.as_os_str().to_str())
1012 .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
1013 .unwrap_or(false)
1014 })
1015 .max()?;
1016 let version = max_libdir.components().last().unwrap();
1017 let version = version.as_os_str().to_str().unwrap().to_string();
1018 Some((root.into(), version))
1019 }
1020
1021 fn get_sdk10_dir(env_getter: &dyn EnvGetter) -> Option<(PathBuf, String)> {
1033 if let (Some(root), Some(version)) = (
1034 env_getter.get_env("WindowsSdkDir"),
1035 env_getter
1036 .get_env("WindowsSDKVersion")
1037 .as_ref()
1038 .and_then(|version| version.as_ref().to_str()),
1039 ) {
1040 return Some((
1041 PathBuf::from(root),
1042 version.trim_end_matches('\\').to_string(),
1043 ));
1044 }
1045
1046 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
1047 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1048 let root = key.query_str("InstallationFolder").ok()?;
1049 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
1050 let mut dirs = readdir
1051 .filter_map(|dir| dir.ok())
1052 .map(|dir| dir.path())
1053 .collect::<Vec<_>>();
1054 dirs.sort();
1055 let dir = dirs
1056 .into_iter()
1057 .rev()
1058 .find(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())?;
1059 let version = dir.components().last().unwrap();
1060 let version = version.as_os_str().to_str().unwrap().to_string();
1061 Some((root.into(), version))
1062 }
1063
1064 fn get_sdk81_dir() -> Option<PathBuf> {
1069 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
1070 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
1071 let root = key.query_str("InstallationFolder").ok()?;
1072 Some(root.into())
1073 }
1074
1075 const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
1076 const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
1077 const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12;
1078 const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
1079 const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
1080 const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64;
1081
1082 fn bin_subdir(target: TargetArch) -> Vec<(&'static str, &'static str)> {
1095 match (target, host_arch()) {
1096 (TargetArch::X86, X86) => vec![("", "")],
1097 (TargetArch::X86, X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
1098 (TargetArch::X64, X86) => vec![("x86_amd64", "")],
1099 (TargetArch::X64, X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
1100 (TargetArch::Arm, X86) => vec![("x86_arm", "")],
1101 (TargetArch::Arm, X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
1102 _ => vec![],
1103 }
1104 }
1105
1106 fn vc_lib_subdir(target: TargetArch) -> &'static str {
1108 match target {
1109 TargetArch::X86 => "",
1110 TargetArch::X64 => "amd64",
1111 TargetArch::Arm => "arm",
1112 TargetArch::Arm64 | TargetArch::Arm64ec => "arm64",
1113 }
1114 }
1115
1116 #[allow(bad_style)]
1117 fn host_arch() -> u16 {
1118 type DWORD = u32;
1119 type WORD = u16;
1120 type LPVOID = *mut u8;
1121 type DWORD_PTR = usize;
1122
1123 #[repr(C)]
1124 struct SYSTEM_INFO {
1125 wProcessorArchitecture: WORD,
1126 _wReserved: WORD,
1127 _dwPageSize: DWORD,
1128 _lpMinimumApplicationAddress: LPVOID,
1129 _lpMaximumApplicationAddress: LPVOID,
1130 _dwActiveProcessorMask: DWORD_PTR,
1131 _dwNumberOfProcessors: DWORD,
1132 _dwProcessorType: DWORD,
1133 _dwAllocationGranularity: DWORD,
1134 _wProcessorLevel: WORD,
1135 _wProcessorRevision: WORD,
1136 }
1137
1138 extern "system" {
1139 fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
1140 }
1141
1142 unsafe {
1143 let mut info = mem::zeroed();
1144 GetNativeSystemInfo(&mut info);
1145 info.wProcessorArchitecture
1146 }
1147 }
1148
1149 #[cfg(test)]
1150 mod tests {
1151 use super::*;
1152 use std::path::Path;
1153 use crate::find_tools::find;
1155
1156 fn host_arch_to_string(host_arch_value: u16) -> &'static str {
1157 match host_arch_value {
1158 X86 => "x86",
1159 X86_64 => "x64",
1160 AARCH64 => "arm64",
1161 _ => panic!("Unsupported host architecture: {}", host_arch_value),
1162 }
1163 }
1164
1165 #[test]
1166 fn test_find_cl_exe() {
1167 let target_architectures = ["x64", "x86", "arm64"];
1172 let mut found_any = false;
1173
1174 let host_arch_value = host_arch();
1176 let host_name = host_arch_to_string(host_arch_value);
1177
1178 for &target_arch in &target_architectures {
1179 if let Some(cmd) = find(target_arch, "cl.exe") {
1180 assert!(
1182 !cmd.get_program().is_empty(),
1183 "cl.exe program path should not be empty"
1184 );
1185 assert!(
1186 Path::new(cmd.get_program()).exists(),
1187 "cl.exe should exist at: {:?}",
1188 cmd.get_program()
1189 );
1190
1191 let path_str = cmd.get_program().to_string_lossy();
1194 let path_str_lower = path_str.to_lowercase();
1195 let expected_host_target_path =
1196 format!("\\bin\\host{host_name}\\{target_arch}");
1197 let expected_host_target_path_unix =
1198 expected_host_target_path.replace("\\", "/");
1199
1200 assert!(
1201 path_str_lower.contains(&expected_host_target_path) || path_str_lower.contains(&expected_host_target_path_unix),
1202 "cl.exe path should contain host-target combination (case-insensitive) '{}' for {} host targeting {}, but found: {}",
1203 expected_host_target_path,
1204 host_name,
1205 target_arch,
1206 path_str
1207 );
1208
1209 found_any = true;
1210 }
1211 }
1212
1213 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");
1214 }
1215
1216 #[test]
1217 #[cfg(not(disable_clang_cl_tests))]
1218 fn test_find_llvm_tools() {
1219 use crate::find_tools::StdEnvGetter;
1221
1222 let target_arch = TargetArch::new("x64").expect("Should support x64 architecture");
1226 let llvm_tools = ["clang.exe", "clang++.exe", "lld.exe", "llvm-ar.exe"];
1227
1228 let host_arch_value = host_arch();
1230 let expected_host_path = match host_arch_value {
1231 X86 => "LLVM\\bin", X86_64 => "LLVM\\x64\\bin", AARCH64 => "LLVM\\ARM64\\bin", _ => panic!("Unsupported host architecture: {}", host_arch_value),
1235 };
1236
1237 let host_name = host_arch_to_string(host_arch_value);
1238
1239 let mut found_tools_count = 0;
1240
1241 for &tool in &llvm_tools {
1242 let env_getter = StdEnvGetter;
1244 let result = find_llvm_tool(tool, target_arch, &env_getter);
1245
1246 match result {
1247 Some(found_tool) => {
1248 found_tools_count += 1;
1249
1250 assert!(
1252 !found_tool.path().as_os_str().is_empty(),
1253 "Found LLVM tool '{}' should have a non-empty path",
1254 tool
1255 );
1256
1257 assert!(
1259 found_tool.path().exists(),
1260 "LLVM tool '{}' path should exist: {:?}",
1261 tool,
1262 found_tool.path()
1263 );
1264
1265 let path_str = found_tool.path().to_string_lossy();
1267 assert!(
1268 path_str.contains(tool.trim_end_matches(".exe")),
1269 "Tool path '{}' should contain tool name '{}'",
1270 path_str,
1271 tool
1272 );
1273
1274 assert!(
1276 path_str.contains(expected_host_path) || path_str.contains(&expected_host_path.replace("\\", "/")),
1277 "LLVM tool should be in host-specific VS LLVM directory '{}' for {} host, but found: {}",
1278 expected_host_path,
1279 host_name,
1280 path_str
1281 );
1282 }
1283 None => {}
1284 }
1285 }
1286
1287 assert!(
1289 found_tools_count > 0,
1290 "Expected to find at least one LLVM tool on CI with Visual Studio + Clang installed for {} host. Found: {}",
1291 host_name,
1292 found_tools_count
1293 );
1294 }
1295 }
1296
1297 fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
1302 let mut max_vers = 0;
1303 let mut max_key = None;
1304 for subkey in key.iter().filter_map(|k| k.ok()) {
1305 let val = subkey
1306 .to_str()
1307 .and_then(|s| s.trim_start_matches('v').replace('.', "").parse().ok());
1308 let val = match val {
1309 Some(s) => s,
1310 None => continue,
1311 };
1312 if val > max_vers {
1313 if let Ok(k) = key.open(&subkey) {
1314 max_vers = val;
1315 max_key = Some((subkey, k));
1316 }
1317 }
1318 }
1319 max_key
1320 }
1321
1322 #[inline(always)]
1323 pub(super) fn has_msbuild_version(version: &str, env_getter: &dyn EnvGetter) -> bool {
1324 match version {
1325 "17.0" => {
1326 find_msbuild_vs17(TargetArch::X64, env_getter).is_some()
1327 || find_msbuild_vs17(TargetArch::X86, env_getter).is_some()
1328 || find_msbuild_vs17(TargetArch::Arm64, env_getter).is_some()
1329 }
1330 "16.0" => {
1331 find_msbuild_vs16(TargetArch::X64, env_getter).is_some()
1332 || find_msbuild_vs16(TargetArch::X86, env_getter).is_some()
1333 || find_msbuild_vs16(TargetArch::Arm64, env_getter).is_some()
1334 }
1335 "15.0" => {
1336 find_msbuild_vs15(TargetArch::X64, env_getter).is_some()
1337 || find_msbuild_vs15(TargetArch::X86, env_getter).is_some()
1338 || find_msbuild_vs15(TargetArch::Arm64, env_getter).is_some()
1339 }
1340 "14.0" => LOCAL_MACHINE
1341 .open(&OsString::from(format!(
1342 "SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}",
1343 version
1344 )))
1345 .is_ok(),
1346 _ => false,
1347 }
1348 }
1349
1350 pub(super) fn find_devenv(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1351 find_devenv_vs15(target, env_getter)
1352 }
1353
1354 fn find_devenv_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1355 find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target, env_getter)
1356 }
1357
1358 pub(super) fn find_msbuild(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1360 if let Some(r) = find_msbuild_vs17(target, env_getter) {
1362 Some(r)
1363 } else if let Some(r) = find_msbuild_vs16(target, env_getter) {
1364 return Some(r);
1365 } else if let Some(r) = find_msbuild_vs15(target, env_getter) {
1366 return Some(r);
1367 } else {
1368 find_old_msbuild(target)
1369 }
1370 }
1371
1372 fn find_msbuild_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> {
1373 find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target, env_getter)
1374 }
1375
1376 fn find_old_msbuild(target: TargetArch) -> Option<Tool> {
1377 let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
1378 LOCAL_MACHINE
1379 .open(key.as_ref())
1380 .ok()
1381 .and_then(|key| {
1382 max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
1383 })
1384 .map(|path| {
1385 let mut path = PathBuf::from(path);
1386 path.push("MSBuild.exe");
1387 let mut tool = Tool {
1388 tool: path,
1389 is_clang_cl: false,
1390 env: Vec::new(),
1391 };
1392 if target == TargetArch::X64 {
1393 tool.env.push(("Platform".into(), "X64".into()));
1394 }
1395 tool
1396 })
1397 }
1398}
1399
1400#[cfg(not(windows))]
1402mod impl_ {
1403 use std::{env, ffi::OsStr};
1404
1405 use super::{EnvGetter, TargetArch};
1406 use crate::Tool;
1407
1408 #[inline(always)]
1411 pub(super) fn find_msbuild(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1412 None
1413 }
1414
1415 #[inline(always)]
1418 pub(super) fn find_devenv(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> {
1419 None
1420 }
1421
1422 #[inline(always)]
1424 pub(super) fn find_llvm_tool(
1425 _tool: &str,
1426 _target: TargetArch,
1427 _: &dyn EnvGetter,
1428 ) -> Option<Tool> {
1429 None
1430 }
1431
1432 pub(super) fn find_msvc_environment(
1434 tool: &str,
1435 _target: TargetArch,
1436 env_getter: &dyn EnvGetter,
1437 ) -> Option<Tool> {
1438 let vc_install_dir = env_getter.get_env("VCINSTALLDIR")?;
1440 let vs_install_dir = env_getter.get_env("VSINSTALLDIR")?;
1441
1442 let get_tool = |install_dir: &OsStr| {
1443 env::split_paths(install_dir)
1444 .map(|p| p.join(tool))
1445 .find(|p| p.exists())
1446 .map(|path| Tool {
1447 tool: path,
1448 is_clang_cl: false,
1449 env: Vec::new(),
1450 })
1451 };
1452
1453 get_tool(vc_install_dir.as_ref())
1455 .or_else(|| get_tool(vs_install_dir.as_ref()))
1457 .or_else(|| {
1459 env_getter
1460 .get_env("PATH")
1461 .as_ref()
1462 .map(|path| path.as_ref())
1463 .and_then(get_tool)
1464 })
1465 }
1466
1467 #[inline(always)]
1468 pub(super) fn find_msvc_15plus(
1469 _tool: &str,
1470 _target: TargetArch,
1471 _: &dyn EnvGetter,
1472 ) -> Option<Tool> {
1473 None
1474 }
1475
1476 #[inline(always)]
1479 pub(super) fn find_msvc_14(
1480 _tool: &str,
1481 _target: TargetArch,
1482 _: &dyn EnvGetter,
1483 ) -> Option<Tool> {
1484 None
1485 }
1486
1487 #[inline(always)]
1488 pub(super) fn has_msbuild_version(_version: &str, _: &dyn EnvGetter) -> bool {
1489 false
1490 }
1491}