Repository: rust-lang/cc-rs
Pull request #1382 (2025-01-31 20:41:03)

Split `impl Build` in two
I think it's important to distinguish between pure builder methods, and methods that invoke the compiler.

I'm also not happy with `try_flags_from_environment`, since it's dependent on `target`, `host` and the current environment, but unsure what to do about it yet.

This should be a non-functional change.

madsmtm: > Shall we actually move some methods into a separate file?

Maybe one day? I don't feel like I have the full picture, so I'm hesitant to do that yet.

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..0631584 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -410,6 +410,7 @@ impl Object {
     }
 }
 
+/// Configure the builder.
 impl Build {
     /// Construct a new instance of a blank set of configuration.
     ///
@@ -612,144 +613,6 @@ impl Build {
         self
     }
 
-    fn ensure_check_file(&self) -> Result<PathBuf, Error> {
-        let out_dir = self.get_out_dir()?;
-        let src = if self.cuda {
-            assert!(self.cpp);
-            out_dir.join("flag_check.cu")
-        } else if self.cpp {
-            out_dir.join("flag_check.cpp")
-        } else {
-            out_dir.join("flag_check.c")
-        };
-
-        if !src.exists() {
-            let mut f = fs::File::create(&src)?;
-            write!(f, "int main(void) {{ return 0; }}")?;
-        }
-
-        Ok(src)
-    }
-
-    /// Run the compiler to test if it accepts the given flag.
-    ///
-    /// For a convenience method for setting flags conditionally,
-    /// see `flag_if_supported()`.
-    ///
-    /// It may return error if it's unable to run the compiler with a test file
-    /// (e.g. the compiler is missing or a write to the `out_dir` failed).
-    ///
-    /// Note: Once computed, the result of this call is stored in the
-    /// `known_flag_support` field. If `is_flag_supported(flag)`
-    /// is called again, the result will be read from the hash table.
-    pub fn is_flag_supported(&self, flag: impl AsRef<OsStr>) -> Result<bool, Error> {
-        self.is_flag_supported_inner(
-            flag.as_ref(),
-            &self.get_base_compiler()?,
-            &self.get_target()?,
-        )
-    }
-
-    fn is_flag_supported_inner(
-        &self,
-        flag: &OsStr,
-        tool: &Tool,
-        target: &TargetInfo<'_>,
-    ) -> Result<bool, Error> {
-        let compiler_flag = CompilerFlag {
-            compiler: tool.path().into(),
-            flag: flag.into(),
-        };
-
-        if let Some(is_supported) = self
-            .build_cache
-            .known_flag_support_status_cache
-            .read()
-            .unwrap()
-            .get(&compiler_flag)
-            .cloned()
-        {
-            return Ok(is_supported);
-        }
-
-        let out_dir = self.get_out_dir()?;
-        let src = self.ensure_check_file()?;
-        let obj = out_dir.join("flag_check");
-
-        let mut compiler = {
-            let mut cfg = Build::new();
-            cfg.flag(flag)
-                .compiler(tool.path())
-                .cargo_metadata(self.cargo_output.metadata)
-                .opt_level(0)
-                .debug(false)
-                .cpp(self.cpp)
-                .cuda(self.cuda)
-                .inherit_rustflags(false)
-                .emit_rerun_if_env_changed(self.emit_rerun_if_env_changed);
-            if let Some(target) = &self.target {
-                cfg.target(target);
-            }
-            if let Some(host) = &self.host {
-                cfg.host(host);
-            }
-            cfg.try_get_compiler()?
-        };
-
-        // Clang uses stderr for verbose output, which yields a false positive
-        // result if the CFLAGS/CXXFLAGS include -v to aid in debugging.
-        if compiler.family.verbose_stderr() {
-            compiler.remove_arg("-v".into());
-        }
-        if compiler.is_like_clang() {
-            // Avoid reporting that the arg is unsupported just because the
-            // compiler complains that it wasn't used.
-            compiler.push_cc_arg("-Wno-unused-command-line-argument".into());
-        }
-
-        let mut cmd = compiler.to_command();
-        let is_arm = matches!(target.arch, "aarch64" | "arm");
-        let clang = compiler.is_like_clang();
-        let gnu = compiler.family == ToolFamily::Gnu;
-        command_add_output_file(
-            &mut cmd,
-            &obj,
-            CmdAddOutputFileArgs {
-                cuda: self.cuda,
-                is_assembler_msvc: false,
-                msvc: compiler.is_like_msvc(),
-                clang,
-                gnu,
-                is_asm: false,
-                is_arm,
-            },
-        );
-
-        if compiler.supports_path_delimiter() {
-            cmd.arg("--");
-        }
-
-        cmd.arg(&src);
-
-        // On MSVC skip the CRT by setting the entry point to `main`.
-        // This way we don't need to add the default library paths.
-        if compiler.is_like_msvc() {
-            // Flags from _LINK_ are appended to the linker arguments.
-            cmd.env("_LINK_", "-entry:main");
-        }
-
-        let output = cmd.output()?;
-        let is_supported = output.status.success() && output.stderr.is_empty();
-
-        self.build_cache
-            .known_flag_support_status_cache
-            .write()
-            .unwrap()
-            .insert(compiler_flag, is_supported);
-
-        Ok(is_supported)
-    }
-
     /// Add an arbitrary flag to the invocation of the compiler if it supports it
     ///
     /// # Example
@@ -1368,6 +1231,147 @@ impl Build {
         self.env.push((a.as_ref().into(), b.as_ref().into()));
         self
     }
+}
+
+/// Invoke or fetch the compiler or archiver.
+impl Build {
+    /// Run the compiler to test if it accepts the given flag.
+    ///
+    /// For a convenience method for setting flags conditionally,
+    /// see `flag_if_supported()`.
+    ///
+    /// It may return error if it's unable to run the compiler with a test file
+    /// (e.g. the compiler is missing or a write to the `out_dir` failed).
+    ///
+    /// Note: Once computed, the result of this call is stored in the
+    /// `known_flag_support` field. If `is_flag_supported(flag)`
+    /// is called again, the result will be read from the hash table.
+    pub fn is_flag_supported(&self, flag: impl AsRef<OsStr>) -> Result<bool, Error> {
+        self.is_flag_supported_inner(
+            flag.as_ref(),
+            &self.get_base_compiler()?,
+            &self.get_target()?,
+        )
+    }
+
+    fn ensure_check_file(&self) -> Result<PathBuf, Error> {
+        let out_dir = self.get_out_dir()?;
+        let src = if self.cuda {
+            assert!(self.cpp);
+            out_dir.join("flag_check.cu")
+        } else if self.cpp {
+            out_dir.join("flag_check.cpp")
+        } else {
+            out_dir.join("flag_check.c")
+        };
+
+        if !src.exists() {
+            let mut f = fs::File::create(&src)?;
+            write!(f, "int main(void) {{ return 0; }}")?;
+        }
+
+        Ok(src)
+    }
+
+    fn is_flag_supported_inner(
+        &self,
+        flag: &OsStr,
+        tool: &Tool,
+        target: &TargetInfo<'_>,
+    ) -> Result<bool, Error> {
+        let compiler_flag = CompilerFlag {
+            compiler: tool.path().into(),
+            flag: flag.into(),
+        };
+
+        if let Some(is_supported) = self
+            .build_cache
+            .known_flag_support_status_cache
+            .read()
+            .unwrap()
+            .get(&compiler_flag)
+            .cloned()
+        {
+            return Ok(is_supported);
+        }
+
+        let out_dir = self.get_out_dir()?;
+        let src = self.ensure_check_file()?;
+        let obj = out_dir.join("flag_check");
+
+        let mut compiler = {
+            let mut cfg = Build::new();
+            cfg.flag(flag)
+                .compiler(tool.path())
+                .cargo_metadata(self.cargo_output.metadata)
+                .opt_level(0)
+                .debug(false)
+                .cpp(self.cpp)
+                .cuda(self.cuda)
+                .inherit_rustflags(false)
+                .emit_rerun_if_env_changed(self.emit_rerun_if_env_changed);
+            if let Some(target) = &self.target {
+                cfg.target(target);
+            }
+            if let Some(host) = &self.host {
+                cfg.host(host);
+            }
+            cfg.try_get_compiler()?
+        };
+
+        // Clang uses stderr for verbose output, which yields a false positive
+        // result if the CFLAGS/CXXFLAGS include -v to aid in debugging.
+        if compiler.family.verbose_stderr() {
+            compiler.remove_arg("-v".into());
+        }
+        if compiler.is_like_clang() {
+            // Avoid reporting that the arg is unsupported just because the
+            // compiler complains that it wasn't used.
+            compiler.push_cc_arg("-Wno-unused-command-line-argument".into());
+        }
+
+        let mut cmd = compiler.to_command();
+        let is_arm = matches!(target.arch, "aarch64" | "arm");
+        let clang = compiler.is_like_clang();
+        let gnu = compiler.family == ToolFamily::Gnu;
+        command_add_output_file(
+            &mut cmd,
+            &obj,
+            CmdAddOutputFileArgs {
+                cuda: self.cuda,
+                is_assembler_msvc: false,
+                msvc: compiler.is_like_msvc(),
+                clang,
+                gnu,
+                is_asm: false,
+                is_arm,
+            },
+        );
+
+        if compiler.supports_path_delimiter() {
+            cmd.arg("--");
+        }
+
+        cmd.arg(&src);
+
+        // On MSVC skip the CRT by setting the entry point to `main`.
+        // This way we don't need to add the default library paths.
+        if compiler.is_like_msvc() {
+            // Flags from _LINK_ are appended to the linker arguments.
+            cmd.env("_LINK_", "-entry:main");
+        }
+
+        let output = cmd.output()?;
+        let is_supported = output.status.success() && output.stderr.is_empty();
+
+        self.build_cache
+            .known_flag_support_status_cache
+            .write()
+            .unwrap()
+            .insert(compiler_flag, is_supported);
+
+        Ok(is_supported)
+    }
 
     /// Run the compiler, generating the file `output`
     ///
@@ -2188,14 +2192,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.
+                    //
+                    // 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.
                     //
-                    // 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());
+                    // 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 +2246,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 +2648,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");
 }