Skip to content

Commit 196cf59

Browse files
committed
Append Windows bin directory to PATH by default
This also allows opting out of the new default using the `RUSTUP_WINDOWS_PATH_ADD_BIN`. Setting it controls if or where the bin directory is added to PATH: 0 => don't add to PATH 1 => prepend to PATH unset, or anything else => append to PATH
1 parent 7588311 commit 196cf59

File tree

2 files changed

+127
-22
lines changed

2 files changed

+127
-22
lines changed

src/env_var.rs

+88-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ use crate::process::Process;
77

88
pub const RUST_RECURSION_COUNT_MAX: u32 = 20;
99

10-
pub(crate) fn prepend_path(
10+
pub(crate) fn insert_path(
1111
name: &str,
1212
prepend: Vec<PathBuf>,
13+
append: Option<PathBuf>,
1314
cmd: &mut Command,
1415
process: &Process,
1516
) {
1617
let old_value = process.var_os(name);
17-
let parts = if let Some(ref v) = old_value {
18+
let mut parts = if let Some(v) = &old_value {
1819
let mut tail = env::split_paths(v).collect::<VecDeque<_>>();
1920
for path in prepend.into_iter().rev() {
2021
if !tail.contains(&path) {
@@ -26,6 +27,12 @@ pub(crate) fn prepend_path(
2627
prepend.into()
2728
};
2829

30+
if let Some(path) = append {
31+
if !parts.contains(&path) {
32+
parts.push_back(path);
33+
}
34+
}
35+
2936
if let Ok(new_value) = env::join_paths(parts) {
3037
cmd.env(name, new_value);
3138
}
@@ -77,7 +84,7 @@ mod tests {
7784
let path_z = PathBuf::from(z);
7885
path_entries.push(path_z);
7986

80-
prepend_path("PATH", path_entries, &mut cmd, &tp.process);
87+
insert_path("PATH", path_entries, None, &mut cmd, &tp.process);
8188
let envs: Vec<_> = cmd.get_envs().collect();
8289

8390
assert_eq!(
@@ -99,4 +106,82 @@ mod tests {
99106
),]
100107
);
101108
}
109+
110+
#[test]
111+
fn append_unique_path() {
112+
let mut vars = HashMap::new();
113+
vars.env(
114+
"PATH",
115+
env::join_paths(["/home/a/.cargo/bin", "/home/b/.cargo/bin"].iter()).unwrap(),
116+
);
117+
let tp = TestProcess::with_vars(vars);
118+
#[cfg(windows)]
119+
let _path_guard = RegistryGuard::new(&USER_PATH).unwrap();
120+
121+
#[track_caller]
122+
fn check(tp: &TestProcess, path_entries: Vec<PathBuf>, append: &str, expected: &[&str]) {
123+
let mut cmd = Command::new("test");
124+
125+
insert_path(
126+
"PATH",
127+
path_entries,
128+
Some(append.into()),
129+
&mut cmd,
130+
&tp.process,
131+
);
132+
let envs: Vec<_> = cmd.get_envs().collect();
133+
134+
assert_eq!(
135+
envs,
136+
&[(
137+
OsStr::new("PATH"),
138+
Some(env::join_paths(expected.iter()).unwrap().as_os_str())
139+
),]
140+
);
141+
}
142+
143+
check(
144+
&tp,
145+
Vec::new(),
146+
"/home/z/.cargo/bin",
147+
&[
148+
"/home/a/.cargo/bin",
149+
"/home/b/.cargo/bin",
150+
"/home/z/.cargo/bin",
151+
],
152+
);
153+
check(
154+
&tp,
155+
Vec::new(),
156+
"/home/a/.cargo/bin",
157+
&["/home/a/.cargo/bin", "/home/b/.cargo/bin"],
158+
);
159+
check(
160+
&tp,
161+
Vec::new(),
162+
"/home/b/.cargo/bin",
163+
&["/home/a/.cargo/bin", "/home/b/.cargo/bin"],
164+
);
165+
check(
166+
&tp,
167+
Vec::from(["/home/c/.cargo/bin".into()]),
168+
"/home/c/.cargo/bin",
169+
&[
170+
"/home/c/.cargo/bin",
171+
"/home/a/.cargo/bin",
172+
"/home/b/.cargo/bin",
173+
],
174+
);
175+
check(
176+
&tp,
177+
Vec::from(["/home/c/.cargo/bin".into()]),
178+
"/home/z/.cargo/bin",
179+
&[
180+
"/home/c/.cargo/bin",
181+
"/home/a/.cargo/bin",
182+
"/home/b/.cargo/bin",
183+
"/home/z/.cargo/bin",
184+
],
185+
);
186+
}
102187
}

src/toolchain.rs

+39-19
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ impl<'a> Toolchain<'a> {
217217
new_path.push(PathBuf::from("/usr/lib"));
218218
}
219219

220-
env_var::prepend_path(sysenv::LOADER_PATH, new_path, cmd, self.cfg.process);
220+
env_var::insert_path(sysenv::LOADER_PATH, new_path, None, cmd, self.cfg.process);
221221

222222
// Prepend CARGO_HOME/bin to the PATH variable so that we're sure to run
223223
// cargo/rustc via the proxy bins. There is no fallback case for if the
@@ -228,30 +228,50 @@ impl<'a> Toolchain<'a> {
228228
path_entries.push(cargo_home.join("bin"));
229229
}
230230

231-
// Historically rustup included the bin directory in PATH to
232-
// work around some bugs (see
233-
// https://github.com/rust-lang/rustup/pull/3178 for more
234-
// information). This shouldn't be needed anymore, and it causes
231+
// On Windows, we append the "bin" directory to PATH by default.
232+
// Windows loads DLLs from PATH and the "bin" directory contains DLLs
233+
// that proc macros and other tools not in the sysroot use.
234+
// It's appended rather than prepended so that the exe files in "bin"
235+
// do not take precedence over anything else in PATH.
236+
//
237+
// Historically rustup prepended the bin directory in PATH but doing so causes
235238
// problems because calling tools recursively (like `cargo
236239
// +nightly metadata` from within a cargo subcommand). The
237240
// recursive call won't work because it is not executing the
238241
// proxy, so the `+` toolchain override doesn't work.
242+
// See: https://github.com/rust-lang/rustup/pull/3178
239243
//
240-
// The RUSTUP_WINDOWS_PATH_ADD_BIN env var was added to opt-in to
241-
// testing the fix. The default is now off, but this is left here
242-
// just in case there are problems. Consider removing in the
243-
// future if it doesn't seem necessary.
244-
#[cfg(target_os = "windows")]
245-
if self
246-
.cfg
247-
.process
248-
.var_os("RUSTUP_WINDOWS_PATH_ADD_BIN")
249-
.is_some_and(|s| s == "1")
250-
{
251-
path_entries.push(self.path.join("bin"));
252-
}
244+
// This behaviour was then changed to not add the bin directory at all.
245+
// But this caused another set of problems due to the sysroot DLLs
246+
// not being found by the loader, e.g. for proc macros.
247+
// See: https://github.com/rust-lang/rustup/issues/3825
248+
//
249+
// Which is how we arrived at the current default described above.
250+
//
251+
// The `RUSTUP_WINDOWS_PATH_ADD_BIN` environment variable allows
252+
// users to opt-in to one of the old behaviours in case the new
253+
// default causes any new issues.
254+
//
255+
// FIXME: The `RUSTUP_WINDOWS_PATH_ADD_BIN` environment variable can
256+
// be removed once we're confident that the default behaviour works.
257+
let append = if cfg!(target_os = "windows") {
258+
let add_bin = self.cfg.process.var("RUSTUP_WINDOWS_PATH_ADD_BIN");
259+
match add_bin.as_deref().unwrap_or("append") {
260+
// Don't add to PATH at all
261+
"0" => None,
262+
// Prepend to PATH
263+
"1" => {
264+
path_entries.push(self.path.join("bin"));
265+
None
266+
}
267+
// Append to PATH (the default)
268+
_ => Some(self.path.join("bin")),
269+
}
270+
} else {
271+
None
272+
};
253273

254-
env_var::prepend_path("PATH", path_entries, cmd, self.cfg.process);
274+
env_var::insert_path("PATH", path_entries, append, cmd, self.cfg.process);
255275
}
256276

257277
/// Infallible function that describes the version of rustc in an installed distribution

0 commit comments

Comments
 (0)