Skip to content

Commit 58cfd96

Browse files
authored
fix: reset $CARGO if the running program is real cargo[.exe] (#15208)
Overwrite `$CARGO` when the current exe is detected to be a cargo binary. From #15099 (comment): > opt-in behavior (set by cargo-the-bin): > > 1. `from_current_exe` if its `cargo[EXE_SUFFIX]` > 2. `from_argv` if its `cargo[EXE_SUFFIX]` > 3. `from_env` > 4. `from_current_exe` > 5. `from_argv` r? @epage
2 parents 726e2a7 + a6fdc01 commit 58cfd96

File tree

3 files changed

+117
-14
lines changed

3 files changed

+117
-14
lines changed

src/cargo/util/context/mod.rs

+18-2
Original file line numberDiff line numberDiff line change
@@ -490,9 +490,25 @@ impl GlobalContext {
490490
paths::resolve_executable(&argv0)
491491
}
492492

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

tests/testsuite/cargo_command.rs

+88-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

@@ -570,3 +573,87 @@ fn full_did_you_mean() {
570573
"#]])
571574
.run();
572575
}
576+
577+
#[cargo_test]
578+
fn overwrite_cargo_environment_variable() {
579+
// If passed arguments `arg1 arg2 ...`, this program runs them as a command.
580+
// If passed no arguments, this program simply prints `$CARGO`.
581+
let p = project()
582+
.file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
583+
.file(
584+
"src/main.rs",
585+
r#"
586+
fn main() {
587+
let mut args = std::env::args().skip(1);
588+
if let Some(arg1) = args.next() {
589+
let status = std::process::Command::new(arg1)
590+
.args(args)
591+
.status()
592+
.unwrap();
593+
assert!(status.success());
594+
} else {
595+
eprintln!("{}", std::env::var("CARGO").unwrap());
596+
}
597+
}
598+
"#,
599+
)
600+
.build();
601+
602+
// Create two other cargo binaries in the project root, one with the wrong
603+
// name and one with the right name.
604+
let cargo_exe = cargo_test_support::cargo_exe();
605+
let wrong_name_path = p
606+
.root()
607+
.join(format!("wrong_name{}", env::consts::EXE_SUFFIX));
608+
let other_cargo_path = p.root().join(cargo_exe.file_name().unwrap());
609+
std::fs::hard_link(&cargo_exe, &wrong_name_path).unwrap();
610+
std::fs::hard_link(&cargo_exe, &other_cargo_path).unwrap();
611+
612+
// The output of each of the following commands should be `path-to-cargo`:
613+
// ```
614+
// cargo run
615+
// cargo run -- cargo run
616+
// cargo run -- wrong_name run
617+
// ```
618+
619+
let cargo = cargo_exe.display().to_string();
620+
let wrong_name = wrong_name_path.display().to_string();
621+
let stderr_cargo = format!(
622+
"{}[EXE]\n",
623+
cargo_exe
624+
.canonicalize()
625+
.unwrap()
626+
.with_extension("")
627+
.to_str()
628+
.unwrap()
629+
);
630+
631+
for cmd in [
632+
"run",
633+
&format!("run -- {cargo} run"),
634+
&format!("run -- {wrong_name} run"),
635+
] {
636+
p.cargo(cmd).with_stderr_contains(&stderr_cargo).run();
637+
}
638+
639+
// The output of the following command should be `path-to-other-cargo`:
640+
// ```
641+
// cargo run -- other_cargo run
642+
// ```
643+
644+
let other_cargo = other_cargo_path.display().to_string();
645+
let stderr_other_cargo = format!(
646+
"{}[EXE]\n",
647+
other_cargo_path
648+
.canonicalize()
649+
.unwrap()
650+
.with_extension("")
651+
.to_str()
652+
.unwrap()
653+
.replace(p.root().parent().unwrap().to_str().unwrap(), "[ROOT]")
654+
);
655+
656+
p.cargo(&format!("run -- {other_cargo} run"))
657+
.with_stderr_contains(stderr_other_cargo)
658+
.run();
659+
}

tests/testsuite/test.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -3909,22 +3909,22 @@ 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
3918+
.canonicalize()
3919+
.unwrap()
39193920
.with_extension("")
39203921
.to_str()
39213922
.unwrap()
3922-
.replace(rustc_host, "[HOST_TARGET]")
3923+
.replace(p.root().parent().unwrap().to_str().unwrap(), "[ROOT]")
39233924
);
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)
3925+
p.process(other_cargo_path)
3926+
.args(&["test", "--lib", "--", "--nocapture"])
3927+
.with_stderr_contains(stderr_other_cargo)
39283928
.with_stdout_data(str![[r#"
39293929
...
39303930
test env_test ... ok

0 commit comments

Comments
 (0)