Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ members = [

[patch.crates-io]
cc = { path = "." }

[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(disable_clang_cl_tests)'] }
32 changes: 27 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ pub struct Build {
shell_escaped_flags: Option<bool>,
build_cache: Arc<BuildCache>,
inherit_rustflags: bool,
prefer_clang_cl_over_msvc: bool,
}

/// Represents the types of errors that may occur while using cc-rs.
Expand Down Expand Up @@ -479,6 +480,7 @@ impl Build {
shell_escaped_flags: None,
build_cache: Arc::default(),
inherit_rustflags: true,
prefer_clang_cl_over_msvc: false,
}
}

Expand Down Expand Up @@ -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<A, B>(&mut self, a: A, b: B) -> &mut Build
where
Expand Down Expand Up @@ -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
Expand All @@ -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<Tool> = self
.env_tool(env)
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 7 additions & 3 deletions src/windows/find_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ mod impl_ {

use super::{EnvGetter, TargetArch, MSVC_FAMILY};
use crate::Tool;
use crate::ToolFamily;

struct MsvcTool {
tool: PathBuf,
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion tests/support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct Test {
pub td: TempDir,
pub gcc: PathBuf,
pub msvc: bool,
pub msvc_autodetect: bool,
}

pub struct Execution {
Expand Down Expand Up @@ -53,6 +54,7 @@ impl Test {
td,
gcc,
msvc: false,
msvc_autodetect: false,
}
}

Expand All @@ -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");
Expand All @@ -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"
Expand Down
97 changes: 97 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -851,3 +851,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"
);
}
}
Loading