Skip to content

Commit f51e799

Browse files
committed
Auto merge of #10236 - theo-lw:master, r=weihanglo
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)
2 parents 5f400f0 + 60cfe7e commit f51e799

File tree

1 file changed

+135
-28
lines changed

1 file changed

+135
-28
lines changed

src/cargo/ops/cargo_clean.rs

Lines changed: 135 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::ops;
55
use crate::util::errors::CargoResult;
66
use crate::util::interning::InternedString;
77
use crate::util::lev_distance;
8-
use crate::util::Config;
8+
use crate::util::{Config, Progress, ProgressStyle};
99

1010
use anyhow::Context as _;
1111
use cargo_util::paths;
@@ -34,7 +34,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
3434
// If the doc option is set, we just want to delete the doc directory.
3535
if opts.doc {
3636
target_dir = target_dir.join("doc");
37-
return rm_rf(&target_dir.into_path_unlocked(), config);
37+
return clean_entire_folder(&target_dir.into_path_unlocked(), config);
3838
}
3939

4040
let profiles = Profiles::new(ws, opts.requested_profile)?;
@@ -53,7 +53,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
5353
// Note that we don't bother grabbing a lock here as we're just going to
5454
// blow it all away anyway.
5555
if opts.spec.is_empty() {
56-
return rm_rf(&target_dir.into_path_unlocked(), config);
56+
return clean_entire_folder(&target_dir.into_path_unlocked(), config);
5757
}
5858

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

136+
let mut progress = CleaningPackagesBar::new(config, packages.len());
136137
for pkg in packages {
137138
let pkg_dir = format!("{}-*", pkg.name());
139+
progress.on_cleaning_package(&pkg.name())?;
138140

139141
// Clean fingerprints.
140142
for (_, layout) in &layouts_with_host {
141143
let dir = escape_glob_path(layout.fingerprint())?;
142-
rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config)?;
144+
rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config, &mut progress)?;
143145
}
144146

145147
for target in pkg.targets() {
146148
if target.is_custom_build() {
147149
// Get both the build_script_build and the output directory.
148150
for (_, layout) in &layouts_with_host {
149151
let dir = escape_glob_path(layout.build())?;
150-
rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config)?;
152+
rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config, &mut progress)?;
151153
}
152154
continue;
153155
}
@@ -178,33 +180,33 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
178180
let dir_glob = escape_glob_path(dir)?;
179181
let dir_glob = Path::new(&dir_glob);
180182

181-
rm_rf_glob(&dir_glob.join(&hashed_name), config)?;
182-
rm_rf(&dir.join(&unhashed_name), config)?;
183+
rm_rf_glob(&dir_glob.join(&hashed_name), config, &mut progress)?;
184+
rm_rf(&dir.join(&unhashed_name), config, &mut progress)?;
183185
// Remove dep-info file generated by rustc. It is not tracked in
184186
// file_types. It does not have a prefix.
185187
let hashed_dep_info = dir_glob.join(format!("{}-*.d", crate_name));
186-
rm_rf_glob(&hashed_dep_info, config)?;
188+
rm_rf_glob(&hashed_dep_info, config, &mut progress)?;
187189
let unhashed_dep_info = dir.join(format!("{}.d", crate_name));
188-
rm_rf(&unhashed_dep_info, config)?;
190+
rm_rf(&unhashed_dep_info, config, &mut progress)?;
189191
// Remove split-debuginfo files generated by rustc.
190192
let split_debuginfo_obj = dir_glob.join(format!("{}.*.o", crate_name));
191-
rm_rf_glob(&split_debuginfo_obj, config)?;
193+
rm_rf_glob(&split_debuginfo_obj, config, &mut progress)?;
192194
let split_debuginfo_dwo = dir_glob.join(format!("{}.*.dwo", crate_name));
193-
rm_rf_glob(&split_debuginfo_dwo, config)?;
195+
rm_rf_glob(&split_debuginfo_dwo, config, &mut progress)?;
194196

195197
// Remove the uplifted copy.
196198
if let Some(uplift_dir) = uplift_dir {
197199
let uplifted_path = uplift_dir.join(file_type.uplift_filename(target));
198-
rm_rf(&uplifted_path, config)?;
200+
rm_rf(&uplifted_path, config, &mut progress)?;
199201
// Dep-info generated by Cargo itself.
200202
let dep_info = uplifted_path.with_extension("d");
201-
rm_rf(&dep_info, config)?;
203+
rm_rf(&dep_info, config, &mut progress)?;
202204
}
203205
}
204206
// TODO: what to do about build_script_build?
205207
let dir = escape_glob_path(layout.incremental())?;
206208
let incremental = Path::new(&dir).join(format!("{}-*", crate_name));
207-
rm_rf_glob(&incremental, config)?;
209+
rm_rf_glob(&incremental, config, &mut progress)?;
208210
}
209211
}
210212
}
@@ -220,29 +222,134 @@ fn escape_glob_path(pattern: &Path) -> CargoResult<String> {
220222
Ok(glob::Pattern::escape(pattern))
221223
}
222224

