]> git.proxmox.com Git - cargo.git/blame - src/cargo/ops/cargo_clean.rs
bump version to 0.66.0+pve1-1~bpo11+pve1
[cargo.git] / src / cargo / ops / cargo_clean.rs
CommitLineData
a8997cbc
EH
1use crate::core::compiler::{CompileKind, CompileMode, Layout, RustcTargetData};
2use crate::core::profiles::Profiles;
7f73a6c7 3use crate::core::{PackageIdSpec, TargetKind, Workspace};
04ddd4d0 4use crate::ops;
ebca5190 5use crate::util::errors::CargoResult;
7f73a6c7 6use crate::util::interning::InternedString;
b04c7fb8 7use crate::util::lev_distance;
4d46ffbe 8use crate::util::{Config, Progress, ProgressStyle};
ebca5190
WL
9
10use anyhow::Context as _;
1dae5acb 11use cargo_util::paths;
a8997cbc
EH
12use std::fs;
13use std::path::Path;
0025dbde 14
2fe0bf83 15pub struct CleanOptions<'a> {
2fe0bf83 16 pub config: &'a Config,
cde85c10 17 /// A list of packages to clean. If empty, everything is cleaned.
d8d1f13b 18 pub spec: Vec<String>,
cde85c10 19 /// The target arch triple to clean, or None for the host arch
3fd28143 20 pub targets: Vec<String>,
cde85c10 21 /// Whether to clean the release directory
04e80b5e
DA
22 pub profile_specified: bool,
23 /// Whether to clean the directory of a certain build profile
77ee608d 24 pub requested_profile: InternedString,
2ea2fafc
D
25 /// Whether to just clean the doc directory
26 pub doc: bool,
325c5f2d 27}
dd2459cd 28
3492a390 29/// Cleans the package's build artifacts.
b8b7faee 30pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
c5d0c34b 31 let mut target_dir = ws.target_dir();
4de95d15 32 let config = ws.config();
dd2459cd 33
2ea2fafc
D
34 // If the doc option is set, we just want to delete the doc directory.
35 if opts.doc {
c5d0c34b 36 target_dir = target_dir.join("doc");
3e1a8ba9 37 return clean_entire_folder(&target_dir.into_path_unlocked(), config);
c5d0c34b
CA
38 }
39
ed4568e1 40 let profiles = Profiles::new(ws, opts.requested_profile)?;
c2a8ca10 41
54216108 42 if opts.profile_specified {
04e80b5e 43 // After parsing profiles we know the dir-name of the profile, if a profile
c86213f1 44 // was passed from the command line. If so, delete only the directory of
04e80b5e 45 // that profile.
77ee608d 46 let dir_name = profiles.get_dir_name();
04e80b5e
DA
47 target_dir = target_dir.join(dir_name);
48 }
49
bacb6be3 50 // If we have a spec, then we need to delete some packages, otherwise, just
325c5f2d 51 // remove the whole target directory and be done with it!
a9fd1c2c
AC
52 //
53 // Note that we don't bother grabbing a lock here as we're just going to
54 // blow it all away anyway.
8628dbeb 55 if opts.spec.is_empty() {
9b39df9d 56 return clean_entire_folder(&target_dir.into_path_unlocked(), config);
dad79c91 57 }
a8997cbc
EH
58
59 // Clean specific packages.
60 let requested_kinds = CompileKind::from_requested_targets(config, &opts.targets)?;
61 let target_data = RustcTargetData::new(ws, &requested_kinds)?;
62 let (pkg_set, resolve) = ops::resolve_ws(ws)?;
63 let prof_dir_name = profiles.get_dir_name();
64 let host_layout = Layout::new(ws, None, &prof_dir_name)?;
65 // Convert requested kinds to a Vec of layouts.
66 let target_layouts: Vec<(CompileKind, Layout)> = requested_kinds
67 .into_iter()
68 .filter_map(|kind| match kind {
69 CompileKind::Target(target) => match Layout::new(ws, Some(target), &prof_dir_name) {
70 Ok(layout) => Some(Ok((kind, layout))),
71 Err(e) => Some(Err(e)),
72 },
73 CompileKind::Host => None,
74 })
75 .collect::<CargoResult<_>>()?;
76 // A Vec of layouts. This is a little convoluted because there can only be
77 // one host_layout.
78 let layouts = if opts.targets.is_empty() {
79 vec![(CompileKind::Host, &host_layout)]
80 } else {
81 target_layouts
82 .iter()
83 .map(|(kind, layout)| (*kind, layout))
84 .collect()
85 };
86 // Create a Vec that also includes the host for things that need to clean both.
87 let layouts_with_host: Vec<(CompileKind, &Layout)> =
88 std::iter::once((CompileKind::Host, &host_layout))
89 .chain(layouts.iter().map(|(k, l)| (*k, *l)))
90 .collect();
91
92 // Cleaning individual rustdoc crates is currently not supported.
93 // For example, the search index would need to be rebuilt to fully
94 // remove it (otherwise you're left with lots of broken links).
95 // Doc tests produce no output.
96
97 // Get Packages for the specified specs.
bd5f5638 98 let mut pkg_ids = Vec::new();
a8997cbc
EH
99 for spec_str in opts.spec.iter() {
100 // Translate the spec to a Package.
101 let spec = PackageIdSpec::parse(spec_str)?;
102 if spec.version().is_some() {
103 config.shell().warn(&format!(
104 "version qualifier in `-p {}` is ignored, \
105 cleaning all versions of `{}` found",
106 spec_str,
107 spec.name()
108 ))?;
109 }
110 if spec.url().is_some() {
111 config.shell().warn(&format!(
112 "url qualifier in `-p {}` ignored, \
113 cleaning all versions of `{}` found",
114 spec_str,
115 spec.name()
116 ))?;
325c5f2d 117 }
a8997cbc
EH
118 let matches: Vec<_> = resolve.iter().filter(|id| spec.matches(*id)).collect();
119 if matches.is_empty() {
b04c7fb8
EH
120 let mut suggestion = String::new();
121 suggestion.push_str(&lev_distance::closest_msg(
122 &spec.name(),
123 resolve.iter(),
124 |id| id.name().as_str(),
125 ));
126 anyhow::bail!(
127 "package ID specification `{}` did not match any packages{}",
128 spec,
129 suggestion
130 );
a8997cbc 131 }
bd5f5638 132 pkg_ids.extend(matches);
3f110840 133 }
bd5f5638 134 let packages = pkg_set.get_many(pkg_ids)?;
3f110840 135
9ab86fc8
T
136 let mut progress = CleaningPackagesBar::new(config, packages.len());
137 for pkg in packages {
a8997cbc 138 let pkg_dir = format!("{}-*", pkg.name());
9ab86fc8 139 progress.on_cleaning_package(&pkg.name())?;
a8997cbc
EH
140
141 // Clean fingerprints.
142 for (_, layout) in &layouts_with_host {
effc7204 143 let dir = escape_glob_path(layout.fingerprint())?;
9ab86fc8 144 rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config, &mut progress)?;
935adb31 145 }
a8997cbc
EH
146
147 for target in pkg.targets() {
148 if target.is_custom_build() {
149 // Get both the build_script_build and the output directory.
150 for (_, layout) in &layouts_with_host {
effc7204 151 let dir = escape_glob_path(layout.build())?;
9ab86fc8 152 rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config, &mut progress)?;
a8997cbc
EH
153 }
154 continue;
41579bad 155 }
a8997cbc
EH
156 let crate_name = target.crate_name();
157 for &mode in &[
158 CompileMode::Build,
159 CompileMode::Test,
160 CompileMode::Check { test: false },
161 ] {
162 for (compile_kind, layout) in &layouts {
163 let triple = target_data.short_name(compile_kind);
164
165 let (file_types, _unsupported) = target_data
166 .info(*compile_kind)
167 .rustc_outputs(mode, target.kind(), triple)?;
6263d726
EH
168 let (dir, uplift_dir) = match target.kind() {
169 TargetKind::ExampleBin | TargetKind::ExampleLib(..) => {
170 (layout.examples(), Some(layout.examples()))
171 }
172 // Tests/benchmarks are never uplifted.
173 TargetKind::Test | TargetKind::Bench => (layout.deps(), None),
174 _ => (layout.deps(), Some(layout.dest())),
a8997cbc
EH
175 };
176 for file_type in file_types {
177 // Some files include a hash in the filename, some don't.
178 let hashed_name = file_type.output_filename(target, Some("*"));
179 let unhashed_name = file_type.output_filename(target, None);
effc7204
RW
180 let dir_glob = escape_glob_path(dir)?;
181 let dir_glob = Path::new(&dir_glob);
182
9ab86fc8
T
183 rm_rf_glob(&dir_glob.join(&hashed_name), config, &mut progress)?;
184 rm_rf(&dir.join(&unhashed_name), config, &mut progress)?;
a8997cbc
EH
185 // Remove dep-info file generated by rustc. It is not tracked in
186 // file_types. It does not have a prefix.
effc7204 187 let hashed_dep_info = dir_glob.join(format!("{}-*.d", crate_name));
9ab86fc8 188 rm_rf_glob(&hashed_dep_info, config, &mut progress)?;
ed4568e1 189 let unhashed_dep_info = dir.join(format!("{}.d", crate_name));
9ab86fc8 190 rm_rf(&unhashed_dep_info, config, &mut progress)?;
ed4568e1 191 // Remove split-debuginfo files generated by rustc.
effc7204 192 let split_debuginfo_obj = dir_glob.join(format!("{}.*.o", crate_name));
9ab86fc8 193 rm_rf_glob(&split_debuginfo_obj, config, &mut progress)?;
effc7204 194 let split_debuginfo_dwo = dir_glob.join(format!("{}.*.dwo", crate_name));
9ab86fc8 195 rm_rf_glob(&split_debuginfo_dwo, config, &mut progress)?;
3f7b09cc 196
a8997cbc 197 // Remove the uplifted copy.
6263d726
EH
198 if let Some(uplift_dir) = uplift_dir {
199 let uplifted_path = uplift_dir.join(file_type.uplift_filename(target));
9ab86fc8 200 rm_rf(&uplifted_path, config, &mut progress)?;
6263d726
EH
201 // Dep-info generated by Cargo itself.
202 let dep_info = uplifted_path.with_extension("d");
9ab86fc8 203 rm_rf(&dep_info, config, &mut progress)?;
6263d726 204 }
a8997cbc
EH
205 }
206 // TODO: what to do about build_script_build?
effc7204
RW
207 let dir = escape_glob_path(layout.incremental())?;
208 let incremental = Path::new(&dir).join(format!("{}-*", crate_name));
9ab86fc8 209 rm_rf_glob(&incremental, config, &mut progress)?;
a8997cbc 210 }
530f2dd4 211 }
3f7b09cc
AC
212 }
213 }
214
3f110840 215 Ok(())
0025dbde 216}
325c5f2d 217
effc7204
RW
218fn escape_glob_path(pattern: &Path) -> CargoResult<String> {
219 let pattern = pattern
220 .to_str()
221 .ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?;
222 Ok(glob::Pattern::escape(pattern))
223}
224
9ab86fc8
T
225fn rm_rf_glob(
226 pattern: &Path,
227 config: &Config,
60cfe7ef 228 progress: &mut dyn CleaningProgressBar,
9ab86fc8 229) -> CargoResult<()> {
a8997cbc
EH
230 // TODO: Display utf8 warning to user? Or switch to globset?
231 let pattern = pattern
232 .to_str()
233 .ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?;
234 for path in glob::glob(pattern)? {
9ab86fc8 235 rm_rf(&path?, config, progress)?;
a8997cbc
EH
236 }
237 Ok(())
238}
239
60cfe7ef 240fn rm_rf(path: &Path, config: &Config, progress: &mut dyn CleaningProgressBar) -> CargoResult<()> {
9ab86fc8
T
241 if fs::symlink_metadata(path).is_err() {
242 return Ok(());
243 }
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")?;
4d46ffbe
T
257 }
258 }
9ab86fc8 259
4d46ffbe
T
260 Ok(())
261}
262
9ab86fc8
T
263fn 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
269trait CleaningProgressBar {
270 fn display_now(&mut self) -> CargoResult<()>;
271 fn on_clean(&mut self) -> CargoResult<()>;
272}
273
274struct CleaningFolderBar<'cfg> {
275 bar: Progress<'cfg>,
276 max: usize,
277 cur: usize,
278}
279
280impl<'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
294impl<'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
305struct 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
313impl<'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
343impl<'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(())
325c5f2d 354 }
c933673e 355}