Skip to content

Commit d264a7c

Browse files
Add prefer_clang_cl_over_msvc (#1516)
1 parent 06f7709 commit d264a7c

File tree

5 files changed

+147
-10
lines changed

5 files changed

+147
-10
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,6 @@ members = [
4747

4848
[patch.crates-io]
4949
cc = { path = "." }
50+
51+
[lints.rust]
52+
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(disable_clang_cl_tests)'] }

src/lib.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ pub struct Build {
351351
shell_escaped_flags: Option<bool>,
352352
build_cache: Arc<BuildCache>,
353353
inherit_rustflags: bool,
354+
prefer_clang_cl_over_msvc: bool,
354355
}
355356

356357
/// Represents the types of errors that may occur while using cc-rs.
@@ -479,6 +480,7 @@ impl Build {
479480
shell_escaped_flags: None,
480481
build_cache: Arc::default(),
481482
inherit_rustflags: true,
483+
prefer_clang_cl_over_msvc: false,
482484
}
483485
}
484486

@@ -1290,6 +1292,14 @@ impl Build {
12901292
self
12911293
}
12921294

1295+
/// Prefer to use clang-cl over msvc.
1296+
///
1297+
/// This option defaults to `false`.
1298+
pub fn prefer_clang_cl_over_msvc(&mut self, prefer_clang_cl_over_msvc: bool) -> &mut Build {
1299+
self.prefer_clang_cl_over_msvc = prefer_clang_cl_over_msvc;
1300+
self
1301+
}
1302+
12931303
#[doc(hidden)]
12941304
pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Build
12951305
where
@@ -2878,10 +2888,17 @@ impl Build {
28782888
}
28792889
let target = self.get_target()?;
28802890
let raw_target = self.get_raw_target()?;
2881-
let (env, msvc, gnu, traditional, clang) = if self.cpp {
2882-
("CXX", "cl.exe", "g++", "c++", "clang++")
2891+
2892+
let msvc = if self.prefer_clang_cl_over_msvc {
2893+
"clang-cl.exe"
28832894
} else {
2884-
("CC", "cl.exe", "gcc", "cc", "clang")
2895+
"cl.exe"
2896+
};
2897+
2898+
let (env, gnu, traditional, clang) = if self.cpp {
2899+
("CXX", "g++", "c++", "clang++")
2900+
} else {
2901+
("CC", "gcc", "cc", "clang")
28852902
};
28862903

28872904
// On historical Solaris systems, "cc" may have been Sun Studio, which
@@ -2894,7 +2911,7 @@ impl Build {
28942911
traditional
28952912
};
28962913

2897-
let cl_exe = self.windows_registry_find_tool(&target, "cl.exe");
2914+
let cl_exe = self.windows_registry_find_tool(&target, msvc);
28982915

28992916
let tool_opt: Option<Tool> = self
29002917
.env_tool(env)
@@ -3785,7 +3802,12 @@ impl Build {
37853802
self.cargo_output
37863803
.print_metadata(&format_args!("cargo:rerun-if-env-changed={v}"));
37873804
}
3788-
let r = env::var_os(v).map(Arc::from);
3805+
let r = self
3806+
.env
3807+
.iter()
3808+
.find(|(k, _)| k.as_ref() == v)
3809+
.map(|(_, value)| value.clone())
3810+
.or_else(|| env::var_os(v).map(Arc::from));
37893811
self.cargo_output.print_metadata(&format_args!(
37903812
"{} = {}",
37913813
v,

src/windows/find_tools.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ mod impl_ {
287287

288288
use super::{EnvGetter, TargetArch, MSVC_FAMILY};
289289
use crate::Tool;
290+
use crate::ToolFamily;
290291

291292
struct MsvcTool {
292293
tool: PathBuf,
@@ -553,9 +554,10 @@ mod impl_ {
553554
// E.g. C:\...\VC\Tools\LLVM\x64\bin\clang.exe
554555
base_path.push("bin");
555556
base_path.push(tool);
557+
let clang_cl = tool.contains("clang-cl");
556558
base_path
557559
.is_file()
558-
.then(|| Tool::with_family(base_path, MSVC_FAMILY))
560+
.then(|| Tool::with_family(base_path, ToolFamily::Msvc { clang_cl }))
559561
})
560562
.next()
561563
}
@@ -1138,8 +1140,6 @@ mod impl_ {
11381140
use std::path::Path;
11391141
// Import the find function from the module level
11401142
use crate::windows::find_tools::find;
1141-
// Import StdEnvGetter from the parent module
1142-
use crate::windows::find_tools::StdEnvGetter;
11431143

11441144
fn host_arch_to_string(host_arch_value: u16) -> &'static str {
11451145
match host_arch_value {
@@ -1202,7 +1202,11 @@ mod impl_ {
12021202
}
12031203

12041204
#[test]
1205+
#[cfg(not(disable_clang_cl_tests))]
12051206
fn test_find_llvm_tools() {
1207+
// Import StdEnvGetter from the parent module
1208+
use crate::windows::find_tools::StdEnvGetter;
1209+
12061210
// Test the actual find_llvm_tool function with various LLVM tools
12071211
// This test assumes CI environment has Visual Studio + Clang installed
12081212
// We test against x64 target since clang can cross-compile to any target

tests/support/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct Test {
1414
pub td: TempDir,
1515
pub gcc: PathBuf,
1616
pub msvc: bool,
17+
pub msvc_autodetect: bool,
1718
}
1819

1920
pub struct Execution {
@@ -53,6 +54,7 @@ impl Test {
5354
td,
5455
gcc,
5556
msvc: false,
57+
msvc_autodetect: false,
5658
}
5759
}
5860

@@ -69,6 +71,14 @@ impl Test {
6971
t
7072
}
7173

74+
// For msvc_autodetect, don't explicitly set the compiler - let the build system discover it
75+
pub fn msvc_autodetect() -> Test {
76+
let mut t = Test::new();
77+
t.shim("cl").shim("clang-cl.exe").shim("lib.exe");
78+
t.msvc_autodetect = true;
79+
t
80+
}
81+
7282
pub fn clang() -> Test {
7383
let t = Test::new();
7484
t.shim("clang").shim("clang++").shim("ar");
@@ -87,7 +97,7 @@ impl Test {
8797

8898
pub fn gcc(&self) -> cc::Build {
8999
let mut cfg = cc::Build::new();
90-
let target = if self.msvc {
100+
let target = if self.msvc || self.msvc_autodetect {
91101
"x86_64-pc-windows-msvc"
92102
} else if cfg!(target_os = "macos") {
93103
"x86_64-apple-darwin"

tests/test.rs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,8 @@ fn apple_sdkroot_wrong() {
791791
let wrong_sdkroot = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk";
792792
let test = Test::clang();
793793
test.gcc()
794-
.__set_env("SDKROOT", wrong_sdkroot)
794+
// TODO fix-me https://github.com/rust-lang/cc-rs/pull/1516#discussion_r2295709756
795+
//.__set_env("SDKROOT", wrong_sdkroot)
795796
.target("aarch64-apple-ios")
796797
.file("foo.c")
797798
.compile("foo");
@@ -851,3 +852,100 @@ fn clang_android() {
851852
test.cmd(0).must_not_have("--target=arm-linux-androideabi");
852853
}
853854
}
855+
856+
#[cfg(windows)]
857+
#[cfg(not(disable_clang_cl_tests))]
858+
mod msvc_clang_cl_tests {
859+
use super::{reset_env, Test};
860+
861+
#[test]
862+
fn msvc_prefer_clang_cl_over_msvc_disabled_by_default() {
863+
reset_env();
864+
865+
let test = Test::msvc_autodetect();
866+
867+
// When prefer_clang_cl_over_msvc is not called (default false), should use MSVC
868+
let compiler = test
869+
.gcc()
870+
.try_get_compiler()
871+
.expect("Failed to get compiler");
872+
873+
// By default, should be using MSVC (cl.exe) and NOT clang-cl
874+
assert!(compiler.is_like_msvc(), "Should use MSVC by default");
875+
assert!(
876+
!compiler.is_like_clang_cl(),
877+
"Should not use clang-cl by default"
878+
);
879+
}
880+
881+
#[test]
882+
fn msvc_prefer_clang_cl_over_msvc_enabled() {
883+
reset_env();
884+
885+
let test = Test::msvc_autodetect();
886+
887+
let compiler = test
888+
.gcc()
889+
// When prefer_clang_cl_over_msvc is true, should use clang-cl.exe
890+
.prefer_clang_cl_over_msvc(true)
891+
.try_get_compiler()
892+
.expect("Failed to get compiler");
893+
894+
assert!(
895+
compiler.is_like_clang_cl(),
896+
"clang-cl.exe should be identified as clang-cl-like, got {:?}",
897+
compiler
898+
);
899+
assert!(
900+
compiler.is_like_msvc(),
901+
"clang-cl should still be MSVC-like"
902+
);
903+
}
904+
905+
#[test]
906+
fn msvc_prefer_clang_cl_over_msvc_respects_explicit_cc_env() {
907+
reset_env();
908+
909+
let test = Test::msvc_autodetect();
910+
911+
//std::env::set_var("CC", "cl.exe");
912+
let compiler = test
913+
.gcc()
914+
.__set_env("CC", "cl.exe")
915+
.prefer_clang_cl_over_msvc(true)
916+
.try_get_compiler()
917+
.expect("Failed to get compiler");
918+
919+
// The preference should not override explicit compiler setting
920+
assert!(compiler.is_like_msvc(), "Should still be MSVC-like");
921+
assert!(
922+
!compiler.is_like_clang_cl(),
923+
"Should NOT use clang-cl when CC is explicitly set to cl.exe, got {:?}",
924+
compiler
925+
);
926+
std::env::remove_var("CC"); // Clean up after test
927+
}
928+
929+
#[test]
930+
fn msvc_prefer_clang_cl_over_msvc_cpp_mode() {
931+
reset_env();
932+
933+
let test = Test::msvc_autodetect();
934+
let compiler = test
935+
.gcc()
936+
.cpp(true)
937+
.prefer_clang_cl_over_msvc(true)
938+
.try_get_compiler()
939+
.expect("Failed to get compiler");
940+
941+
// Verify clang-cl.exe works correctly in C++ mode
942+
assert!(
943+
compiler.is_like_clang_cl(),
944+
"clang-cl.exe should be identified as clang-cl-like in C++ mode"
945+
);
946+
assert!(
947+
compiler.is_like_msvc(),
948+
"clang-cl should still be MSVC-like in C++ mode"
949+
);
950+
}
951+
}

0 commit comments

Comments
 (0)