223-
fn rm_rf_glob(pattern: &Path, config: &Config) -> CargoResult<()> {
225+
fn rm_rf_glob(
226+
pattern: &Path,
227+
config: &Config,
228+
progress: &mut dyn CleaningProgressBar,
229+
) -> CargoResult<()> {
224230
// TODO: Display utf8 warning to user? Or switch to globset?
225231
let pattern = pattern
226232
.to_str()
227233
.ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?;
228234
for path in glob::glob(pattern)? {
229-
rm_rf(&path?, config)?;
235+
rm_rf(&path?, config, progress)?;
230236
}
231237
Ok(())
232238
}
233239

234-
fn rm_rf(path: &Path, config: &Config) -> CargoResult<()> {
235-
let m = fs::symlink_metadata(path);
236-
if m.as_ref().map(|s| s.is_dir()).unwrap_or(false) {
237-
config
238-
.shell()
239-
.verbose(|shell| shell.status("Removing", path.display()))?;
240-
paths::remove_dir_all(path).with_context(|| "could not remove build directory")?;
241-
} else if m.is_ok() {
242-
config
243-
.shell()
244-
.verbose(|shell| shell.status("Removing", path.display()))?;
245-
paths::remove_file(path).with_context(|| "failed to remove build artifact")?;
240+
fn rm_rf(path: &Path, config: &Config, progress: &mut dyn CleaningProgressBar) -> CargoResult<()> {
241+
if fs::symlink_metadata(path).is_err() {
242+
return Ok(());
246243
}
244+
245+
config
246+
.shell()
247+
.verbose(|shell| shell.status("Removing", path.display()))?;
248+
progress.display_now()?;
249+
250+
for entry in walkdir::WalkDir::new(path).contents_first(true) {
251+
let entry = entry?;
252+
progress.on_clean()?;
253+
if entry.file_type().is_dir() {
254+
paths::remove_dir(entry.path()).with_context(|| "could not remove build directory")?;
255+
} else {
256+
paths::remove_file(entry.path()).with_context(|| "failed to remove build artifact")?;
257+
}
258+
}
259+
247260
Ok(())
248261
}
262+
263+
fn clean_entire_folder(path: &Path, config: &Config) -> CargoResult<()> {
264+
let num_paths = walkdir::WalkDir::new(path).into_iter().count();
265+
let mut progress = CleaningFolderBar::new(config, num_paths);
266+
rm_rf(path, config, &mut progress)
267+
}
268+
269+
trait CleaningProgressBar {
270+
fn display_now(&mut self) -> CargoResult<()>;
271+
fn on_clean(&mut self) -> CargoResult<()>;
272+
}
273+
274+
struct CleaningFolderBar<'cfg> {
275+
bar: Progress<'cfg>,
276+
max: usize,
277+
cur: usize,
278+
}
279+
280+
impl<'cfg> CleaningFolderBar<'cfg> {
281+
fn new(cfg: &'cfg Config, max: usize) -> Self {
282+
Self {
283+
bar: Progress::with_style("Cleaning", ProgressStyle::Percentage, cfg),
284+
max,
285+
cur: 0,
286+
}
287+
}
288+
289+
fn cur_progress(&self) -> usize {
290+
std::cmp::min(self.cur, self.max)
291+
}
292+
}
293+
294+
impl<'cfg> CleaningProgressBar for CleaningFolderBar<'cfg> {
295+
fn display_now(&mut self) -> CargoResult<()> {
296+
self.bar.tick_now(self.cur_progress(), self.max, "")
297+
}
298+
299+
fn on_clean(&mut self) -> CargoResult<()> {
300+
self.cur += 1;
301+
self.bar.tick(self.cur_progress(), self.max, "")
302+
}
303+
}
304+
305+
struct CleaningPackagesBar<'cfg> {
306+
bar: Progress<'cfg>,
307+
max: usize,
308+
cur: usize,
309+
num_files_folders_cleaned: usize,
310+
package_being_cleaned: String,
311+
}
312+
313+
impl<'cfg> CleaningPackagesBar<'cfg> {
314+
fn new(cfg: &'cfg Config, max: usize) -> Self {
315+
Self {
316+
bar: Progress::with_style("Cleaning", ProgressStyle::Ratio, cfg),
317+
max,
318+
cur: 0,
319+
num_files_folders_cleaned: 0,
320+
package_being_cleaned: String::new(),
321+
}
322+
}
323+
324+
fn on_cleaning_package(&mut self, package: &str) -> CargoResult<()> {
325+
self.cur += 1;
326+
self.package_being_cleaned = String::from(package);
327+
self.bar
328+
.tick(self.cur_progress(), self.max, &self.format_message())
329+
}
330+
331+
fn cur_progress(&self) -> usize {
332+
std::cmp::min(self.cur, self.max)
333+
}
334+
335+
fn format_message(&self) -> String {
336+
format!(
337+
": {}, {} files/folders cleaned",
338+
self.package_being_cleaned, self.num_files_folders_cleaned
339+
)
340+
}
341+
}
342+
343+
impl<'cfg> CleaningProgressBar for CleaningPackagesBar<'cfg> {
344+
fn display_now(&mut self) -> CargoResult<()> {
345+
self.bar
346+
.tick_now(self.cur_progress(), self.max, &self.format_message())
347+
}
348+
349+
fn on_clean(&mut self) -> CargoResult<()> {
350+
self.bar
351+
.tick(self.cur_progress(), self.max, &self.format_message())?;
352+
self.num_files_folders_cleaned += 1;
353+
Ok(())
354+
}
355+
}

0 commit comments

Comments
 (0)