Repository: rust-lang/cargo (rust)

Issue:
Precedence of `cargo rustc -- <flags>`  is too low to override flags set by Cargo
<sup>**Original issue title:** `-C strip=symbols` doesn't work like `strip = true`</sup>

### Problem

Reproducer: https://github.com/konstin/strip-reproducer/blob/main/compile.sh

With the default linker on linux, passing `-C link-arg=-s`, `-C strip=symbols` and `-C strip=debuginfo` to `cargo rustc` all don't strip the binary properly as setting `strip = true` does, even though the [docs](https://doc.rust-lang.org/cargo/reference/profiles.html#strip) say "The strip option controls the [-C strip flag](https://doc.rust-lang.org/rustc/codegen-options/index.html#strip)".

In [maturin](https://github.com/PyO3/maturin) we expose a `--strip` option, since small size is important for publishing. We pass this on to `cargo rustc`, historically with `-C link-arg=-s`, but i also tried with `-C strip=symbols`. This does strip some debuginfo, 40MB -> 37MB in the case of uv, but not down to 30MB for the fully stripped binary (https://github.com/astral-sh/uv/issues/5683). In the minimal reproducer, only `strip = true` shows any effect.

Note that this only happens with the default linker, mold strips properly in both cases.

### Steps

https://github.com/konstin/strip-reproducer/blob/main/compile.sh

Create a new project with `cargo new strip-reproducer`, then run:

```bash
cargo build -q --release
echo "Release: $(stat --printf="%s" target/release/strip-reproducer)"

cargo rustc -q --release -- -C strip=symbols
echo "strip=symbols: $(stat --printf="%s" target/release/strip-reproducer)"

cargo rustc -q --release -- -C strip=debuginfo
echo "strip=debuginfo: $(stat --printf="%s" target/release/strip-reproducer)"

printf "[profile.release]\nstrip = true\n" >> Cargo.toml
cargo build -q --release
echo "strip = true: $(stat --printf="%s" target/release/strip-reproducer)"
```

```
Release: 398360
strip=symbols: 398360
strip=debuginfo: 398360
strip = true: 334304
```

### Possible Solution(s)

There should ideally be a cli option to strip like `strip = true` does, or the docs should be adjusted on the differences between `strip = true` and the `-C` options.

### Notes

_No response_

### Version

```text
cargo 1.80.0 (376290515 2024-07-16)
release: 1.80.0
commit-hash: 37629051518c3df9ac2c1744589362a02ecafa99
commit-date: 2024-07-16
host: x86_64-unknown-linux-gnu
libgit2: 1.7.2 (sys:0.18.3 vendored)
libcurl: 8.6.0-DEV (sys:0.4.72+curl-8.6.0 vendored ssl:OpenSSL/1.1.1w)
ssl: OpenSSL 1.1.1w  11 Sep 2023
os: Ubuntu 24.4.0 (noble) [64-bit]
```


Pull request description:
fix(cargo-rustc): give trailing flags higher precedence on nightly
### What does this PR try to resolve?

Previously `cargo rustc -- <flags>` got a lower precedence than
some of the flags set by cargo internal.
This is a bit unintuitive as Cargo generally treats user-provided
CLI flags with the highest priority.

This commit changes `cargo rustc -- <flags>` to a higher precedence:
higher than most of flags set by Cargo, and only lower than
`build.rustflags` family.




### How should we test and review this PR?

Unsure if this affects people's workflow, so this behavior is only
enabled on nightly for collectin feedback. A environment variable
`__CARGO_RUSTC_ORIG_ARGS_PRIO=1` is provided for users to opt-out.
If everything goes well, the nightly gate will be removed after a
few of releases.

### Additional information

See discussion on https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/rustflags.20precendence.20of.20.60cargo.20rustc.60

Fixes #14346


Patch:
diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs
index 8e9e2ac21..83c918a03 100644
--- a/src/cargo/core/compiler/mod.rs
+++ b/src/cargo/core/compiler/mod.rs
@@ -694,6 +694,11 @@ fn prepare_rustc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult
     base.inherit_jobserver(&build_runner.jobserver);
     build_deps_args(&mut base, build_runner, unit)?;
     add_cap_lints(build_runner.bcx, unit, &mut base);
+    if cargo_rustc_higher_args_precedence(build_runner) {
+        if let Some(args) = build_runner.bcx.extra_args_for(unit) {
+            base.args(args);
+        }
+    }
     base.args(&unit.rustflags);
     if build_runner.bcx.gctx.cli_unstable().binary_dep_depinfo {
         base.arg("-Z").arg("binary-dep-depinfo");
@@ -753,8 +758,11 @@ fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResu
     }
 
     rustdoc.args(unit.pkg.manifest().lint_rustflags());
-    if let Some(args) = build_runner.bcx.extra_args_for(unit) {
-        rustdoc.args(args);
+
+    if !cargo_rustc_higher_args_precedence(build_runner) {
+        if let Some(args) = build_runner.bcx.extra_args_for(unit) {
+            rustdoc.args(args);
+        }
     }
 
     let metadata = build_runner.metadata_for_doc_units[unit];
@@ -795,6 +803,11 @@ fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResu
 
     rustdoc::add_output_format(build_runner, unit, &mut rustdoc)?;
 
+    if cargo_rustc_higher_args_precedence(build_runner) {
+        if let Some(args) = build_runner.bcx.extra_args_for(unit) {
+            rustdoc.args(args);
+        }
+    }
     rustdoc.args(&unit.rustdocflags);
 
     if !crate_version_flag_already_present(&rustdoc) {
@@ -1097,8 +1110,10 @@ fn build_base_args(
 
     cmd.args(unit.pkg.manifest().lint_rustflags());
     cmd.args(&profile_rustflags);
-    if let Some(args) = build_runner.bcx.extra_args_for(unit) {
-        cmd.args(args);
+    if !cargo_rustc_higher_args_precedence(build_runner) {
+        if let Some(args) = build_runner.bcx.extra_args_for(unit) {
+            cmd.args(args);
+        }
     }
 
     // `-C overflow-checks` is implied by the setting of `-C debug-assertions`,
@@ -1969,3 +1984,19 @@ fn scrape_output_path(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoR
         .outputs(unit)
         .map(|outputs| outputs[0].path.clone())
 }
+
+/// Provides a way to change the precedence of `cargo rustc -- <flags>`.
+///
+/// This is intended to be a short-live function.
+///
+/// See <https://github.com/rust-lang/cargo/issues/14346>
+fn cargo_rustc_higher_args_precedence(build_runner: &BuildRunner<'_, '_>) -> bool {
+    build_runner.bcx.gctx.nightly_features_allowed
+        && build_runner
+            .bcx
+            .gctx
+            .get_env("__CARGO_RUSTC_ORIG_ARGS_PRIO")
+            .ok()
+            .as_deref()
+            != Some("1")
+}


Test patch:
diff --git a/tests/testsuite/rustc.rs b/tests/testsuite/rustc.rs
index be01f1465..71ca898ae 100644
--- a/tests/testsuite/rustc.rs
+++ b/tests/testsuite/rustc.rs
@@ -795,3 +795,66 @@ windows
         )
         .run();
 }
+
+#[cargo_test]
+fn precedence() {
+    // Ensure that the precedence of cargo-rustc is only lower than RUSTFLAGS,
+    // but higher than most flags set by cargo.
+    //
+    // See rust-lang/cargo#14346
+    let p = project()
+        .file(
+            "Cargo.toml",
+            r#"
+            [package]
+            name = "foo"
+            edition = "2021"
+
+            [lints.rust]
+            unexpected_cfgs = "allow"
+            "#,
+        )
+        .file("src/lib.rs", "")
+        .build();
+
+    p.cargo("rustc --release -v -- --cfg cargo_rustc -C strip=symbols")
+        .env("RUSTFLAGS", "--cfg from_rustflags")
+        .masquerade_as_nightly_cargo(&["cargo-rustc-precedence"])
+        .with_stderr_data(str![[r#"
+[COMPILING] foo v0.0.0 ([ROOT]/foo)
+[RUNNING] `rustc [..]-C strip=debuginfo [..]--cfg cargo_rustc -C strip=symbols --cfg from_rustflags`
+[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
+
+"#]])
+        .run();
+
+    // Ensure the short-live env var to work
+    p.cargo("clean").run();
+    p.cargo("rustc --release -v -- --cfg cargo_rustc -C strip=symbols")
+        .env("RUSTFLAGS", "--cfg from_rustflags")
+        .env("__CARGO_RUSTC_ORIG_ARGS_PRIO", "1")
+        .masquerade_as_nightly_cargo(&["cargo-rustc-precedence"])
+        .with_stderr_data(
+            str![[r#"
+[COMPILING] foo v0.0.0 ([ROOT]/foo)
+[RUNNING] `rustc [..]--cfg cargo_rustc -C strip=symbols [..]-C strip=debuginfo [..]--cfg from_rustflags`
+[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
+
+"#]]
+        )
+        .run();
+
+    // Ensure non-nightly to work as before
+    p.cargo("clean").run();
+    p.cargo("rustc --release -v -- --cfg cargo_rustc -C strip=symbols")
+        .env("RUSTFLAGS", "--cfg from_rustflags")
+        .with_stderr_data(
+            str![[r#"
+[COMPILING] foo v0.0.0 ([ROOT]/foo)
+[RUNNING] `rustc [..]--cfg cargo_rustc -C strip=symbols [..]-C strip=debuginfo [..]--cfg from_rustflags`
+[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s
+
+"#]]
+        )
+        .run();
+}