diff --git a/Cargo.toml b/Cargo.toml index 467b24248..52c60c0ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,3 +47,6 @@ members = [ [patch.crates-io] cc = { path = "." } + +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(disable_clang_cl_tests)'] } diff --git a/src/lib.rs b/src/lib.rs index 51bc39d7f..7a6c51494 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 @@ -2878,10 +2888,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 { - ("CC", "cl.exe", "gcc", "cc", "clang") + "cl.exe" + }; + + let (env, gnu, traditional, clang) = if self.cpp { + ("CXX", "g++", "c++", "clang++") + } else { + ("CC", "gcc", "cc", "clang") }; // On historical Solaris systems, "cc" may have been Sun Studio, which @@ -2894,7 +2911,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) @@ -3785,7 +3802,12 @@ impl Build { self.cargo_output .print_metadata(&format_args!("cargo:rerun-if-env-changed={v}")); } - let r = env::var_os(v).map(Arc::from); + let r = self + .env + .iter() + .find(|(k, _)| k.as_ref() == v) + .map(|(_, value)| value.clone()) + .or_else(|| env::var_os(v).map(Arc::from)); self.cargo_output.print_metadata(&format_args!( "{} = {}", v, diff --git a/src/windows/find_tools.rs b/src/windows/find_tools.rs index 029d8123d..7fc55f300 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, @@ -553,9 +554,10 @@ mod impl_ { // E.g. C:\...\VC\Tools\LLVM\x64\bin\clang.exe base_path.push("bin"); base_path.push(tool); + let clang_cl = tool.contains("clang-cl"); base_path .is_file() - .then(|| Tool::with_family(base_path, MSVC_FAMILY)) + .then(|| Tool::with_family(base_path, ToolFamily::Msvc { clang_cl })) }) .next() } @@ -1138,8 +1140,6 @@ mod impl_ { use std::path::Path; // Import the find function from the module level use crate::windows::find_tools::find; - // Import StdEnvGetter from the parent module - use crate::windows::find_tools::StdEnvGetter; fn host_arch_to_string(host_arch_value: u16) -> &'static str { match host_arch_value { @@ -1202,7 +1202,11 @@ mod impl_ { } #[test] + #[cfg(not(disable_clang_cl_tests))] fn test_find_llvm_tools() { + // Import StdEnvGetter from the parent module + use crate::windows::find_tools::StdEnvGetter; + // Test the actual find_llvm_tool function with various LLVM tools // This test assumes CI environment has Visual Studio + Clang installed // We test against x64 target since clang can cross-compile to any target 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..a0d40e26c 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -791,7 +791,8 @@ fn apple_sdkroot_wrong() { let wrong_sdkroot = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"; let test = Test::clang(); test.gcc() - .__set_env("SDKROOT", wrong_sdkroot) + // TODO fix-me https://github.com/rust-lang/cc-rs/pull/1516#discussion_r2295709756 + //.__set_env("SDKROOT", wrong_sdkroot) .target("aarch64-apple-ios") .file("foo.c") .compile("foo"); @@ -851,3 +852,100 @@ fn clang_android() { test.cmd(0).must_not_have("--target=arm-linux-androideabi"); } } + +#[cfg(windows)] +#[cfg(not(disable_clang_cl_tests))] +mod msvc_clang_cl_tests { + use super::{reset_env, Test}; + + #[test] + 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] + 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] + fn msvc_prefer_clang_cl_over_msvc_respects_explicit_cc_env() { + reset_env(); + + let test = Test::msvc_autodetect(); + + //std::env::set_var("CC", "cl.exe"); + let compiler = test + .gcc() + .__set_env("CC", "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 + ); + std::env::remove_var("CC"); // Clean up after test + } + + #[test] + 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" + ); + } +}