Skip to content

Add prefer_clang_cl_over_msvc #1516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

dennisameling
Copy link
Contributor

Closes #882

This could probably replace #1367. Now that #1506 has been merged, windows_registry_find_tool can find clang-cl.exe correctly.

This PR also adds a fix for something I missed in find_tools as part of #1506: clang_cl should be set to true when using that toolchain.

Lastly, this PR adds some tests to ensure that the logic that @briansmith described here works as expected.

Comment on lines -558 to +559
.then(|| Tool::with_family(base_path, MSVC_FAMILY))
.then(|| Tool::with_family(base_path, ToolFamily::Msvc { clang_cl: true }))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this bit in my other PR #1506. MSVC_FAMILY basically sets ToolFamily::Msvc { clang_cl: false }, which isn't accurate when looking for clang-cl.exe.

Question:

use cc::windows_registry::find;

let clang = find("x64", "clang.exe"); // Will return `ToolFamily::Msvc { clang_cl: true }`. Is that OK, or should it only be set to `true` when we're explicitly looking for `clang-cl.exe`?
let clang = find("x64", "clang-cl.exe"); // Will return `ToolFamily::Msvc { clang_cl: true }`.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang_cl should only set to true for clang-cl

For clang, I think it'd count as family clang instead of msvc?

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

// For msvc_autodetect, don't explicitly set the compiler - let the build system discover it
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 115-118 explicitly set the compiler to cl when msvc is set to true, which makes our use case much harder to test. We could just rely on the build system to discover it, given that GitHub Actions runners have MSVC + Clang installed anyway. I think this would make the tests fail on Windows systems that don't have MSVC + Clang installed though. Is that an acceptable trade-off, or should I try to mock things better and not rely on MSVC + Clang actually being installed when running the tests?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add a cfg(not(CC_TEST_DISABLE_CLANG_CL_TESTS)) just in case someone needs it

@dennisameling
Copy link
Contributor Author

The failing tests in CI seem to be caused by ca81dcc, which has the same failing tests on the main branch.

@NobodyXu
Copy link
Collaborator

#1426

Confirmed that nightly has removed env in favor of abi, so it's not caused by this PR or that one

@NobodyXu
Copy link
Collaborator

Fixed in #1517, just rebasing would fix it

@dennisameling dennisameling force-pushed the add-clang-cl-preference-tests branch from 064f3f0 to 27efba8 Compare August 11, 2025 12:59
Copy link
Collaborator

@NobodyXu NobodyXu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

I think you need to modify line 177 to add clang-cl to the array?

Comment on lines +909 to +910
// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a hidden cc::Build::__set_env function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I just tried using that as follows:

    let compiler = test
        .gcc()
        // If the user has set CC to cl.exe, we should respect that
        .__set_env("CC", "cl.exe")
        .prefer_clang_cl_over_msvc(true)
        .try_get_compiler()
        .expect("Failed to get compiler");

However, it's not being picked up because get_base_compiler calls env_tool which - if I understand the code correctly - looks at the actual environment variables that are available to the current process, and not at the ones that were set through the hidden __set_env method. So even though it looks for the CC env var, it doesn't find anything because it's not set at the process level.

I'm curious if you're aware of any alternatives though! I also tried std::env::set_var("CC", "cl.exe"); which works when I run the test in isolation, but not when running the entire test suite, which runs many tests in parallel and probably shares the same process-level env vars.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could update the getenv function to first look at self.env if set, and otherwise fall back to process-level env variables using env::var_os, which it was already doing. I'm by no means proficient in Rust so I'm sure there might be a more efficient way to do this, but the code below seems to work and the entire test suite (ran with cargo test) also passes with it:

diff --git a/src/lib.rs b/src/lib.rs
index 8a74141..24a7bbb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3795,7 +3795,11 @@ 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).cloned().map(|(_, v)| v);
+        let r = match r {
+            Some(ref val) if !val.is_empty() => Some(val.clone()),
+            _ => env::var_os(v).map(Arc::from),
+        };
         self.cargo_output.print_metadata(&format_args!(
             "{} = {}",
             v,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks pretty good except for the empty checking and unnecessary cloning.

        let r = self.env
            .iter()
            .find(|(k, value)| k.as_ref() == v)
            .map(|(_, value)| value.clone())
            .or_else(|| env::var_os(v).map(Arc::from));

@@ -851,3 +851,97 @@ fn clang_android() {
test.cmd(0).must_not_have("--target=arm-linux-androideabi");
}
}

#[test]
#[cfg(windows)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to put them in a mod to avoid repeated cfg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add "prefer to use clang instead of MSVC" API that can automatically find Microsoft-provided clang
2 participants