Skip to content

Commit 7220628

Browse files
authored
Merge pull request #2978 from jonhoo/dont-add-if-added
Don't prepend CARGO_HOME/bin unnecessarily
2 parents 641f498 + d434741 commit 7220628

File tree

4 files changed

+105
-10
lines changed

4 files changed

+105
-10
lines changed

src/env_var.rs

Lines changed: 12 additions & 7 deletions
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

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
44
pub mod mock;
55

6-
use std::env::consts::EXE_SUFFIX;
76
use std::str;
7+
use std::{env::consts::EXE_SUFFIX, path::Path};
88

99
use rustup::for_host;
1010
use rustup::test::this_host_triple;
@@ -298,6 +298,92 @@ 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_first_then =
307+
|config: &Config,
308+
args: &[&str],
309+
env: &[(&str, &str)],
310+
first: &Path,
311+
second: Option<&Path>| {
312+
let out = run(config, args[0], &args[1..], env);
313+
let first_then_second = |list: &str| -> bool {
314+
let mut saw_first = false;
315+
let mut saw_second = false;
316+
for path in std::env::split_paths(list) {
317+
if path == first {
318+
if saw_second {
319+
return false;
320+
}
321+
saw_first = true;
322+
}
323+
if Some(&*path) == second {
324+
if !saw_first {
325+
return false;
326+
}
327+
saw_second = true;
328+
}
329+
}
330+
true
331+
};
332+
if !out.ok || !first_then_second(&out.stderr) {
333+
clitools::print_command(args, &out);
334+
println!("expected.ok: true");
335+
clitools::print_indented(
336+
"expected.stderr.first_then",
337+
&format!("{} comes before {:?}", first.display(), second),
338+
);
339+
panic!();
340+
}
341+
};
342+
343+
// For all of these, CARGO_HOME/bin will be auto-prepended.
344+
let cargo_home_bin = config.cargodir.join("bin");
345+
expect_stderr_ok_env_first_then(
346+
config,
347+
&["cargo", "--echo-path"],
348+
&[],
349+
&cargo_home_bin,
350+
None,
351+
);
352+
expect_stderr_ok_env_first_then(
353+
config,
354+
&["cargo", "--echo-path"],
355+
&[("PATH", "")],
356+
&cargo_home_bin,
357+
None,
358+
);
359+
360+
// Check that CARGO_HOME/bin is prepended to path.
361+
expect_stderr_ok_env_first_then(
362+
config,
363+
&["cargo", "--echo-path"],
364+
&[("PATH", &format!("{}", config.exedir.display()))],
365+
&cargo_home_bin,
366+
Some(&config.exedir),
367+
);
368+
369+
// But if CARGO_HOME/bin is already on PATH, it will not be prepended again,
370+
// so exedir will take precedence.
371+
expect_stderr_ok_env_first_then(
372+
config,
373+
&["cargo", "--echo-path"],
374+
&[(
375+
"PATH",
376+
std::env::join_paths([&config.exedir, &cargo_home_bin])
377+
.unwrap()
378+
.to_str()
379+
.unwrap(),
380+
)],
381+
&config.exedir,
382+
Some(&cargo_home_bin),
383+
);
384+
});
385+
}
386+
301387
#[test]
302388
fn rustup_failed_path_search() {
303389
setup(&|config| {

tests/mock/clitools.rs

Lines changed: 2 additions & 2 deletions
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

Lines changed: 4 additions & 0 deletions
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)