Repository: rust-lang/cc-rs
Pull request #1381 (2025-01-31 19:40:20)

test: ensure `clang --driver-mode=cl` is MSVC- and `clang-cl`-like

ErichDonGubler: Just confirmed that this test fails prior to #1378, so I'm satisfied with where it's at.

Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 4259216..110551e 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -22,9 +22,13 @@ jobs:
             beta,
             nightly,
             linux32,
-            macos,
             aarch64-macos,
+            x86_64-macos,
             aarch64-ios,
+            aarch64-ios-sim,
+            x86_64-ios-sim,
+            aarch64-ios-macabi,
+            x86_64-ios-macabi,
             win32,
             win64,
             mingw32,
@@ -49,19 +53,39 @@ jobs:
             os: ubuntu-latest
             rust: stable
             target: i686-unknown-linux-gnu
-          - build: macos
-            os: macos-latest
-            rust: stable
-            target: x86_64-apple-darwin
           - build: aarch64-macos
             os: macos-14
             rust: stable
             target: aarch64-apple-darwin
+          - build: x86_64-macos
+            os: macos-13 # x86
+            rust: stable
+            target: x86_64-apple-darwin
           - build: aarch64-ios
             os: macos-latest
             rust: stable
             target: aarch64-apple-ios
             no_run: --no-run
+          - build: aarch64-ios-sim
+            os: macos-latest
+            rust: stable
+            target: aarch64-apple-ios-sim
+            no_run: --no-run
+          - build: x86_64-ios-sim
+            os: macos-13 # x86
+            rust: stable
+            target: x86_64-apple-ios # Simulator
+            no_run: --no-run
+          - build: aarch64-ios-macabi
+            os: macos-latest
+            rust: stable
+            target: aarch64-apple-ios-macabi
+            no_run: --no-run # FIXME(madsmtm): Fix running tests
+          - build: x86_64-ios-macabi
+            os: macos-13 # x86
+            rust: stable
+            target: x86_64-apple-ios-macabi
+            no_run: --no-run # FIXME(madsmtm): Fix running tests
           - build: windows-aarch64
             os: windows-latest
             rust: stable
@@ -139,42 +163,42 @@ jobs:
       - run: cargo test ${{ matrix.no_run }} --workspace --target ${{ matrix.target }} ${{ matrix.cargo_flags }}
 
   # This is separate from the matrix above because there is no prebuilt rust-std component for these targets.
-  check-tvos:
+  check-build-std:
     name: Test build-std
-    runs-on: ${{ matrix.os }}
+    runs-on: macos-latest
     strategy:
       matrix:
-        build: [aarch64-tvos, aarch64-tvos-sim, x86_64-tvos]
-        include:
-          - build: aarch64-tvos
-            os: macos-latest
-            rust: nightly
-            target: aarch64-apple-tvos
-            no_run: --no-run
-          - build: aarch64-tvos-sim
-            os: macos-latest
-            rust: nightly
-            target: aarch64-apple-tvos-sim
-            no_run: --no-run
-          - build: x86_64-tvos
-            os: macos-latest
-            rust: nightly
-            target: x86_64-apple-tvos
-            no_run: --no-run
+        target:
+          - x86_64h-apple-darwin
+          # FIXME(madsmtm): needs deployment target
+          # - armv7s-apple-ios
+          # FIXME(madsmtm): needs deployment target
+          # - i386-apple-ios # Simulator
+          - aarch64-apple-tvos
+          - aarch64-apple-tvos-sim
+          - x86_64-apple-tvos # Simulator
+          - aarch64-apple-watchos
+          - aarch64-apple-watchos-sim
+          - x86_64-apple-watchos-sim
+          # FIXME(madsmtm): needs deployment target
+          # - arm64_32-apple-watchos
+          - armv7k-apple-watchos
+          - aarch64-apple-visionos
+          - aarch64-apple-visionos-sim
     steps:
       - uses: actions/checkout@v4
       - name: Install Rust (rustup)
         run: |
           set -euxo pipefail
