Skip to content

Commit dcd24b7

Browse files
authored
Add progress bar for uv cache clean (#8857)
## Summary Closes #8786
1 parent 987d778 commit dcd24b7

File tree

9 files changed

+171
-127
lines changed

9 files changed

+171
-127
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/uv-cache/src/lib.rs

+30-21
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,8 @@ impl Cache {
320320
}
321321

322322
/// Clear the cache, removing all entries.
323-
pub fn clear(&self) -> Result<Removal, io::Error> {
324-
rm_rf(&self.root)
323+
pub fn clear(&self, reporter: Option<&dyn CleanReporter>) -> Result<Removal, io::Error> {
324+
rm_rf(&self.root, reporter)
325325
}
326326

327327
/// Remove a package from the cache.
@@ -379,7 +379,7 @@ impl Cache {
379379
let path = fs_err::canonicalize(entry.path())?;
380380
if !after.contains(&path) && before.contains(&path) {
381381
debug!("Removing dangling cache entry: {}", path.display());
382-
summary += rm_rf(path)?;
382+
summary += rm_rf(path, None)?;
383383
}
384384
}
385385
}
@@ -409,13 +409,13 @@ impl Cache {
409409
if CacheBucket::iter().all(|bucket| entry.file_name() != bucket.to_str()) {
410410
let path = entry.path();
411411
debug!("Removing dangling cache bucket: {}", path.display());
412-
summary += rm_rf(path)?;
412+
summary += rm_rf(path, None)?;
413413
}
414414
} else {
415415
// If the file is not a marker file, remove it.
416416
let path = entry.path();
417417
debug!("Removing dangling cache bucket: {}", path.display());
418-
summary += rm_rf(path)?;
418+
summary += rm_rf(path, None)?;
419419
}
420420
}
421421

@@ -427,7 +427,7 @@ impl Cache {
427427
let entry = entry?;
428428
let path = fs_err::canonicalize(entry.path())?;
429429
debug!("Removing dangling cache environment: {}", path.display());
430-
summary += rm_rf(path)?;
430+
summary += rm_rf(path, None)?;
431431
}
432432
}
433433
Err(err) if err.kind() == io::ErrorKind::NotFound => (),
@@ -444,7 +444,7 @@ impl Cache {
444444
let path = fs_err::canonicalize(entry.path())?;
445445
if path.is_dir() {
446446
debug!("Removing unzipped wheel entry: {}", path.display());
447-
summary += rm_rf(path)?;
447+
summary += rm_rf(path, None)?;
448448
}
449449
}
450450
}
@@ -472,10 +472,10 @@ impl Cache {
472472

473473
if path.is_dir() {
474474
debug!("Removing unzipped built wheel entry: {}", path.display());
475-
summary += rm_rf(path)?;
475+
summary += rm_rf(path, None)?;
476476
} else if path.is_symlink() {
477477
debug!("Removing unzipped built wheel entry: {}", path.display());
478-
summary += rm_rf(path)?;
478+
summary += rm_rf(path, None)?;
479479
}
480480
}
481481
}
@@ -505,7 +505,7 @@ impl Cache {
505505
let path = fs_err::canonicalize(entry.path())?;
506506
if !references.contains(&path) {
507507
debug!("Removing dangling cache archive: {}", path.display());
508-
summary += rm_rf(path)?;
508+
summary += rm_rf(path, None)?;
509509
}
510510
}
511511
}
@@ -517,6 +517,15 @@ impl Cache {
517517
}
518518
}
519519

