Skip to content

Commit 9e71316

Browse files
committed
Auto merge of #10621 - LovecraftianHorror:more-precise-artifact-cleanup, r=weihanglo
Only remove fingerprints and build script artifacts of the requested package Fixes #10069 This is my first PR to cargo. Let me know if you want me to add tests, or if there are any other changes you would like to see :) This PR changes the globs used to remove fingerprints and build artifacts when running `cargo clean -p <pkid>`. The glob used was `<package_name>-*` which would match artifacts for packages that are prefixed by `<package_name>-` (e.g. `cargo clean -p sqlx` would also remove artifacts for `sqlx-{core,macros,etc.}`). This problem is not seen with other artifacts since those use the crate name instead of package name which normalize hyphens to underscores. The changes follow the naive approach mentioned in #10069 where some basic string manipulation is done to strip off the last hyphen, hash, and potential extension to get the original package name which can be used to determine if the artifact is actually for the desired package. This means that this **does not** handle trying to resolve the package to determine the artifacts, so it still ignores the url and version that may be passed in the pkgid
2 parents cdc22d4 + 2ebcc75 commit 9e71316

File tree

2 files changed

+136
-12
lines changed

2 files changed

+136
-12
lines changed

src/cargo/ops/cargo_clean.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,25 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
141141
// Clean fingerprints.
142142
for (_, layout) in &layouts_with_host {
143143
let dir = escape_glob_path(layout.fingerprint())?;
144-
rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config, &mut progress)?;
144+
rm_rf_package_glob_containing_hash(
145+
&pkg.name(),
146+
&Path::new(&dir).join(&pkg_dir),
147+
config,
148+
&mut progress,
149+
)?;
145150
}
146151

147152
for target in pkg.targets() {
148153
if target.is_custom_build() {
149154
// Get both the build_script_build and the output directory.
150155
for (_, layout) in &layouts_with_host {
151156
let dir = escape_glob_path(layout.build())?;
152-
rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config, &mut progress)?;
157+
rm_rf_package_glob_containing_hash(
158+
&pkg.name(),
159+
&Path::new(&dir).join(&pkg_dir),
160+
config,
161+
&mut progress,
162+
)?;
153163
}
154164
continue;
155165
}
@@ -222,6 +232,40 @@ fn escape_glob_path(pattern: &Path) -> CargoResult<String> {
222232
Ok(glob::Pattern::escape(pattern))
223233
}
224234

