Skip to content

Commit

Permalink
Auto merge of #10236 - theo-lw:master, r=weihanglo
Browse files Browse the repository at this point in the history
Add a progress indicator for `cargo clean`

Closes #9981.

Cleaning the entire `target` directory:

[![Cleaning `target`](https://i.gyazo.com/e32536fe5538cf5a394d777a9d838890.gif)](https://gyazo.com/e32536fe5538cf5a394d777a9d838890)

Cleaning some packages:

[![Cleaning a few packages](https://i.gyazo.com/860f1833523afc0629d5547d97c146c2.gif)](https://gyazo.com/860f1833523afc0629d5547d97c146c2)
  • Loading branch information
bors committed Apr 10, 2022
2 parents 5f400f0 + 60cfe7e commit f51e799
Showing 1 changed file with 135 additions and 28 deletions.
163 changes: 135 additions & 28 deletions src/cargo/ops/cargo_clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 rm_rf(&target_dir.into_path_unlocked(), config);
return clean_entire_folder(&target_dir.into_path_unlocked(), config);
}

let profiles = Profiles::new(ws, opts.requested_profile)?;
Expand All @@ -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 rm_rf(&target_dir.into_path_unlocked(), config);
return clean_entire_folder(&target_dir.into_path_unlocked(), config);
}

// Clean specific packages.
Expand Down Expand Up @@ -133,21 +133,23 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
}
let packages = pkg_set.get_many(pkg_ids)?;

let mut progress = CleaningPackagesBar::new(config, packages.len());
for pkg in packages {
let pkg_dir = 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() {
if target.is_custom_build() {
// 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;
}
Expand Down Expand Up @@ -178,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)?;
}
}
}
Expand All @@ -220,29 +222,134 @@ fn escape_glob_path(pattern: &Path) -> CargoResult<String> {
Ok(glob::Pattern::escape(pattern))
}

fn rm_rf_glob(pattern: &Path, config: &Config) -> CargoResult<()> {
fn rm_rf_glob(
pattern: &Path,
config: &Config,
progress: &mut dyn 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(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 rm_rf(path: &Path, config: &Config, progress: &mut dyn 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 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(())
}
}

0 comments on commit f51e799

Please sign in to comment.