Skip to content

Commit 2b48142

Browse files
authored
clean: Add --workspace support (#16263)
Co-authored-by: dino <dinojoaocosta@gmail.com> ### What does this PR try to resolve? Fixes #14720. ### How to test and review this PR? We tested this change manually on Ruff repository. We've also added a test[^1]. [^1]: During testing we've found that `Workspace::members` returns path dependencies of workspace members as workspace members; this means that `cargo clean --workspace` will clean up artifacts of path dependencies as well. We are not sure if that's a good behaviour and would love to get some more guidance on it.
2 parents b0896a0 + ecc89a8 commit 2b48142

File tree

8 files changed

+193
-21
lines changed

8 files changed

+193
-21
lines changed

src/bin/cargo/commands/clean.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use cargo::ops::CleanContext;
77
use cargo::ops::{self, CleanOptions};
88
use cargo::util::print_available_packages;
99
use clap_complete::ArgValueCandidates;
10+
use indexmap::IndexSet;
1011
use std::time::Duration;
1112

1213
pub fn cli() -> Command {
@@ -18,6 +19,10 @@ pub fn cli() -> Command {
1819
"Package to clean artifacts for",
1920
ArgValueCandidates::new(get_pkg_name_candidates),
2021
)
22+
.arg(
23+
flag("workspace", "Clean artifacts of the workspace members")
24+
.help_heading(heading::PACKAGE_SELECTION),
25+
)
2126
.arg_release("Whether or not to clean release artifacts")
2227
.arg_profile("Clean artifacts of the specified profile")
2328
.arg_target_triple("Target triple to clean output for")
@@ -146,10 +151,14 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
146151
if args.is_present_with_zero_values("package") {
147152
print_available_packages(&ws)?;
148153
}
154+
let mut spec = IndexSet::from_iter(values(args, "package"));
149155

156+
if args.flag("workspace") {
157+
spec.extend(ws.members().map(|package| package.name().to_string()))
158+
};
150159
let opts = CleanOptions {
151160
gctx,
152-
spec: values(args, "package"),
161+
spec,
153162
targets: args.targets()?,
154163
requested_profile: args.get_profile_name("dev", ProfileChecking::Custom)?,
155164
profile_specified: args.contains_id("profile") || args.flag("release"),

src/cargo/ops/cargo_clean.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::util::interning::InternedString;
99
use crate::util::{GlobalContext, Progress, ProgressStyle};
1010
use anyhow::bail;
1111
use cargo_util::paths;
12+
use indexmap::IndexSet;
1213
use std::collections::{HashMap, HashSet};
1314
use std::fs;
1415
use std::path::{Path, PathBuf};
@@ -17,7 +18,7 @@ use std::rc::Rc;
1718
pub struct CleanOptions<'gctx> {
1819
pub gctx: &'gctx GlobalContext,
1920
/// A list of packages to clean. If empty, everything is cleaned.
20-
pub spec: Vec<String>,
21+
pub spec: IndexSet<String>,
2122
/// The target arch triple to clean, or None for the host arch
2223
pub targets: Vec<String>,
2324
/// Whether to clean the release directory
@@ -108,7 +109,7 @@ fn clean_specs(
108109
ws: &Workspace<'_>,
109110
profiles: &Profiles,
110111
targets: &[String],
111-
spec: &[String],
112+
spec: &IndexSet<String>,
112113
dry_run: bool,
113114
) -> CargoResult<()> {
114115
// Clean specific packages.

src/doc/man/cargo-clean.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,16 @@ When no packages are selected, all packages and all dependencies in the
2626
workspace are cleaned.
2727

2828
{{#options}}
29+
2930
{{#option "`-p` _spec_..." "`--package` _spec_..." }}
3031
Clean only the specified packages. This flag may be specified
3132
multiple times. See {{man "cargo-pkgid" 1}} for the SPEC format.
3233
{{/option}}
34+
35+
{{#option "`--workspace`" }}
36+
Clean artifacts of the workspace members.
37+
{{/option}}
38+
3339
{{/options}}
3440

3541
### Clean Options

src/doc/man/generated_txt/cargo-clean.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ OPTIONS
2121
Clean only the specified packages. This flag may be specified
2222
multiple times. See cargo-pkgid(1) for the SPEC format.
2323

24+
--workspace
25+
Clean artifacts of the workspace members.
26+
2427
Clean Options
2528
--dry-run
2629
Displays a summary of what would be deleted without deleting

src/doc/src/commands/cargo-clean.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,19 @@ When no packages are selected, all packages and all dependencies in the
2222
workspace are cleaned.
2323

2424
<dl>
25+
2526
<dt class="option-term" id="option-cargo-clean--p"><a class="option-anchor" href="#option-cargo-clean--p"><code>-p</code> <em>spec</em>…</a></dt>
2627
<dt class="option-term" id="option-cargo-clean---package"><a class="option-anchor" href="#option-cargo-clean---package"><code>--package</code> <em>spec</em>…</a></dt>
2728
<dd class="option-desc"><p>Clean only the specified packages. This flag may be specified
2829
multiple times. See <a href="cargo-pkgid.html">cargo-pkgid(1)</a> for the SPEC format.</p>
2930
</dd>
3031

32+
33+
<dt class="option-term" id="option-cargo-clean---workspace"><a class="option-anchor" href="#option-cargo-clean---workspace"><code>--workspace</code></a></dt>
34+
<dd class="option-desc"><p>Clean artifacts of the workspace members.</p>
35+
</dd>
36+
37+
3138
</dl>
3239

3340
### Clean Options

src/etc/man/cargo-clean.1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ workspace are cleaned.
2323
Clean only the specified packages. This flag may be specified
2424
multiple times. See \fBcargo\-pkgid\fR(1) for the SPEC format.
2525
.RE
26+
.sp
27+
\fB\-\-workspace\fR
28+
.RS 4
29+
Clean artifacts of the workspace members.
30+
.RE
2631
.SS "Clean Options"
2732
.sp
2833
\fB\-\-dry\-run\fR

tests/testsuite/cargo_clean/help/stdout.term.svg

Lines changed: 20 additions & 18 deletions
Loading

tests/testsuite/clean.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,145 @@ fn clean_p_only_cleans_specified_package() {
199199
);
200200
}
201201

202+
#[cargo_test]
203+
fn clean_workspace_does_not_touch_non_workspace_packages() {
204+
Package::new("external_dependency", "0.1.0").publish();
205+
let foo_manifest = r#"
206+
[package]
207+
name = "foo"
208+
version = "0.1.0"
209+
edition = "2015"
210+
211+
[dependencies]
212+
external_dependency = "0.1.0"
213+
"#;
214+
let p = project()
215+
.file(
216+
"Cargo.toml",
217+
r#"
218+
[workspace]
219+
members = [
220+
"foo",
221+
"foo_core",
222+
"foo-base",
223+
]
224+
"#,
225+
)
226+
.file("foo/Cargo.toml", foo_manifest)
227+
.file("foo/src/lib.rs", "//! foo")
228+
.file("foo_core/Cargo.toml", &basic_manifest("foo_core", "0.1.0"))
229+
.file("foo_core/src/lib.rs", "//! foo_core")
230+
.file("foo-base/Cargo.toml", &basic_manifest("foo-base", "0.1.0"))
231+
.file("foo-base/src/lib.rs", "//! foo-base")
232+
.build();
233+
234+
let fingerprint_path = &p.build_dir().join("debug").join(".fingerprint");
235+
236+
p.cargo("check -p foo -p foo_core -p foo-base").run();
237+
238+
let mut fingerprint_names = get_fingerprints_without_hashes(fingerprint_path);
239+
240+
// Artifacts present for all after building
241+
assert!(fingerprint_names.iter().any(|e| e == "foo"));
242+
assert!(fingerprint_names.iter().any(|e| e == "foo_core"));
243+
assert!(fingerprint_names.iter().any(|e| e == "foo-base"));
244+
245+
let num_external_dependency_artifacts = fingerprint_names
246+
.iter()
247+
.filter(|&e| e == "external_dependency")
248+
.count();
249+
assert_ne!(num_external_dependency_artifacts, 0);
250+
251+
p.cargo("clean --workspace").run();
252+
253+
fingerprint_names = get_fingerprints_without_hashes(fingerprint_path);
254+
255+
// Cleaning workspace members leaves artifacts for the external dependency
256+
assert!(
257+
!fingerprint_names
258+
.iter()
259+
.any(|e| e == "foo" || e == "foo_core" || e == "foo-base")
260+
);
261+
assert_eq!(
262+
fingerprint_names
263+
.iter()
264+
.filter(|&e| e == "external_dependency")
265+
.count(),
266+
num_external_dependency_artifacts,
267+
);
268+
}
269+
270+
#[cargo_test]
271+
fn clean_workspace_with_extra_package_specifiers() {
272+
Package::new("external_dependency_1", "0.1.0").publish();
273+
Package::new("external_dependency_2", "0.1.0").publish();
274+
let foo_manifest = r#"
275+
[package]
276+
name = "foo"
277+
version = "0.1.0"
278+
edition = "2015"
279+
280+
[dependencies]
281+
external_dependency_1 = "0.1.0"
282+
external_dependency_2 = "0.1.0"
283+
"#;
284+
let p = project()
285+
.file(
286+
"Cargo.toml",
287+
r#"
288+
[workspace]
289+
members = [
290+
"foo",
291+
"foo_core",
292+
"foo-base",
293+
]
294+
"#,
295+
)
296+
.file("foo/Cargo.toml", foo_manifest)
297+
.file("foo/src/lib.rs", "//! foo")
298+
.file("foo_core/Cargo.toml", &basic_manifest("foo_core", "0.1.0"))
299+
.file("foo_core/src/lib.rs", "//! foo_core")
300+
.file("foo-base/Cargo.toml", &basic_manifest("foo-base", "0.1.0"))
301+
.file("foo-base/src/lib.rs", "//! foo-base")
302+
.build();
303+
304+
let fingerprint_path = &p.build_dir().join("debug").join(".fingerprint");
305+
306+
p.cargo("check -p foo -p foo_core -p foo-base").run();
307+
308+
let mut fingerprint_names = get_fingerprints_without_hashes(fingerprint_path);
309+
310+
// Artifacts present for all after building
311+
assert!(fingerprint_names.iter().any(|e| e == "foo"));
312+
assert!(fingerprint_names.iter().any(|e| e == "foo_core"));
313+
assert!(fingerprint_names.iter().any(|e| e == "foo-base"));
314+
315+
let num_external_dependency_2_artifacts = fingerprint_names
316+
.iter()
317+
.filter(|&e| e == "external_dependency_2")
318+
.count();
319+
assert_ne!(num_external_dependency_2_artifacts, 0);
320+
321+
p.cargo("clean --workspace -p external_dependency_1").run();
322+
323+
fingerprint_names = get_fingerprints_without_hashes(fingerprint_path);
324+
325+
// Cleaning workspace members and external_dependency_1 leaves artifacts for the external_dependency_2
326+
assert!(
327+
!fingerprint_names.iter().any(|e| e == "foo"
328+
|| e == "foo_core"
329+
|| e == "foo-base"
330+
|| e == "external_dependency_1")
331+
);
332+
assert_eq!(
333+
fingerprint_names
334+
.iter()
335+
.filter(|&e| e == "external_dependency_2")
336+
.count(),
337+
num_external_dependency_2_artifacts,
338+
);
339+
}
340+
202341
fn get_fingerprints_without_hashes(fingerprint_path: &Path) -> Vec<String> {
203342
std::fs::read_dir(fingerprint_path)
204343
.expect("Build dir should be readable")

0 commit comments

Comments
 (0)