From 4d46ffbe85c1c87b4720d1731c3a3679fd1bfbd2 Mon Sep 17 00:00:00 2001 From: Teddy_Wang Date: Mon, 27 Dec 2021 20:33:45 -0500 Subject: [PATCH 1/5] Add a progress indicator for cargo clean --- src/cargo/ops/cargo_clean.rs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 1320efac3c7..20e46ddeb9c 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -5,7 +5,7 @@ use crate::ops; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; use crate::util::lev_distance; -use crate::util::Config; +use crate::util::{Config, Progress, ProgressStyle}; use anyhow::Context as _; use cargo_util::paths; @@ -33,8 +33,9 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { // If the doc option is set, we just want to delete the doc directory. if opts.doc { + let mut progress = Progress::with_style("Cleaning", ProgressStyle::Percentage, config); target_dir = target_dir.join("doc"); - return rm_rf(&target_dir.into_path_unlocked(), config); + return rm_rf_with_progress(&target_dir.into_path_unlocked(), &mut progress); } let profiles = Profiles::new(ws, opts.requested_profile)?; @@ -53,7 +54,8 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { // Note that we don't bother grabbing a lock here as we're just going to // blow it all away anyway. if opts.spec.is_empty() { - return rm_rf(&target_dir.into_path_unlocked(), config); + let mut progress = Progress::with_style("Cleaning", ProgressStyle::Percentage, config); + return rm_rf_with_progress(&target_dir.into_path_unlocked(), &mut progress); } // Clean specific packages. @@ -133,8 +135,10 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { } let packages = pkg_set.get_many(pkg_ids)?; - for pkg in packages { + let mut progress = Progress::with_style("Cleaning", ProgressStyle::Ratio, config); + for (pkg_idx, pkg) in packages.iter().enumerate() { let pkg_dir = format!("{}-*", pkg.name()); + progress.tick_now(pkg_idx + 1, packages.len(), &format!(": {}", pkg.name()))?; // Clean fingerprints. for (_, layout) in &layouts_with_host { @@ -231,6 +235,25 @@ fn rm_rf_glob(pattern: &Path, config: &Config) -> CargoResult<()> { Ok(()) } +fn rm_rf_with_progress(path: &Path, progress: &mut Progress<'_>) -> CargoResult<()> { + let num_paths = walkdir::WalkDir::new(path).into_iter().count(); + for (idx, entry) in walkdir::WalkDir::new(path) + .contents_first(true) + .into_iter() + .enumerate() + { + progress.tick(std::cmp::min(idx + 1, num_paths), num_paths, "")?; + if let Ok(entry) = entry { + if entry.file_type().is_dir() { + paths::remove_dir(entry.path())?; + } else { + paths::remove_file(entry.path())?; + } + } + } + Ok(()) +} + fn rm_rf(path: &Path, config: &Config) -> CargoResult<()> { let m = fs::symlink_metadata(path); if m.as_ref().map(|s| s.is_dir()).unwrap_or(false) { From 9ab86fc8466d42beb53de358edfa39de0593f69e Mon Sep 17 00:00:00 2001 From: Teddy_Wang Date: Tue, 28 Dec 2021 23:28:42 -0500 Subject: [PATCH 2/5] Print the bar in verbose mode, keep count of the number of cleaned files --- src/cargo/ops/cargo_clean.rs | 178 ++++++++++++++++++++++++++--------- 1 file changed, 131 insertions(+), 47 deletions(-) diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 20e46ddeb9c..e916b3b50ba 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -33,9 +33,8 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { // If the doc option is set, we just want to delete the doc directory. if opts.doc { - let mut progress = Progress::with_style("Cleaning", ProgressStyle::Percentage, config); target_dir = target_dir.join("doc"); - return rm_rf_with_progress(&target_dir.into_path_unlocked(), &mut progress); + return clean_entire_folder(&target_dir.into_path_unlocked(), &config); } let profiles = Profiles::new(ws, opts.requested_profile)?; @@ -54,8 +53,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { // Note that we don't bother grabbing a lock here as we're just going to // blow it all away anyway. if opts.spec.is_empty() { - let mut progress = Progress::with_style("Cleaning", ProgressStyle::Percentage, config); - return rm_rf_with_progress(&target_dir.into_path_unlocked(), &mut progress); + return clean_entire_folder(&target_dir.into_path_unlocked(), &config); } // Clean specific packages. @@ -135,15 +133,15 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { } let packages = pkg_set.get_many(pkg_ids)?; - let mut progress = Progress::with_style("Cleaning", ProgressStyle::Ratio, config); - for (pkg_idx, pkg) in packages.iter().enumerate() { + let mut progress = CleaningPackagesBar::new(config, packages.len()); + for pkg in packages { let pkg_dir = format!("{}-*", pkg.name()); - progress.tick_now(pkg_idx + 1, packages.len(), &format!(": {}", pkg.name()))?; + progress.on_cleaning_package(&pkg.name())?; // Clean fingerprints. for (_, layout) in &layouts_with_host { let dir = escape_glob_path(layout.fingerprint())?; - rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config)?; + rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config, &mut progress)?; } for target in pkg.targets() { @@ -151,7 +149,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { // Get both the build_script_build and the output directory. for (_, layout) in &layouts_with_host { let dir = escape_glob_path(layout.build())?; - rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config)?; + rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config, &mut progress)?; } continue; } @@ -182,33 +180,33 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { let dir_glob = escape_glob_path(dir)?; let dir_glob = Path::new(&dir_glob); - rm_rf_glob(&dir_glob.join(&hashed_name), config)?; - rm_rf(&dir.join(&unhashed_name), config)?; + rm_rf_glob(&dir_glob.join(&hashed_name), config, &mut progress)?; + rm_rf(&dir.join(&unhashed_name), config, &mut progress)?; // Remove dep-info file generated by rustc. It is not tracked in // file_types. It does not have a prefix. let hashed_dep_info = dir_glob.join(format!("{}-*.d", crate_name)); - rm_rf_glob(&hashed_dep_info, config)?; + rm_rf_glob(&hashed_dep_info, config, &mut progress)?; let unhashed_dep_info = dir.join(format!("{}.d", crate_name)); - rm_rf(&unhashed_dep_info, config)?; + rm_rf(&unhashed_dep_info, config, &mut progress)?; // Remove split-debuginfo files generated by rustc. let split_debuginfo_obj = dir_glob.join(format!("{}.*.o", crate_name)); - rm_rf_glob(&split_debuginfo_obj, config)?; + rm_rf_glob(&split_debuginfo_obj, config, &mut progress)?; let split_debuginfo_dwo = dir_glob.join(format!("{}.*.dwo", crate_name)); - rm_rf_glob(&split_debuginfo_dwo, config)?; + rm_rf_glob(&split_debuginfo_dwo, config, &mut progress)?; // Remove the uplifted copy. if let Some(uplift_dir) = uplift_dir { let uplifted_path = uplift_dir.join(file_type.uplift_filename(target)); - rm_rf(&uplifted_path, config)?; + rm_rf(&uplifted_path, config, &mut progress)?; // Dep-info generated by Cargo itself. let dep_info = uplifted_path.with_extension("d"); - rm_rf(&dep_info, config)?; + rm_rf(&dep_info, config, &mut progress)?; } } // TODO: what to do about build_script_build? let dir = escape_glob_path(layout.incremental())?; let incremental = Path::new(&dir).join(format!("{}-*", crate_name)); - rm_rf_glob(&incremental, config)?; + rm_rf_glob(&incremental, config, &mut progress)?; } } } @@ -224,48 +222,134 @@ fn escape_glob_path(pattern: &Path) -> CargoResult { Ok(glob::Pattern::escape(pattern)) } -fn rm_rf_glob(pattern: &Path, config: &Config) -> CargoResult<()> { +fn rm_rf_glob( + pattern: &Path, + config: &Config, + progress: &mut impl CleaningProgressBar, +) -> CargoResult<()> { // TODO: Display utf8 warning to user? Or switch to globset? let pattern = pattern .to_str() .ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?; for path in glob::glob(pattern)? { - rm_rf(&path?, config)?; + rm_rf(&path?, config, progress)?; } Ok(()) } -fn rm_rf_with_progress(path: &Path, progress: &mut Progress<'_>) -> CargoResult<()> { - let num_paths = walkdir::WalkDir::new(path).into_iter().count(); - for (idx, entry) in walkdir::WalkDir::new(path) - .contents_first(true) - .into_iter() - .enumerate() - { - progress.tick(std::cmp::min(idx + 1, num_paths), num_paths, "")?; - if let Ok(entry) = entry { - if entry.file_type().is_dir() { - paths::remove_dir(entry.path())?; - } else { - paths::remove_file(entry.path())?; - } +fn rm_rf(path: &Path, config: &Config, progress: &mut impl CleaningProgressBar) -> CargoResult<()> { + if fs::symlink_metadata(path).is_err() { + return Ok(()); + } + + config + .shell() + .verbose(|shell| shell.status("Removing", path.display()))?; + progress.display_now()?; + + for entry in walkdir::WalkDir::new(path).contents_first(true) { + let entry = entry?; + progress.on_clean()?; + if entry.file_type().is_dir() { + paths::remove_dir(entry.path()).with_context(|| "could not remove build directory")?; + } else { + paths::remove_file(entry.path()).with_context(|| "failed to remove build artifact")?; } } + Ok(()) } -fn rm_rf(path: &Path, config: &Config) -> CargoResult<()> { - let m = fs::symlink_metadata(path); - if m.as_ref().map(|s| s.is_dir()).unwrap_or(false) { - config - .shell() - .verbose(|shell| shell.status("Removing", path.display()))?; - paths::remove_dir_all(path).with_context(|| "could not remove build directory")?; - } else if m.is_ok() { - config - .shell() - .verbose(|shell| shell.status("Removing", path.display()))?; - paths::remove_file(path).with_context(|| "failed to remove build artifact")?; +fn clean_entire_folder(path: &Path, config: &Config) -> CargoResult<()> { + let num_paths = walkdir::WalkDir::new(path).into_iter().count(); + let mut progress = CleaningFolderBar::new(config, num_paths); + rm_rf(path, config, &mut progress) +} + +trait CleaningProgressBar { + fn display_now(&mut self) -> CargoResult<()>; + fn on_clean(&mut self) -> CargoResult<()>; +} + +struct CleaningFolderBar<'cfg> { + bar: Progress<'cfg>, + max: usize, + cur: usize, +} + +impl<'cfg> CleaningFolderBar<'cfg> { + fn new(cfg: &'cfg Config, max: usize) -> Self { + Self { + bar: Progress::with_style("Cleaning", ProgressStyle::Percentage, cfg), + max, + cur: 0, + } + } + + fn cur_progress(&self) -> usize { + std::cmp::min(self.cur, self.max) + } +} + +impl<'cfg> CleaningProgressBar for CleaningFolderBar<'cfg> { + fn display_now(&mut self) -> CargoResult<()> { + self.bar.tick_now(self.cur_progress(), self.max, "") + } + + fn on_clean(&mut self) -> CargoResult<()> { + self.cur += 1; + self.bar.tick(self.cur_progress(), self.max, "") + } +} + +struct CleaningPackagesBar<'cfg> { + bar: Progress<'cfg>, + max: usize, + cur: usize, + num_files_folders_cleaned: usize, + package_being_cleaned: String, +} + +impl<'cfg> CleaningPackagesBar<'cfg> { + fn new(cfg: &'cfg Config, max: usize) -> Self { + Self { + bar: Progress::with_style("Cleaning", ProgressStyle::Ratio, cfg), + max, + cur: 0, + num_files_folders_cleaned: 0, + package_being_cleaned: String::new(), + } + } + + fn on_cleaning_package(&mut self, package: &str) -> CargoResult<()> { + self.cur += 1; + self.package_being_cleaned = String::from(package); + self.bar + .tick(self.cur_progress(), self.max, &self.format_message()) + } + + fn cur_progress(&self) -> usize { + std::cmp::min(self.cur, self.max) + } + + fn format_message(&self) -> String { + format!( + ": {}, {} files/folders cleaned", + self.package_being_cleaned, self.num_files_folders_cleaned + ) + } +} + +impl<'cfg> CleaningProgressBar for CleaningPackagesBar<'cfg> { + fn display_now(&mut self) -> CargoResult<()> { + self.bar + .tick_now(self.cur_progress(), self.max, &self.format_message()) + } + + fn on_clean(&mut self) -> CargoResult<()> { + self.bar + .tick(self.cur_progress(), self.max, &self.format_message())?; + self.num_files_folders_cleaned += 1; + Ok(()) } - Ok(()) } From 3e1a8ba9ef1e26ba5307d01022cdf98ab1acca6e Mon Sep 17 00:00:00 2001 From: theo-lw Date: Wed, 5 Jan 2022 10:32:33 -0500 Subject: [PATCH 3/5] Remove borow Co-authored-by: Weihang Lo --- src/cargo/ops/cargo_clean.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index e916b3b50ba..42f077d6ddf 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -34,7 +34,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { // If the doc option is set, we just want to delete the doc directory. if opts.doc { target_dir = target_dir.join("doc"); - return clean_entire_folder(&target_dir.into_path_unlocked(), &config); + return clean_entire_folder(&target_dir.into_path_unlocked(), config); } let profiles = Profiles::new(ws, opts.requested_profile)?; From 9b39df9deef4b2b7fc6566203818e20a7e083466 Mon Sep 17 00:00:00 2001 From: theo-lw Date: Wed, 5 Jan 2022 10:32:49 -0500 Subject: [PATCH 4/5] Remove borrow Co-authored-by: Weihang Lo --- src/cargo/ops/cargo_clean.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 42f077d6ddf..d4416ca4d9c 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -53,7 +53,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { // Note that we don't bother grabbing a lock here as we're just going to // blow it all away anyway. if opts.spec.is_empty() { - return clean_entire_folder(&target_dir.into_path_unlocked(), &config); + return clean_entire_folder(&target_dir.into_path_unlocked(), config); } // Clean specific packages. From 60cfe7ef51a9abea164037b6a0939e493441bcb3 Mon Sep 17 00:00:00 2001 From: Teddy_Wang Date: Wed, 5 Jan 2022 10:46:52 -0500 Subject: [PATCH 5/5] Dyn instead of impl --- src/cargo/ops/cargo_clean.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index d4416ca4d9c..08fb3ac181b 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -225,7 +225,7 @@ fn escape_glob_path(pattern: &Path) -> CargoResult { fn rm_rf_glob( pattern: &Path, config: &Config, - progress: &mut impl CleaningProgressBar, + progress: &mut dyn CleaningProgressBar, ) -> CargoResult<()> { // TODO: Display utf8 warning to user? Or switch to globset? let pattern = pattern @@ -237,7 +237,7 @@ fn rm_rf_glob( Ok(()) } -fn rm_rf(path: &Path, config: &Config, progress: &mut impl CleaningProgressBar) -> CargoResult<()> { +fn rm_rf(path: &Path, config: &Config, progress: &mut dyn CleaningProgressBar) -> CargoResult<()> { if fs::symlink_metadata(path).is_err() { return Ok(()); }