Skip to content

Commit 46973d6

Browse files
authored
Merge pull request #2849 from chansuke/issue-2848
Fix duplicated PATH entries
2 parents 060b8f9 + c97e02a commit 46973d6

File tree

1 file changed

+191
-5
lines changed

1 file changed

+191
-5
lines changed

src/env_var.rs

Lines changed: 191 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,41 @@
1-
use std::collections::VecDeque;
1+
use std::collections::{HashSet, VecDeque};
22
use std::env;
3+
use std::ffi::OsStr;
34
use std::path::PathBuf;
45
use std::process::Command;
56

67
use crate::process;
78

89
pub const RUST_RECURSION_COUNT_MAX: u32 = 20;
910

11+
/// This can be removed when https://github.com/rust-lang/rust/issues/44434 is
12+
/// stablised.
13+
pub(crate) trait ProcessEnvs {
14+
fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
15+
where
16+
Self: Sized,
17+
K: AsRef<OsStr>,
18+
V: AsRef<OsStr>;
19+
}
20+
21+
impl ProcessEnvs for Command {
22+
fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
23+
where
24+
Self: Sized,
25+
K: AsRef<OsStr>,
26+
V: AsRef<OsStr>,
27+
{
28+
self.env(key, val)
29+
}
30+
}
31+
1032
#[allow(unused)]
11-
fn append_path(name: &str, value: Vec<PathBuf>, cmd: &mut Command) {
33+
fn append_path<E: ProcessEnvs>(name: &str, value: Vec<PathBuf>, cmd: &mut E) {
1234
let old_value = process().var_os(name);
1335
let mut parts: Vec<PathBuf>;
1436
if let Some(ref v) = old_value {
15-
parts = env::split_paths(v).collect();
16-
parts.extend(value);
37+
let old_paths: Vec<PathBuf> = env::split_paths(v).collect::<Vec<_>>();
38+
parts = concat_uniq_paths(old_paths, value);
1739
} else {
1840
parts = value;
1941
}
@@ -22,7 +44,7 @@ fn append_path(name: &str, value: Vec<PathBuf>, cmd: &mut Command) {
2244
}
2345
}
2446

25-
pub(crate) fn prepend_path(name: &str, prepend: Vec<PathBuf>, cmd: &mut Command) {
47+
pub(crate) fn prepend_path<E: ProcessEnvs>(name: &str, prepend: Vec<PathBuf>, cmd: &mut E) {
2648
let old_value = process().var_os(name);
2749
let parts = if let Some(ref v) = old_value {
2850
let mut tail = env::split_paths(v).collect::<VecDeque<_>>();
@@ -50,3 +72,167 @@ pub(crate) fn inc(name: &str, cmd: &mut Command) {
5072

5173
cmd.env(name, (old_value + 1).to_string());
5274
}
75+
76+
fn concat_uniq_paths(fst_paths: Vec<PathBuf>, snd_paths: Vec<PathBuf>) -> Vec<PathBuf> {
77+
let deduped_fst_paths = dedupe_with_preserved_order(fst_paths);
78+
let deduped_snd_paths = dedupe_with_preserved_order(snd_paths);
79+
80+
let vec_fst_paths: Vec<_> = deduped_fst_paths.into_iter().collect();
81+
82+
let mut unified_paths;
83+
unified_paths = vec_fst_paths.clone();
84+
unified_paths.extend(
85+
deduped_snd_paths
86+
.into_iter()
87+
.filter(|v| !vec_fst_paths.contains(v))
88+
.collect::<Vec<_>>(),
89+
);
90+
91+
unified_paths
92+
}
93+
94+
fn dedupe_with_preserved_order(paths: Vec<PathBuf>) -> Vec<PathBuf> {
95+
let mut uniq_paths: Vec<PathBuf> = Vec::new();
96+
let mut seen_paths: HashSet<PathBuf> = HashSet::new();
97+
98+
for path in &paths {
99+
if !seen_paths.contains(path) {
100+
seen_paths.insert(path.to_path_buf());
101+
uniq_paths.push(path.to_path_buf());
102+
}
103+
}
104+
105+
uniq_paths
106+
}
107+
108+
#[cfg(test)]
109+
mod tests {
110+
use super::*;
111+
use crate::currentprocess;
112+
use crate::test::{with_saved_path, Env};
113+
114+
use super::ProcessEnvs;
115+
use std::collections::HashMap;
116+
use std::ffi::{OsStr, OsString};
117+
118+
#[derive(Default)]
119+
struct TestCommand {
120+
envs: HashMap<OsString, Option<OsString>>,
121+
}
122+
123+
impl ProcessEnvs for TestCommand {
124+
fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
125+
where
126+
Self: Sized,
127+
K: AsRef<OsStr>,
128+
V: AsRef<OsStr>,
129+
{
130+
self.envs
131+
.insert(key.as_ref().to_owned(), Some(val.as_ref().to_owned()));
132+
self
133+
}
134+
}
135+
136+
#[test]
137+
fn deduplicate_and_concat_paths() {
138+
let mut old_paths = vec![];
139+
140+
let z = OsString::from("/home/z/.cargo/bin");
141+
let path_z = PathBuf::from(z);
142+
old_paths.push(path_z);
143+
144+
let a = OsString::from("/home/a/.cargo/bin");
145+
let path_a = PathBuf::from(a);
146+
old_paths.push(path_a);
147+
148+
let _a = OsString::from("/home/a/.cargo/bin");
149+
let _path_a = PathBuf::from(_a);
150+
old_paths.push(_path_a);
151+
152+
let mut new_paths = vec![];
153+
154+
let n = OsString::from("/home/n/.cargo/bin");
155+
let path_n = PathBuf::from(n);
156+
new_paths.push(path_n);
157+
158+
let g = OsString::from("/home/g/.cargo/bin");
159+
let path_g = PathBuf::from(g);
160+
new_paths.push(path_g);
161+
162+
let _g = OsString::from("/home/g/.cargo/bin");
163+
let _path_g = PathBuf::from(_g);
164+
new_paths.push(_path_g);
165+
166+
let _z = OsString::from("/home/z/.cargo/bin");
167+
let path_z = PathBuf::from(_z);
168+
old_paths.push(path_z);
169+
170+
let mut unified_paths: Vec<PathBuf> = vec![];
171+
let zpath = OsString::from("/home/z/.cargo/bin");
172+
let _zpath = PathBuf::from(zpath);
173+
unified_paths.push(_zpath);
174+
let apath = OsString::from("/home/a/.cargo/bin");
175+
let _apath = PathBuf::from(apath);
176+
unified_paths.push(_apath);
177+
let npath = OsString::from("/home/n/.cargo/bin");
178+
let _npath = PathBuf::from(npath);
179+
unified_paths.push(_npath);
180+
let gpath = OsString::from("/home/g/.cargo/bin");
181+
let _gpath = PathBuf::from(gpath);
182+
unified_paths.push(_gpath);
183+
184+
assert_eq!(concat_uniq_paths(old_paths, new_paths), unified_paths);
185+
}
186+
187+
#[test]
188+
fn prepend_unique_path() {
189+
let mut vars = HashMap::new();
190+
vars.env(
191+
"PATH",
192+
env::join_paths(vec!["/home/a/.cargo/bin", "/home/b/.cargo/bin"].iter()).unwrap(),
193+
);
194+
let tp = Box::new(currentprocess::TestProcess {
195+
vars,
196+
..Default::default()
197+
});
198+
with_saved_path(&|| {
199+
currentprocess::with(tp.clone(), || {
200+
let mut path_entries = vec![];
201+
let mut cmd = TestCommand::default();
202+
203+
let a = OsString::from("/home/a/.cargo/bin");
204+
let path_a = PathBuf::from(a);
205+
path_entries.push(path_a);
206+
207+
let _a = OsString::from("/home/a/.cargo/bin");
208+
let _path_a = PathBuf::from(_a);
209+
path_entries.push(_path_a);
210+
211+
let z = OsString::from("/home/z/.cargo/bin");
212+
let path_z = PathBuf::from(z);
213+
path_entries.push(path_z);
214+
215+
prepend_path("PATH", path_entries, &mut cmd);
216+
let envs: Vec<_> = cmd.envs.iter().collect();
217+
218+
assert_eq!(
219+
envs,
220+
&[(
221+
&OsString::from("PATH"),
222+
&Some(
223+
env::join_paths(
224+
vec![
225+
"/home/z/.cargo/bin",
226+
"/home/a/.cargo/bin",
227+
"/home/b/.cargo/bin"
228+
]
229+
.iter()
230+
)
231+
.unwrap()
232+
)
233+
),]
234+
);
235+
});
236+
});
237+
}
238+
}

0 commit comments

Comments
 (0)