Skip to content

Commit bc6547a

Browse files
chansukerbtcollins
authored andcommitted
Fix duplicated PATH entries
- Unit test append_path and prepend_path - Order is preserved, second and above instances of a given entry are dropped. Signed-off-by: Robert Collins <[email protected]>
1 parent d34bf02 commit bc6547a

File tree

1 file changed

+183
-7
lines changed

1 file changed

+183
-7
lines changed

src/env_var.rs

+183-7
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,41 @@
1+
use std::collections::HashSet;
12
use std::env;
3+
use std::ffi::OsStr;
24
use std::path::PathBuf;
35
use std::process::Command;
46

57
use crate::process;
68

79
pub const RUST_RECURSION_COUNT_MAX: u32 = 20;
810

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+
932
#[allow(unused)]
10-
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) {
1134
let old_value = process().var_os(name);
1235
let mut parts: Vec<PathBuf>;
1336
if let Some(ref v) = old_value {
14-
parts = env::split_paths(v).collect();
15-
parts.extend(value);
37+
let old_paths: Vec<PathBuf> = env::split_paths(v).collect::<Vec<_>>();
38+
parts = concat_uniq_paths(old_paths, value);
1639
} else {
1740
parts = value;
1841
}
@@ -21,12 +44,12 @@ fn append_path(name: &str, value: Vec<PathBuf>, cmd: &mut Command) {
2144
}
2245
}
2346

24-
pub(crate) fn prepend_path(name: &str, value: Vec<PathBuf>, cmd: &mut Command) {
47+
pub(crate) fn prepend_path<E: ProcessEnvs>(name: &str, value: Vec<PathBuf>, cmd: &mut E) {
2548
let old_value = process().var_os(name);
26-
let mut parts: Vec<PathBuf>;
49+
let parts: Vec<PathBuf>;
2750
if let Some(ref v) = old_value {
28-
parts = value;
29-
parts.extend(env::split_paths(v).collect::<Vec<_>>());
51+
let old_paths: Vec<PathBuf> = env::split_paths(v).collect::<Vec<_>>();
52+
parts = concat_uniq_paths(value, old_paths);
3053
} else {
3154
parts = value;
3255
}
@@ -45,3 +68,156 @@ pub(crate) fn inc(name: &str, cmd: &mut Command) {
4568

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

0 commit comments

Comments
 (0)