-          rustup toolchain install ${{ matrix.rust }} --no-self-update --profile minimal
-          rustup component add rust-src --toolchain ${{ matrix.rust }}
-          rustup default ${{ matrix.rust }}
+          rustup toolchain install nightly --no-self-update --profile minimal
+          rustup component add rust-src --toolchain nightly
+          rustup default nightly
         shell: bash
       - run: cargo update
       - uses: Swatinem/rust-cache@v2
-      - run: cargo test -Z build-std=std ${{ matrix.no_run }} --workspace --target ${{ matrix.target }}
-      - run: cargo test -Z build-std=std ${{ matrix.no_run }} --workspace --target ${{ matrix.target }} --release
-      - run: cargo test -Z build-std=std ${{ matrix.no_run }} --workspace --target ${{ matrix.target }} --features parallel
+      - run: cargo test -Z build-std=std --no-run --workspace --target ${{ matrix.target }}
+      - run: cargo test -Z build-std=std --no-run --workspace --target ${{ matrix.target }} --release
+      - run: cargo test -Z build-std=std --no-run --workspace --target ${{ matrix.target }} --features parallel
 
   check-wasm:
     name: Test wasm
@@ -188,7 +212,7 @@ jobs:
         run: |
           rustup target add ${{ matrix.target }}
         shell: bash
-      - run: cargo update 
+      - run: cargo update
       - uses: Swatinem/rust-cache@v2
       - run: cargo test --no-run --target ${{ matrix.target }}
       - run: cargo test --no-run --target ${{ matrix.target }} --release
@@ -251,7 +275,7 @@ jobs:
           sudo dpkg -i cuda-keyring_1.0-1_all.deb
           sudo apt-get update
           sudo apt-get -y install cuda-minimal-build-11-8
-      - run: cargo update 
+      - run: cargo update
       - uses: Swatinem/rust-cache@v2
       - name: Test 'cudart' feature
         shell: bash
@@ -321,7 +345,7 @@ jobs:
     name: Tests pass
     needs:
       - test
-      - check-tvos
+      - check-build-std
       - check-wasm
       - test-wasm32-wasip1-thread
       - cuda
