Skip to content

Commit 0a8a248

Browse files
committed
Overwrite `$CARGO` when the current exe is detected to be a cargo binary. See: rust-lang#15099 (comment)
1 parent 1566e4c commit 0a8a248

File tree

4 files changed

+104
-14
lines changed

4 files changed

+104
-14
lines changed

src/cargo/util/context/mod.rs

+18-2
Original file line numberDiff line numberDiff line change
@@ -493,9 +493,25 @@ impl GlobalContext {
493493
paths::resolve_executable(&argv0)
494494
}
495495

496+
// Determines whether `path` is a cargo binary.
497+
// See: https://github.com/rust-lang/cargo/issues/15099#issuecomment-2666737150
498+
fn is_cargo(path: &Path) -> bool {
499+
path.file_stem() == Some(OsStr::new("cargo"))
500+
}
501+
502+
let from_current_exe = from_current_exe();
503+
if from_current_exe.as_deref().is_ok_and(is_cargo) {
504+
return from_current_exe;
505+
}
506+
507+
let from_argv = from_argv();
508+
if from_argv.as_deref().is_ok_and(is_cargo) {
509+
return from_argv;
510+
}
511+
496512
let exe = from_env()
497-
.or_else(|_| from_current_exe())
498-
.or_else(|_| from_argv())
513+
.or(from_current_exe)
514+
.or(from_argv)
499515
.context("couldn't get the path to cargo executable")?;
500516
Ok(exe)
501517
})

tests/testsuite/cargo_command.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -391,10 +391,13 @@ fn cargo_subcommand_env() {
391391
.canonicalize()
392392
.unwrap();
393393
let envtest_bin = envtest_bin.to_str().unwrap();
394+
// Previously, `$CARGO` would be left at `envtest_bin`. However, with the
395+
// fix for #15099, `$CARGO` is now overwritten with the path to the current
396+
// exe when it is detected to be a cargo binary.
394397
cargo_process("envtest")
395398
.env("PATH", &path)
396399
.env(cargo::CARGO_ENV, &envtest_bin)
397-
.with_stdout_data(format!("{}\n", envtest_bin).raw().raw())
400+
.with_stdout_data(format!("{}\n", cargo.display()).raw())
398401
.run();
399402
}
400403

tests/testsuite/freshness.rs

+73
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Tests for fingerprinting (rebuild detection).
22
3+
use std::env::consts::EXE_SUFFIX;
34
use std::fs::{self, OpenOptions};
45
use std::io;
56
use std::io::prelude::*;
@@ -3182,3 +3183,75 @@ fn use_mtime_cache_in_cargo_home() {
31823183
"#]])
31833184
.run();
31843185
}
3186+
3187+
#[cargo_test]
3188+
fn overwrite_cargo_environment_variable() {
3189+
// If passed arguments `arg1 arg2 ...`, this program runs them as a command.
3190+
// If passed no arguments, this program simply prints `$CARGO`.
3191+
let p = project()
3192+
.file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
3193+
.file(
3194+
"src/main.rs",
3195+
r#"
3196+
fn main() {
3197+
let mut args = std::env::args().skip(1);
3198+
if let Some(arg1) = args.next() {
3199+
let status = std::process::Command::new(arg1)
3200+
.args(args)
3201+
.status()
3202+
.unwrap();
3203+
assert!(status.success());
3204+
} else {
3205+
println!("{}", std::env::var("CARGO").unwrap());
3206+
}
3207+
}
3208+
"#,
3209+
)
3210+
.build();
3211+
3212+
// Create two other cargo binaries in the project root, one with the wrong
3213+
// name and one with the right name.
3214+
let cargo_exe = cargo_test_support::cargo_exe();
3215+
let wrong_name_path = p.root().join(format!("wrong_name{EXE_SUFFIX}"));
3216+
let other_cargo_path = p.root().join(cargo_exe.file_name().unwrap());
3217+
std::fs::hard_link(&cargo_exe, &wrong_name_path).unwrap();
3218+
std::fs::hard_link(&cargo_exe, &other_cargo_path).unwrap();
3219+
3220+
// The output of each of the following commands should be `path-to-cargo`:
3221+
// ```
3222+
// cargo run
3223+
// cargo run -- cargo run
3224+
// cargo run -- wrong_name run
3225+
// ```
3226+
3227+
let cargo = cargo_exe.display().to_string();
3228+
let wrong_name = wrong_name_path.display().to_string();
3229+
let stdout_cargo = format!("{}[EXE]\n", cargo_exe.with_extension("").display());
3230+
3231+
for cmd in [
3232+
"run",
3233+
&format!("run -- {cargo} run"),
3234+
&format!("run -- {wrong_name} run"),
3235+
] {
3236+
p.cargo(cmd).with_stdout_data(&stdout_cargo).run();
3237+
}
3238+
3239+
// The output of the following command should be `path-to-other-cargo`:
3240+
// ```
3241+
// cargo run -- other_cargo run
3242+
// ```
3243+
3244+
let other_cargo = other_cargo_path.display().to_string();
3245+
let stdout_other_cargo = format!(
3246+
"{}[EXE]\n",
3247+
other_cargo_path
3248+
.with_extension("")
3249+
.to_str()
3250+
.unwrap()
3251+
.replace(p.root().parent().unwrap().to_str().unwrap(), "[ROOT]")
3252+
);
3253+
3254+
p.cargo(&format!("run -- {other_cargo} run"))
3255+
.with_stdout_data(stdout_other_cargo)
3256+
.run();
3257+
}

tests/testsuite/test.rs

+9-11
Original file line numberDiff line numberDiff line change
@@ -3909,22 +3909,20 @@ test env_test ... ok
39093909
.run();
39103910

39113911
// Check that `cargo test` propagates the environment's $CARGO
3912-
let rustc = cargo_util::paths::resolve_executable("rustc".as_ref())
3913-
.unwrap()
3914-
.canonicalize()
3915-
.unwrap();
3916-
let stderr_rustc = format!(
3912+
let cargo_exe = cargo_test_support::cargo_exe();
3913+
let other_cargo_path = p.root().join(cargo_exe.file_name().unwrap());
3914+
std::fs::hard_link(&cargo_exe, &other_cargo_path).unwrap();
3915+
let stderr_other_cargo = format!(
39173916
"{}[EXE]",
3918-
rustc
3917+
other_cargo_path
39193918
.with_extension("")
39203919
.to_str()
39213920
.unwrap()
3922-
.replace(rustc_host, "[HOST_TARGET]")
3921+
.replace(p.root().parent().unwrap().to_str().unwrap(), "[ROOT]")
39233922
);
3924-
p.cargo("test --lib -- --nocapture")
3925-
// we use rustc since $CARGO is only used if it points to a path that exists
3926-
.env(cargo::CARGO_ENV, rustc)
3927-
.with_stderr_contains(stderr_rustc)
3923+
p.process(other_cargo_path)
3924+
.args(&["test", "--lib", "--", "--nocapture"])
3925+
.with_stderr_contains(stderr_other_cargo)
39283926
.with_stdout_data(str![[r#"
39293927
...
39303928
test env_test ... ok

0 commit comments

Comments
 (0)