diff --git a/src/lib.rs b/src/lib.rs index 6c41d3e6e..8a741413f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -351,6 +351,7 @@ pub struct Build { shell_escaped_flags: Option, build_cache: Arc, inherit_rustflags: bool, + prefer_clang_cl_over_msvc: bool, } /// Represents the types of errors that may occur while using cc-rs. @@ -479,6 +480,7 @@ impl Build { shell_escaped_flags: None, build_cache: Arc::default(), inherit_rustflags: true, + prefer_clang_cl_over_msvc: false, } } @@ -1290,6 +1292,14 @@ impl Build { self } + /// Prefer to use clang-cl over msvc. + /// + /// This option defaults to `false`. + pub fn prefer_clang_cl_over_msvc(&mut self, prefer_clang_cl_over_msvc: bool) -> &mut Build { + self.prefer_clang_cl_over_msvc = prefer_clang_cl_over_msvc; + self + } + #[doc(hidden)] pub fn __set_env(&mut self, a: A, b: B) -> &mut Build where @@ -2871,10 +2881,17 @@ impl Build { } let target = self.get_target()?; let raw_target = self.get_raw_target()?; - let (env, msvc, gnu, traditional, clang) = if self.cpp { - ("CXX", "cl.exe", "g++", "c++", "clang++") + + let msvc = if self.prefer_clang_cl_over_msvc { + "clang-cl.exe" + } else { + "cl.exe" + }; + + let (env, gnu, traditional, clang) = if self.cpp { + ("CXX", "g++", "c++", "clang++") } else { - ("CC", "cl.exe", "gcc", "cc", "clang") + ("CC", "gcc", "cc", "clang") }; // On historical Solaris systems, "cc" may have been Sun Studio, which @@ -2887,7 +2904,7 @@ impl Build { traditional }; - let cl_exe = self.windows_registry_find_tool(&target, "cl.exe"); + let cl_exe = self.windows_registry_find_tool(&target, msvc); let tool_opt: Option = self .env_tool(env) diff --git a/src/windows/find_tools.rs b/src/windows/find_tools.rs index 64ce0e83e..76d62f426 100644 --- a/src/windows/find_tools.rs +++ b/src/windows/find_tools.rs @@ -287,6 +287,7 @@ mod impl_ { use super::{EnvGetter, TargetArch, MSVC_FAMILY}; use crate::Tool; + use crate::ToolFamily; struct MsvcTool { tool: PathBuf, @@ -555,7 +556,7 @@ mod impl_ { base_path.push(tool); base_path .is_file() - .then(|| Tool::with_family(base_path, MSVC_FAMILY)) + .then(|| Tool::with_family(base_path, ToolFamily::Msvc { clang_cl: true })) }) .next() } diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 0ca29ebb7..7ec2f2f69 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -14,6 +14,7 @@ pub struct Test { pub td: TempDir, pub gcc: PathBuf, pub msvc: bool, + pub msvc_autodetect: bool, } pub struct Execution { @@ -53,6 +54,7 @@ impl Test { td, gcc, msvc: false, + msvc_autodetect: false, } } @@ -69,6 +71,14 @@ impl Test { t } + // For msvc_autodetect, don't explicitly set the compiler - let the build system discover it + pub fn msvc_autodetect() -> Test { + let mut t = Test::new(); + t.shim("cl").shim("clang-cl.exe").shim("lib.exe"); + t.msvc_autodetect = true; + t + } + pub fn clang() -> Test { let t = Test::new(); t.shim("clang").shim("clang++").shim("ar"); @@ -87,7 +97,7 @@ impl Test { pub fn gcc(&self) -> cc::Build { let mut cfg = cc::Build::new(); - let target = if self.msvc { + let target = if self.msvc || self.msvc_autodetect { "x86_64-pc-windows-msvc" } else if cfg!(target_os = "macos") { "x86_64-apple-darwin" diff --git a/tests/test.rs b/tests/test.rs index bcac10021..5925b228a 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -851,3 +851,97 @@ fn clang_android() { test.cmd(0).must_not_have("--target=arm-linux-androideabi"); } } + +#[test] +#[cfg(windows)] +fn msvc_prefer_clang_cl_over_msvc_disabled_by_default() { + reset_env(); + + let test = Test::msvc_autodetect(); + + // When prefer_clang_cl_over_msvc is not called (default false), should use MSVC + let compiler = test + .gcc() + .try_get_compiler() + .expect("Failed to get compiler"); + + // By default, should be using MSVC (cl.exe) and NOT clang-cl + assert!(compiler.is_like_msvc(), "Should use MSVC by default"); + assert!( + !compiler.is_like_clang_cl(), + "Should not use clang-cl by default" + ); +} + +#[test] +#[cfg(windows)] +fn msvc_prefer_clang_cl_over_msvc_enabled() { + reset_env(); + + let test = Test::msvc_autodetect(); + + let compiler = test + .gcc() + // When prefer_clang_cl_over_msvc is true, should use clang-cl.exe + .prefer_clang_cl_over_msvc(true) + .try_get_compiler() + .expect("Failed to get compiler"); + + assert!( + compiler.is_like_clang_cl(), + "clang-cl.exe should be identified as clang-cl-like, got {:?}", + compiler + ); + assert!( + compiler.is_like_msvc(), + "clang-cl should still be MSVC-like" + ); +} + +#[test] +#[cfg(windows)] +fn msvc_prefer_clang_cl_over_msvc_respects_explicit_cc_env() { + reset_env(); + + let test = Test::msvc_autodetect(); + let compiler = test + .gcc() + // We can't set the CC=cl.exe environment variable directly in the test as it's removed + // in mod.rs, so we simulate it by setting the compiler directly + .compiler("cl.exe") + .prefer_clang_cl_over_msvc(true) + .try_get_compiler() + .expect("Failed to get compiler"); + + // The preference should not override explicit compiler setting + assert!(compiler.is_like_msvc(), "Should still be MSVC-like"); + assert!( + !compiler.is_like_clang_cl(), + "Should NOT use clang-cl when CC is explicitly set to cl.exe, got {:?}", + compiler + ); +} + +#[test] +#[cfg(windows)] +fn msvc_prefer_clang_cl_over_msvc_cpp_mode() { + reset_env(); + + let test = Test::msvc_autodetect(); + let compiler = test + .gcc() + .cpp(true) + .prefer_clang_cl_over_msvc(true) + .try_get_compiler() + .expect("Failed to get compiler"); + + // Verify clang-cl.exe works correctly in C++ mode + assert!( + compiler.is_like_clang_cl(), + "clang-cl.exe should be identified as clang-cl-like in C++ mode" + ); + assert!( + compiler.is_like_msvc(), + "clang-cl should still be MSVC-like in C++ mode" + ); +}