Skip to content

Commit c187114

Browse files
committed
Auto merge of #11023 - dpc:cargo-cmd-path-order, r=epage
Do not add home bin path to PATH if it's already there This is to allow users to control the order via PATH if they so desire. Tested by preparing two different `cargo-foo` scripts in `$HOME/.cargo/bin` and `$HOME/bin`: ``` > env PATH="/usr/bin/:$HOME/bin:$HOME/.cargo/bin" ./target/debug/cargo foo Inside ~/bin/ > env PATH="$HOME/.cargo/bin:/usr/bin/:$HOME/bin" ./target/debug/cargo foo Inside ~/.cargo/bin/ > env PATH="/usr/bin/:$HOME/bin" ./target/debug/cargo foo Inside ~/.cargo/bin/ ``` and trailing slash: ``` > env PATH="$HOME/.cargo/bin/:/usr/bin/:$HOME/bin" ./target/debug/cargo foo Inside ~/.cargo/bin/ > env PATH="/usr/bin/:$HOME/bin:$HOME/.cargo/bin/" ./target/debug/cargo foo Inside ~/bin/ ``` Fix #11020
2 parents 0eac8be + c712f08 commit c187114

File tree

2 files changed

+102
-5
lines changed

2 files changed

+102
-5
lines changed

src/bin/cargo/main.rs

+22-5
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,28 @@ fn is_executable<P: AsRef<Path>>(path: P) -> bool {
212212
}
213213

214214
fn search_directories(config: &Config) -> Vec<PathBuf> {
215-
let mut dirs = vec![config.home().clone().into_path_unlocked().join("bin")];
216-
if let Some(val) = env::var_os("PATH") {
217-
dirs.extend(env::split_paths(&val));
218-
}
219-
dirs
215+
let mut path_dirs = if let Some(val) = env::var_os("PATH") {
216+
env::split_paths(&val).collect()
217+
} else {
218+
vec![]
219+
};
220+
221+
let home_bin = config.home().clone().into_path_unlocked().join("bin");
222+
223+
// If any of that PATH elements contains `home_bin`, do not
224+
// add it again. This is so that the users can control priority
225+
// of it using PATH, while preserving the historical
226+
// behavior of preferring it over system global directories even
227+
// when not in PATH at all.
228+
// See https://github.com/rust-lang/cargo/issues/11020 for details.
229+
//
230+
// Note: `p == home_bin` will ignore trailing slash, but we don't
231+
// `canonicalize` the paths.
232+
if !path_dirs.iter().any(|p| p == &home_bin) {
233+
path_dirs.insert(0, home_bin);
234+
};
235+
236+
path_dirs
220237
}
221238

222239
fn init_git_transports(config: &Config) {

tests/testsuite/cargo_command.rs

+80
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ use std::path::{Path, PathBuf};
77
use std::process::Stdio;
88
use std::str;
99

10+
use cargo_test_support::basic_manifest;
11+
use cargo_test_support::paths::CargoPathExt;
1012
use cargo_test_support::registry::Package;
1113
use cargo_test_support::tools::echo_subcommand;
1214
use cargo_test_support::{
1315
basic_bin_manifest, cargo_exe, cargo_process, paths, project, project_in_home,
1416
};
17+
use cargo_util::paths::join_paths;
1518

1619
fn path() -> Vec<PathBuf> {
1720
env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect()
@@ -341,6 +344,83 @@ fn cargo_subcommand_env() {
341344
.run();
342345
}
343346

347+
#[cargo_test]
348+
fn cargo_cmd_bins_vs_explicit_path() {
349+
// Set up `cargo-foo` binary in two places: inside `$HOME/.cargo/bin` and outside of it
350+
//
351+
// Return paths to both places
352+
fn set_up_cargo_foo() -> (PathBuf, PathBuf) {
353+
let p = project()
354+
.at("cargo-foo")
355+
.file("Cargo.toml", &basic_manifest("cargo-foo", "1.0.0"))
356+
.file(
357+
"src/bin/cargo-foo.rs",
358+
r#"fn main() { println!("INSIDE"); }"#,
359+
)
360+
.file(
361+
"src/bin/cargo-foo2.rs",
362+
r#"fn main() { println!("OUTSIDE"); }"#,
363+
)
364+
.build();
365+
p.cargo("build").run();
366+
let cargo_bin_dir = paths::home().join(".cargo/bin");
367+
cargo_bin_dir.mkdir_p();
368+
let root_bin_dir = paths::root().join("bin");
369+
root_bin_dir.mkdir_p();
370+
let exe_name = format!("cargo-foo{}", env::consts::EXE_SUFFIX);
371+
fs::rename(p.bin("cargo-foo"), cargo_bin_dir.join(&exe_name)).unwrap();
372+
fs::rename(p.bin("cargo-foo2"), root_bin_dir.join(&exe_name)).unwrap();
373+
374+
(root_bin_dir, cargo_bin_dir)
375+
}
376+
377+
let (outside_dir, inside_dir) = set_up_cargo_foo();
378+
379+
// If `$CARGO_HOME/bin` is not in a path, prefer it over anything in `$PATH`.
380+
//
381+
// This is the historical behavior we don't want to break.
382+
cargo_process("foo").with_stdout_contains("INSIDE").run();
383+
384+
// When `$CARGO_HOME/bin` is in the `$PATH`
385+
// use only `$PATH` so the user-defined ordering is respected.
386+
{
387+
cargo_process("foo")
388+
.env(
389+
"PATH",
390+
join_paths(&[&inside_dir, &outside_dir], "PATH").unwrap(),
391+
)
392+
.with_stdout_contains("INSIDE")
393+
.run();
394+
395+
cargo_process("foo")
396+
// Note: trailing slash
397+
.env(
398+
"PATH",
399+
join_paths(&[inside_dir.join(""), outside_dir.join("")], "PATH").unwrap(),
400+
)
401+
.with_stdout_contains("INSIDE")
402+
.run();
403+
404+
cargo_process("foo")
405+
.env(
406+
"PATH",
407+
join_paths(&[&outside_dir, &inside_dir], "PATH").unwrap(),
408+
)
409+
.with_stdout_contains("OUTSIDE")
410+
.run();
411+
412+
cargo_process("foo")
413+
// Note: trailing slash
414+
.env(
415+
"PATH",
416+
join_paths(&[outside_dir.join(""), inside_dir.join("")], "PATH").unwrap(),
417+
)
418+
.with_stdout_contains("OUTSIDE")
419+
.run();
420+
}
421+
}
422+
423+
#[test]
344424
#[cargo_test]
345425
fn cargo_subcommand_args() {
346426
let p = echo_subcommand();

0 commit comments

Comments
 (0)