@@ -5,7 +5,7 @@ use crate::ops;
55use crate :: util:: errors:: CargoResult ;
66use crate :: util:: interning:: InternedString ;
77use crate :: util:: lev_distance;
8- use crate :: util:: Config ;
8+ use crate :: util:: { Config , Progress , ProgressStyle } ;
99
1010use anyhow:: Context as _;
1111use 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