Skip to content

Commit 1a5a407

Browse files
authored
Merge pull request #634 from inejge/remove-cleanup
Remove empty directories after component uninstall
2 parents b5becd3 + dd95c01 commit 1a5a407

File tree

2 files changed

+107
-2
lines changed

2 files changed

+107
-2
lines changed

src/rustup-dist/src/component/components.rs

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,112 @@ impl Component {
184184
// TODO: If this is the last component remove the components file
185185
// and the version file.
186186

187+
// Track visited directories
188+
use std::collections::HashSet;
189+
use std::collections::hash_set::IntoIter;
190+
use std::fs::read_dir;
191+
192+
// dirs will contain the set of longest disjoint directory paths seen
193+
// ancestors help in filtering seen paths and constructing dirs
194+
// All seen paths must be relative to avoid surprises
195+
struct PruneSet {
196+
dirs: HashSet<PathBuf>,
197+
ancestors: HashSet<PathBuf>,
198+
prefix: PathBuf,
199+
}
200+
201+
impl PruneSet {
202+
fn seen(&mut self, mut path: PathBuf) {
203+
if !path.is_relative() || !path.pop() {
204+
return;
205+
}
206+
if self.dirs.contains(&path) || self.ancestors.contains(&path) {
207+
return;
208+
}
209+
self.dirs.insert(path.clone());
210+
while path.pop() {
211+
if path.file_name().is_none() {
212+
break;
213+
}
214+
if self.dirs.contains(&path) {
215+
self.dirs.remove(&path);
216+
}
217+
if self.ancestors.contains(&path) {
218+
break;
219+
}
220+
self.ancestors.insert(path.clone());
221+
}
222+
}
223+
}
224+
225+
struct PruneIter {
226+
iter: IntoIter<PathBuf>,
227+
path_buf: Option<PathBuf>,
228+
prefix: PathBuf,
229+
}
230+
231+
impl IntoIterator for PruneSet {
232+
type Item = PathBuf;
233+
type IntoIter = PruneIter;
234+
235+
fn into_iter(self) -> Self::IntoIter {
236+
PruneIter {
237+
iter: self.dirs.into_iter(),
238+
path_buf: None,
239+
prefix: self.prefix,
240+
}
241+
}
242+
}
243+
244+
// Returns only empty directories
245+
impl Iterator for PruneIter {
246+
type Item = PathBuf;
247+
248+
fn next(&mut self) -> Option<Self::Item> {
249+
self.path_buf = match self.path_buf {
250+
None => self.iter.next(),
251+
Some(_) => {
252+
let mut path_buf = self.path_buf.take().unwrap();
253+
match path_buf.file_name() {
254+
Some(_) => if path_buf.pop() { Some(path_buf) } else { None },
255+
None => self.iter.next(),
256+
}
257+
},
258+
};
259+
if self.path_buf.is_none() {
260+
return None;
261+
}
262+
let full_path = self.prefix.join(self.path_buf.as_ref().unwrap());
263+
let empty = match read_dir(full_path) {
264+
Ok(dir) => dir.count() == 0,
265+
Err(_) => false,
266+
};
267+
if empty {
268+
self.path_buf.clone()
269+
} else {
270+
// No dir above can be empty, go to next path in dirs
271+
self.path_buf = None;
272+
self.next()
273+
}
274+
}
275+
}
276+
187277
// Remove parts
278+
let mut pset = PruneSet {
279+
dirs: HashSet::new(),
280+
ancestors: HashSet::new(),
281+
prefix: self.components.prefix.abs_path(""),
282+
};
188283
for part in try!(self.parts()).into_iter().rev() {
189284
match &*part.0 {
190-
"file" => try!(tx.remove_file(&self.name, part.1)),
191-
"dir" => try!(tx.remove_dir(&self.name, part.1)),
285+
"file" => try!(tx.remove_file(&self.name, part.1.clone())),
286+
"dir" => try!(tx.remove_dir(&self.name, part.1.clone())),
192287
_ => return Err(ErrorKind::CorruptComponent(self.name.clone()).into()),
193288
}
289+
pset.seen(part.1);
290+
}
291+
for empty_dir in pset {
292+
try!(tx.remove_dir(&self.name, empty_dir));
194293
}
195294

196295
// Remove component manifest

tests/cli-v2.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,12 @@ fn remove_target() {
551551
let path = format!("toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib",
552552
this_host_triple(), clitools::CROSS_ARCH1);
553553
assert!(!config.rustupdir.join(path).exists());
554+
let path = format!("toolchains/nightly-{}/lib/rustlib/{}/lib",
555+
this_host_triple(), clitools::CROSS_ARCH1);
556+
assert!(!config.rustupdir.join(path).exists());
557+
let path = format!("toolchains/nightly-{}/lib/rustlib/{}",
558+
this_host_triple(), clitools::CROSS_ARCH1);
559+
assert!(!config.rustupdir.join(path).exists());
554560
});
555561
}
556562

0 commit comments

Comments
 (0)