235+
/// Glob remove artifacts for the provided `package`
236+
///
237+
/// Make sure the artifact is for `package` and not another crate that is prefixed by
238+
/// `package` by getting the original name stripped of the trailing hash and possible
239+
/// extension
240+
fn rm_rf_package_glob_containing_hash(
241+
package: &str,
242+
pattern: &Path,
243+
config: &Config,
244+
progress: &mut dyn CleaningProgressBar,
245+
) -> CargoResult<()> {
246+
// TODO: Display utf8 warning to user? Or switch to globset?
247+
let pattern = pattern
248+
.to_str()
249+
.ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?;
250+
for path in glob::glob(pattern)? {
251+
let path = path?;
252+
253+
let pkg_name = path
254+
.file_name()
255+
.and_then(std::ffi::OsStr::to_str)
256+
.and_then(|artifact| artifact.rsplit_once('-'))
257+
.ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?
258+
.0;
259+
260+
if pkg_name != package {
261+
continue;
262+
}
263+
264+
rm_rf(&path, config, progress)?;
265+
}
266+
Ok(())
267+
}
268+
225269
fn rm_rf_glob(
226270
pattern: &Path,
227271
config: &Config,

tests/testsuite/clean.rs

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,33 +96,113 @@ fn clean_multiple_packages_in_glob_char_path() {
9696
.build();
9797
let foo_path = &p.build_dir().join("debug").join("deps");
9898

99+
#[cfg(not(target_env = "msvc"))]
100+
let file_glob = "foo-*";
101+
102+
#[cfg(target_env = "msvc")]
103+
let file_glob = "foo.pdb";
104+
99105
// Assert that build artifacts are produced
100106
p.cargo("build").run();
101-
assert_ne!(get_build_artifacts(foo_path).len(), 0);
107+
assert_ne!(get_build_artifacts(foo_path, file_glob).len(), 0);
102108

103109
// Assert that build artifacts are destroyed
104110
p.cargo("clean -p foo").run();
105-
assert_eq!(get_build_artifacts(foo_path).len(), 0);
111+
assert_eq!(get_build_artifacts(foo_path, file_glob).len(), 0);
106112
}
107113

108-
fn get_build_artifacts(path: &PathBuf) -> Vec<Result<PathBuf, GlobError>> {
114+
fn get_build_artifacts(path: &PathBuf, file_glob: &str) -> Vec<Result<PathBuf, GlobError>> {
109115
let pattern = path.to_str().expect("expected utf-8 path");
110116
let pattern = glob::Pattern::escape(pattern);
111117

112-
#[cfg(not(target_env = "msvc"))]
113-
const FILE: &str = "foo-*";
114-
115-
#[cfg(target_env = "msvc")]
116-
const FILE: &str = "foo.pdb";
117-
118-
let path = PathBuf::from(pattern).join(FILE);
118+
let path = PathBuf::from(pattern).join(file_glob);
119119
let path = path.to_str().expect("expected utf-8 path");
120120
glob::glob(path)
121121
.expect("expected glob to run")
122122
.into_iter()
123123
.collect::<Vec<Result<PathBuf, GlobError>>>()
124124
}
125125

126+
#[cargo_test]
127+
fn clean_p_only_cleans_specified_package() {
128+
let p = project()
129+
.file(
130+
"Cargo.toml",
131+
r#"
132+
[workspace]
133+
members = [
134+
"foo",
135+
"foo_core",
136+
"foo-base",
137+
]
138+
"#,
139+
)
140+
.file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
141+
.file("foo/src/lib.rs", "//! foo")
142+
.file("foo_core/Cargo.toml", &basic_manifest("foo_core", "0.1.0"))
143+
.file("foo_core/src/lib.rs", "//! foo_core")
144+
.file("foo-base/Cargo.toml", &basic_manifest("foo-base", "0.1.0"))
145+
.file("foo-base/src/lib.rs", "//! foo-base")
146+
.build();
147+
148+
let fingerprint_path = &p.build_dir().join("debug").join(".fingerprint");
149+
150+
p.cargo("build -p foo -p foo_core -p foo-base").run();
151+
152+
let mut fingerprint_names = get_fingerprints_without_hashes(fingerprint_path);
153+
154+
// Artifacts present for all after building
155+
assert!(fingerprint_names.iter().any(|e| e == "foo"));
156+
let num_foo_core_artifacts = fingerprint_names
157+
.iter()
158+
.filter(|&e| e == "foo_core")
159+
.count();
160+
assert_ne!(num_foo_core_artifacts, 0);
161+
let num_foo_base_artifacts = fingerprint_names
162+
.iter()
163+
.filter(|&e| e == "foo-base")
164+
.count();
165+
assert_ne!(num_foo_base_artifacts, 0);
166+
167+
p.cargo("clean -p foo").run();
168+
169+
fingerprint_names = get_fingerprints_without_hashes(fingerprint_path);
170+
171+
// Cleaning `foo` leaves artifacts for the others
172+
assert!(!fingerprint_names.iter().any(|e| e == "foo"));
173+
assert_eq!(
174+
fingerprint_names
175+
.iter()
176+
.filter(|&e| e == "foo_core")
177+
.count(),
178+
num_foo_core_artifacts,
179+
);
180+
assert_eq!(
181+
fingerprint_names
182+
.iter()
183+
.filter(|&e| e == "foo-base")
184+
.count(),
185+
num_foo_core_artifacts,
186+
);
187+
}
188+
189+
fn get_fingerprints_without_hashes(fingerprint_path: &Path) -> Vec<String> {
190+
std::fs::read_dir(fingerprint_path)
191+
.expect("Build dir should be readable")
192+
.filter_map(|entry| entry.ok())
193+
.map(|entry| {
194+
let name = entry.file_name();
195+
let name = name
196+
.into_string()
197+
.expect("fingerprint name should be UTF-8");
198+
name.rsplit_once('-')
199+
.expect("Name should contain at least one hyphen")
200+
.0
201+
.to_owned()
202+
})
203+
.collect()
204+
}
205+
126206
#[cargo_test]
127207
fn clean_release() {
128208
let p = project()

0 commit comments

Comments
 (0)