diff --git a/dev-tools/gen-target-info/src/main.rs b/dev-tools/gen-target-info/src/main.rs
index aafe999..383d292 100644
--- a/dev-tools/gen-target-info/src/main.rs
+++ b/dev-tools/gen-target-info/src/main.rs
@@ -84,15 +84,22 @@ fn generate_target_mapping(f: &mut File, target_specs: &RustcTargetSpecs) -> std
 
 fn main() {
     // Primarily use information from nightly.
-    let mut target_specs = get_target_specs_from_json();
+    let mut target_specs = get_target_specs_from_json(std::env::var("RUSTC").ok());
     // Next, read from MSRV to support old, removed targets.
-    for target_triple in get_targets_msrv().lines() {
-        let target_triple = target_triple.unwrap();
-        let target_triple = target_triple.trim();
-        target_specs
-            .0
-            .entry(target_triple.to_string())
-            .or_insert_with(|| get_target_spec_from_msrv(target_triple));
+    if std::env::var("CC_RS_MSRV")
+        .unwrap_or("1".to_string())
+        .parse::<u32>()
+        .unwrap()
+        != 0
+    {
+        for target_triple in get_targets_msrv().lines() {
+            let target_triple = target_triple.unwrap();
+            let target_triple = target_triple.trim();
+            target_specs
+                .0
+                .entry(target_triple.to_string())
+                .or_insert_with(|| get_target_spec_from_msrv(target_triple));
+        }
     }
 
     // Open file to write to
diff --git a/dev-tools/gen-target-info/src/read.rs b/dev-tools/gen-target-info/src/read.rs
index c5c81f5..4e61b2e 100644
--- a/dev-tools/gen-target-info/src/read.rs
+++ b/dev-tools/gen-target-info/src/read.rs
@@ -40,14 +40,14 @@ pub fn get_target_spec_from_msrv(target: &str) -> TargetSpec {
     serde_json::from_slice(&stdout).unwrap()
 }
 
-pub fn get_target_specs_from_json() -> RustcTargetSpecs {
-    let mut cmd = process::Command::new("rustc");
-    cmd.args([
-        "+nightly",
-        "-Zunstable-options",
-        "--print",
-        "all-target-specs-json",
-    ]);
+pub fn get_target_specs_from_json(rustc: Option<String>) -> RustcTargetSpecs {
+    let mut cmd = process::Command::new(rustc.clone().unwrap_or("rustc".into()));
+
+    if rustc.is_none() {
+        cmd.arg("+nightly");
+    }
+
+    cmd.args(["-Zunstable-options", "--print", "all-target-specs-json"]);
     cmd.stdout(process::Stdio::piped());
     cmd.stderr(process::Stdio::inherit());
 
diff --git a/src/bin/gcc-shim.rs b/src/bin/cc-shim.rs
similarity index 62%
rename from src/bin/gcc-shim.rs
rename to src/bin/cc-shim.rs
index da7a8b3..5271c68 100644
--- a/src/bin/gcc-shim.rs
+++ b/src/bin/cc-shim.rs
@@ -8,14 +8,16 @@ use std::env;
 use std::fs::File;
 use std::io::{self, prelude::*};
 use std::path::PathBuf;
+use std::process::ExitCode;
 
-fn main() {
-    let mut args = env::args();
+fn main() -> ExitCode {
+    let args = env::args().collect::<Vec<_>>();
+    let mut args = args.iter();
     let program = args.next().expect("Unexpected empty args");
 
     let out_dir = PathBuf::from(
-        env::var_os("GCCTEST_OUT_DIR")
-            .unwrap_or_else(|| panic!("{}: GCCTEST_OUT_DIR not found", program)),
+        env::var_os("CC_SHIM_OUT_DIR")
+            .unwrap_or_else(|| panic!("{}: CC_SHIM_OUT_DIR not found", program)),
     );
 
     // Find the first nonexistent candidate file to which the program's args can be written.
@@ -42,7 +44,7 @@ fn main() {
     let mut f = io::BufWriter::new(f);
 
     (|| {
-        for arg in args {
+        for arg in args.clone() {
             writeln!(f, "{}", arg)?;
         }
 
@@ -61,6 +63,27 @@ fn main() {
         )
     });
 
+    if program.starts_with("clang") {
+        // Validate that we got no `-?` without a preceding `--driver-mode=cl`. Compiler family
+        // detection depends on this.
+        if let Some(cl_like_help_option_idx) = args.clone().position(|a| a == "-?") {
+            let has_cl_clang_driver_before_cl_like_help_option = args
+                .clone()
+                .take(cl_like_help_option_idx)
+                .rev()
+                .find_map(|a| a.strip_prefix("--driver-mode="))
+                .is_some_and(|a| a == "cl");
+            if has_cl_clang_driver_before_cl_like_help_option {
+                return ExitCode::SUCCESS;
+            } else {
+                eprintln!(
+                    "Found `-?` argument, but it was not preceded by a `--driver-mode=cl` argument."
+                );
+                return ExitCode::FAILURE;
+            }
+        }
+    }
+
     // Create a file used by some tests.
     let path = &out_dir.join("libfoo.a");
     File::create(path).unwrap_or_else(|e| {
@@ -71,4 +94,6 @@ fn main() {
             e
         )
     });
+
+    ExitCode::SUCCESS
 }
diff --git a/src/lib.rs b/src/lib.rs
index 328ac46..ab896c1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2188,14 +2188,23 @@ impl Build {
                         }
                     }
 
-                    // Pass `--target` with the LLVM target to properly configure Clang even when
-                    // cross-compiling.
+                    // Pass `--target` with the LLVM target to configure Clang for cross-compiling.
                     //
-                    // We intentionally don't put the deployment version in here on Apple targets,
-                    // and instead pass that via `-mmacosx-version-min=` and similar flags, for
-                    // better compatibility with older versions of Clang that has poor support for
-                    // versioned target names (especially when it comes to configuration files).
-                    cmd.push_cc_arg(format!("--target={}", target.llvm_target).into());
+                    // NOTE: In the past, we passed this, along with the deployment version in here
+                    // on Apple targets, but versioned targets were found to have poor compatibility
+                    // with older versions of Clang, especially around comes to configuration files.
+                    //
+                    // Instead, we specify `-arch` along with `-mmacosx-version-min=`, `-mtargetos=`
+                    // and similar flags in `.apple_flags()`.
+                    //
+                    // Note that Clang errors when both `-mtargetos=` and `-target` are specified,
+                    // so we omit this entirely on Apple targets (it's redundant when specifying
+                    // both the `-arch` and the deployment target / OS flag) (in theory we _could_
+                    // specify this on some of the Apple targets that use the older
+                    // `-m*-version-min=`, but for consistency we omit it entirely).
+                    if target.vendor != "apple" {
+                        cmd.push_cc_arg(format!("--target={}", target.llvm_target).into());
+                    }
                 }
             }
             ToolFamily::Msvc { clang_cl } => {
@@ -2233,12 +2242,6 @@ impl Build {
                 }
             }
             ToolFamily::Gnu => {
-                if target.vendor == "apple" {
-                    let arch = map_darwin_target_from_rust_to_compiler_architecture(target);
-                    cmd.args.push("-arch".into());
-                    cmd.args.push(arch.into());
-                }
-
                 if target.vendor == "kmc" {
                     cmd.args.push("-finput-charset=utf-8".into());
                 }
@@ -2641,6 +2644,14 @@ impl Build {
     fn apple_flags(&self, cmd: &mut Tool) -> Result<(), Error> {
         let target = self.get_target()?;
 
+        // Add `-arch` on all compilers. This is a Darwin/Apple-specific flag
+        // that works both on GCC and Clang.
+        // https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html#:~:text=arch
+        // https://clang.llvm.org/docs/CommandGuide/clang.html#cmdoption-arch
+        let arch = map_darwin_target_from_rust_to_compiler_architecture(&target);
+        cmd.args.push("-arch".into());
+        cmd.args.push(arch.into());
+
         // Pass the deployment target via `-mmacosx-version-min=`, `-mtargetos=` and similar.
         //
         // It is also necessary on GCC, as it forces a compilation error if the compiler is not


Test patch:
diff --git a/dev-tools/cc-test/build.rs b/dev-tools/cc-test/build.rs
index 4adb870..23c0a6f 100644
--- a/dev-tools/cc-test/build.rs
+++ b/dev-tools/cc-test/build.rs
@@ -50,10 +50,17 @@ fn main() {
         .compile("bar");
 
     let target = std::env::var("TARGET").unwrap();
-    let file = target.split('-').next().unwrap();
+    let arch = match target.split('-').next().unwrap() {
+        "arm64_32" => "aarch64",
+        "armv7k" => "armv7",
+        "armv7s" => "armv7",
+        "i386" => "i686",
+        "x86_64h" => "x86_64",
+        arch => arch,
+    };
     let file = format!(
         "src/{}.{}",
-        file,
+        arch,
         if target.contains("msvc") { "asm" } else { "S" }
     );
     cc::Build::new().file(file).compile("asm");
diff --git a/dev-tools/cc-test/src/armv7.S b/dev-tools/cc-test/src/armv7.S
index 5c161c2..bc42479 100644
--- a/dev-tools/cc-test/src/armv7.S
+++ b/dev-tools/cc-test/src/armv7.S
@@ -1,4 +1,11 @@
 .globl asm
+.balign 4
 asm:
     mov r0, #7
     bx lr
+
+.globl _asm
+.balign 4
+_asm:
+    mov r0, #7
+    bx lr
diff --git a/tests/cc_env.rs b/tests/cc_env.rs
index ced77ca..a493873 100644
--- a/tests/cc_env.rs
+++ b/tests/cc_env.rs
@@ -15,6 +15,7 @@ fn main() {
     extra_flags();
     path_to_ccache();
     more_spaces();
+    clang_cl();
 }
 
 fn ccache() {
@@ -110,3 +111,18 @@ fn more_spaces() {
     let compiler = test.gcc().file("foo.c").get_compiler();
     assert_eq!(compiler.path(), Path::new("cc"));
 }
+
+fn clang_cl() {
+    for exe_suffix in ["", ".exe"] {
+        let test = Test::clang();
+        let bin = format!("clang{exe_suffix}");
+        env::set_var("CC", &format!("{bin} --driver-mode=cl"));
+        let test_compiler = |build: cc::Build| {
+            let compiler = build.get_compiler();
+            assert_eq!(compiler.path(), Path::new(&*bin));
+            assert!(compiler.is_like_msvc());
+            assert!(compiler.is_like_clang_cl());
+        };
+        test_compiler(test.gcc());
+    }
+}
diff --git a/tests/support/mod.rs b/tests/support/mod.rs
index 5e87001..67516a3 100644
--- a/tests/support/mod.rs
+++ b/tests/support/mod.rs
@@ -44,8 +44,11 @@ impl Test {
         if gcc.ends_with("deps") {
             gcc.pop();
         }
-        let td = Builder::new().prefix("gcc-test").tempdir_in(&gcc).unwrap();
-        gcc.push(format!("gcc-shim{}", env::consts::EXE_SUFFIX));
+        let td = Builder::new()
+            .prefix("cc-shim-test")
+            .tempdir_in(&gcc)
+            .unwrap();
+        gcc.push(format!("cc-shim{}", env::consts::EXE_SUFFIX));
         Test {
             td,
             gcc,
@@ -98,7 +101,7 @@ impl Test {
             .debug(false)
             .out_dir(self.td.path())
             .__set_env("PATH", self.path())
-            .__set_env("GCCTEST_OUT_DIR", self.td.path());
+            .__set_env("CC_SHIM_OUT_DIR", self.td.path());
         if self.msvc {
             cfg.compiler(self.td.path().join("cl"));
             cfg.archiver(self.td.path().join("lib.exe"));
diff --git a/tests/test.rs b/tests/test.rs
index 063a923..231be41 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -525,7 +525,8 @@ fn asm_flags() {
 
 #[test]
 fn gnu_apple_darwin() {
-    for (arch, version) in &[("x86_64", "10.7"), ("aarch64", "11.0")] {
+    for (arch, ld64_arch, version) in &[("x86_64", "x86_64", "10.7"), ("aarch64", "arm64", "11.0")]
+    {
         let target = format!("{}-apple-darwin", arch);
         let test = Test::gnu();
         test.shim("fake-gcc")
@@ -539,6 +540,7 @@ fn gnu_apple_darwin() {
             .compile("foo");
 
         let cmd = test.cmd(0);
+        cmd.must_have_in_order("-arch", ld64_arch);
         cmd.must_have(format!("-mmacosx-version-min={version}"));
         cmd.must_not_have("-isysroot");
     }
@@ -614,6 +616,7 @@ fn clang_apple_tvos() {
         .file("foo.c")
         .compile("foo");
 
+    test.cmd(0).must_have_in_order("-arch", "arm64");
     test.cmd(0).must_have("-mappletvos-version-min=9.0");
 }
 
@@ -637,7 +640,9 @@ fn clang_apple_mac_catalyst() {
         .compile("foo");
     let execution = test.cmd(0);
 
-    execution.must_have("--target=arm64-apple-ios-macabi");
+    execution.must_have_in_order("-arch", "arm64");
+    // --target and -mtargetos= don't mix
+    execution.must_not_have("--target=arm64-apple-ios-macabi");
     execution.must_have("-mtargetos=ios15.0-macabi");
     execution.must_have_in_order("-isysroot", sdkroot);
     execution.must_have_in_order(
@@ -667,8 +672,7 @@ fn clang_apple_tvsimulator() {
         .file("foo.c")
         .compile("foo");
 
-    test.cmd(0)
-        .must_have("--target=x86_64-apple-tvos-simulator");
+    test.cmd(0).must_have_in_order("-arch", "x86_64");
     test.cmd(0).must_have("-mappletvsimulator-version-min=9.0");
 }
 
@@ -693,8 +697,12 @@ fn clang_apple_visionos() {
 
     dbg!(test.cmd(0).args);
 
-    test.cmd(0).must_have("--target=arm64-apple-xros");
+    test.cmd(0).must_have_in_order("-arch", "arm64");
+    // --target and -mtargetos= don't mix.
+    test.cmd(0).must_not_have("--target=arm64-apple-xros");
     test.cmd(0).must_have("-mtargetos=xros1.0");
+
+    // Flags that don't exist.
     test.cmd(0).must_not_have("-mxros-version-min=1.0");
     test.cmd(0).must_not_have("-mxrsimulator-version-min=1.0");
 }