520+
pub trait CleanReporter: Send + Sync {
521+
/// Called after one file or directory is removed.
522+
fn on_clean(&self);
523+
/// Called after a package is cleaned.
524+
fn on_clean_package(&self, _package: &str, _removal: &Removal) {}
525+
/// Called after all files and directories are removed.
526+
fn on_complete(&self);
527+
}
528+
520529
/// The different kinds of data in the cache are stored in different bucket, which in our case
521530
/// are subdirectories of the cache root.
522531
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
@@ -800,32 +809,32 @@ impl CacheBucket {
800809
Self::Wheels => {
801810
// For `pypi` wheels, we expect a directory per package (indexed by name).
802811
let root = cache.bucket(self).join(WheelCacheKind::Pypi);
803-
summary += rm_rf(root.join(name.to_string()))?;
812+
summary += rm_rf(root.join(name.to_string()), None)?;
804813

805814
// For alternate indices, we expect a directory for every index (under an `index`
806815
// subdirectory), followed by a directory per package (indexed by name).
807816
let root = cache.bucket(self).join(WheelCacheKind::Index);
808817
for directory in directories(root) {
809-
summary += rm_rf(directory.join(name.to_string()))?;
818+
summary += rm_rf(directory.join(name.to_string()), None)?;
810819
}
811820

812821
// For direct URLs, we expect a directory for every URL, followed by a
813822
// directory per package (indexed by name).
814823
let root = cache.bucket(self).join(WheelCacheKind::Url);
815824
for directory in directories(root) {
816-
summary += rm_rf(directory.join(name.to_string()))?;
825+
summary += rm_rf(directory.join(name.to_string()), None)?;
817826
}
818827
}
819828
Self::SourceDistributions => {
820829
// For `pypi` wheels, we expect a directory per package (indexed by name).
821830
let root = cache.bucket(self).join(WheelCacheKind::Pypi);
822-
summary += rm_rf(root.join(name.to_string()))?;
831+
summary += rm_rf(root.join(name.to_string()), None)?;
823832

824833
// For alternate indices, we expect a directory for every index (under an `index`
825834
// subdirectory), followed by a directory per package (indexed by name).
826835
let root = cache.bucket(self).join(WheelCacheKind::Index);
827836
for directory in directories(root) {
828-
summary += rm_rf(directory.join(name.to_string()))?;
837+
summary += rm_rf(directory.join(name.to_string()), None)?;
829838
}
830839

831840
// For direct URLs, we expect a directory for every URL, followed by a
@@ -834,7 +843,7 @@ impl CacheBucket {
834843
let root = cache.bucket(self).join(WheelCacheKind::Url);
835844
for url in directories(root) {
836845
if directories(&url).any(|version| is_match(&version, name)) {
837-
summary += rm_rf(url)?;
846+
summary += rm_rf(url, None)?;
838847
}
839848
}
840849

@@ -844,7 +853,7 @@ impl CacheBucket {
844853
let root = cache.bucket(self).join(WheelCacheKind::Path);
845854
for path in directories(root) {
846855
if directories(&path).any(|version| is_match(&version, name)) {
847-
summary += rm_rf(path)?;
856+
summary += rm_rf(path, None)?;
848857
}
849858
}
850859

@@ -855,28 +864,28 @@ impl CacheBucket {
855864
for repository in directories(root) {
856865
for sha in directories(repository) {
857866
if is_match(&sha, name) {
858-
summary += rm_rf(sha)?;
867+
summary += rm_rf(sha, None)?;
859868
}
860869
}
861870
}
862871
}
863872
Self::Simple => {
864873
// For `pypi` wheels, we expect a rkyv file per package, indexed by name.
865874
let root = cache.bucket(self).join(WheelCacheKind::Pypi);
866-
summary += rm_rf(root.join(format!("{name}.rkyv")))?;
875+
summary += rm_rf(root.join(format!("{name}.rkyv")), None)?;
867876

868877
// For alternate indices, we expect a directory for every index (under an `index`
869878
// subdirectory), followed by a directory per package (indexed by name).
870879
let root = cache.bucket(self).join(WheelCacheKind::Index);
871880
for directory in directories(root) {
872-
summary += rm_rf(directory.join(format!("{name}.rkyv")))?;
881+
summary += rm_rf(directory.join(format!("{name}.rkyv")), None)?;
873882
}
874883
}
875884
Self::FlatIndex => {
876885
// We can't know if the flat index includes a package, so we just remove the entire
877886
// cache entry.
878887
let root = cache.bucket(self);
879-
summary += rm_rf(root)?;
888+
summary += rm_rf(root, None)?;
880889
}
881890
Self::Git => {
882891
// Nothing to do.

crates/uv-cache/src/removal.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
use std::io;
66
use std::path::Path;
77

8+
use crate::CleanReporter;
9+
810
/// Remove a file or directory and all its contents, returning a [`Removal`] with
911
/// the number of files and directories removed, along with a total byte count.
10-
pub fn rm_rf(path: impl AsRef<Path>) -> io::Result<Removal> {
12+
pub fn rm_rf(path: impl AsRef<Path>, reporter: Option<&dyn CleanReporter>) -> io::Result<Removal> {
1113
let mut removal = Removal::default();
12-
removal.rm_rf(path.as_ref())?;
14+
removal.rm_rf(path.as_ref(), reporter)?;
1315
Ok(removal)
1416
}
1517

@@ -28,7 +30,7 @@ pub struct Removal {
2830

2931
impl Removal {
3032
/// Recursively remove a file or directory and all its contents.
31-
fn rm_rf(&mut self, path: &Path) -> io::Result<()> {
33+
fn rm_rf(&mut self, path: &Path, reporter: Option<&dyn CleanReporter>) -> io::Result<()> {
3234
let metadata = match fs_err::symlink_metadata(path) {
3335
Ok(metadata) => metadata,
3436
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(()),
@@ -47,6 +49,8 @@ impl Removal {
4749
remove_file(path)?;
4850
}
4951

52+
reporter.map(CleanReporter::on_clean);
53+
5054
return Ok(());
5155
}
5256

@@ -61,7 +65,7 @@ impl Removal {
6165
if set_readable(dir).unwrap_or(false) {
6266
// Retry the operation; if we _just_ `self.rm_rf(dir)` and continue,
6367
// `walkdir` may give us duplicate entries for the directory.
64-
return self.rm_rf(path);
68+
return self.rm_rf(path, reporter);
6569
}
6670
}
6771
}
@@ -88,8 +92,12 @@ impl Removal {
8892
}
8993
remove_file(entry.path())?;
9094
}
95+
96+
reporter.map(CleanReporter::on_clean);
9197
}
9298

99+
reporter.map(CleanReporter::on_complete);
100+
93101
Ok(())
94102
}
95103
}

crates/uv-distribution/src/source/mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1995,8 +1995,8 @@ pub fn prune(cache: &Cache) -> Result<Removal, Error> {
19951995
"Removing dangling source revision: {}",
19961996
sibling.path().display()
19971997
);
1998-
removal +=
1999-
uv_cache::rm_rf(sibling.path()).map_err(Error::CacheWrite)?;
1998+
removal += uv_cache::rm_rf(sibling.path(), None)
1999+
.map_err(Error::CacheWrite)?;
20002000
}
20012001
}
20022002
}
@@ -2020,8 +2020,8 @@ pub fn prune(cache: &Cache) -> Result<Removal, Error> {
20202020
"Removing dangling source revision: {}",
20212021
sibling.path().display()
20222022
);
2023-
removal +=
2024-
uv_cache::rm_rf(sibling.path()).map_err(Error::CacheWrite)?;
2023+
removal += uv_cache::rm_rf(sibling.path(), None)
2024+
.map_err(Error::CacheWrite)?;
20252025
}
20262026
}
20272027
}

crates/uv/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ tracing-subscriber = { workspace = true, features = ["json"] }
9393
tracing-tree = { workspace = true }
9494
unicode-width = { workspace = true }
9595
url = { workspace = true }
96+
walkdir = { workspace = true }
9697
which = { workspace = true }
9798
zip = { workspace = true }
9899

0 commit comments

Comments
 (0)