Skip to content

Commit 3c0e427

Browse files
Jon GjengsetDarunada
Jon Gjengset
authored andcommitted
Don't prepend CARGO_HOME/bin unnecessarily
The current logic forces nested invocations to execute `cargo` and friends from `$CARGO_HOME/bin`. This makes rustup break in environments where the appropriate rustup proxies to use happen to be installed elsewhere (earlier on `$PATH`), and using the binaries in `$CARGO_HOME/bin` would not work correctly. It also ensures that Rustup won't change `$PATH` "just for the heck of it", which _should_ help reduce unnecessary re-compilations when downstream build logic notices that `$PATH` changes (since it will no longer). Helps with rust-lang#2848. Fixes rust-lang/cargo#7431.
1 parent 029c778 commit 3c0e427

File tree

4 files changed

+71
-9
lines changed

4 files changed

+71
-9
lines changed

src/env_var.rs

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::VecDeque;
12
use std::env;
23
use std::path::PathBuf;
34
use std::process::Command;
@@ -21,15 +22,19 @@ fn append_path(name: &str, value: Vec<PathBuf>, cmd: &mut Command) {
2122
}
2223
}
2324

24-
pub(crate) fn prepend_path(name: &str, value: Vec<PathBuf>, cmd: &mut Command) {
25+
pub(crate) fn prepend_path(name: &str, prepend: Vec<PathBuf>, cmd: &mut Command) {
2526
let old_value = process().var_os(name);
26-
let mut parts: Vec<PathBuf>;
27-
if let Some(ref v) = old_value {
28-
parts = value;
29-
parts.extend(env::split_paths(v).collect::<Vec<_>>());
27+
let parts = if let Some(ref v) = old_value {
28+
let mut tail = env::split_paths(v).collect::<VecDeque<_>>();
29+
for path in prepend.into_iter().rev() {
30+
if !tail.contains(&path) {
31+
tail.push_front(path);
32+
}
33+
}
34+
tail
3035
} else {
31-
parts = value;
32-
}
36+
prepend.into()
37+
};
3338

3439
if let Ok(new_value) = env::join_paths(parts) {
3540
cmd.env(name, new_value);

tests/cli-misc.rs

+53
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,59 @@ fn rustup_run_searches_path() {
298298
});
299299
}
300300

301+
#[test]
302+
fn rustup_doesnt_prepend_path_unnecessarily() {
303+
setup(&|config| {
304+
expect_ok(config, &["rustup", "default", "nightly"]);
305+
306+
let expect_stderr_ok_env_startswith =
307+
|config: &Config, args: &[&str], env: &[(&str, &str)], expected: &str| {
308+
let out = run(config, args[0], &args[1..], env);
309+
if !out.ok || !out.stderr.starts_with(expected) {
310+
clitools::print_command(args, &out);
311+
println!("expected.ok: true");
312+
clitools::print_indented("expected.stderr.starts_with", expected);
313+
panic!();
314+
}
315+
};
316+
317+
// For all of these, CARGO_HOME/bin will be auto-prepended.
318+
let cargo_home_bin = config.cargodir.join("bin");
319+
expect_stderr_ok_env_startswith(
320+
config,
321+
&["cargo", "--echo-path"],
322+
&[],
323+
&format!("{}", cargo_home_bin.display()),
324+
);
325+
expect_stderr_ok_env_startswith(
326+
config,
327+
&["cargo", "--echo-path"],
328+
&[("PATH", "")],
329+
&format!("{}", cargo_home_bin.display()),
330+
);
331+
332+
// Check that CARGO_HOME/bin is prepended to path.
333+
expect_stderr_ok_env_startswith(
334+
config,
335+
&["cargo", "--echo-path"],
336+
&[("PATH", &format!("{}", config.exedir.display()))],
337+
&format!("{}:{}", cargo_home_bin.display(), config.exedir.display()),
338+
);
339+
340+
// But if CARGO_HOME/bin is already on PATH, it will not be prepended again,
341+
// so exedir will take precedence.
342+
expect_stderr_ok_env_startswith(
343+
config,
344+
&["cargo", "--echo-path"],
345+
&[(
346+
"PATH",
347+
&format!("{}:{}", config.exedir.display(), cargo_home_bin.display()),
348+
)],
349+
&format!("{}:{}", config.exedir.display(), cargo_home_bin.display()),
350+
);
351+
});
352+
}
353+
301354
#[test]
302355
fn rustup_failed_path_search() {
303356
setup(&|config| {

tests/mock/clitools.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ pub fn expect_component_not_executable(config: &Config, cmd: &str) {
437437
}
438438
}
439439

440-
fn print_command(args: &[&str], out: &SanitizedOutput) {
440+
pub(crate) fn print_command(args: &[&str], out: &SanitizedOutput) {
441441
print!("\n>");
442442
for arg in args {
443443
if arg.contains(' ') {
@@ -452,7 +452,7 @@ fn print_command(args: &[&str], out: &SanitizedOutput) {
452452
print_indented("out.stderr", &out.stderr);
453453
}
454454

455-
fn print_indented(heading: &str, text: &str) {
455+
pub(crate) fn print_indented(heading: &str, text: &str) {
456456
let mut lines = text.lines().count();
457457
// The standard library treats `a\n` and `a` as both being one line.
458458
// This is confusing when the test fails because of a missing newline.

tests/mock/mock_bin_src.rs

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ fn main() {
6767
writeln!(out, "{}", arg.to_string_lossy()).unwrap();
6868
}
6969
}
70+
Some("--echo-path") => {
71+
let mut out = io::stderr();
72+
writeln!(out, "{}", std::env::var("PATH").unwrap()).unwrap();
73+
}
7074
_ => panic!("bad mock proxy commandline"),
7175
}
7276
}

0 commit comments

Comments
 (0)