Skip to content

Commit adf601d

Browse files
committed
Auto merge of #10072 - russweas:master, r=alexcrichton
Implement escaping to allow clean -p to delete all files when directory contains glob characters Closes #10068. Runs `glob::Pattern::escape` on path input to `rm_rf_glob()`. This fixes a bug in which `cargo clean -p` fails to delete a number of files from `target/` if the path to target contains glob characters.
2 parents d2d4b06 + effc720 commit adf601d

File tree

3 files changed

+65
-9
lines changed

3 files changed

+65
-9
lines changed

crates/cargo-test-support/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,11 @@ pub fn project() -> ProjectBuilder {
446446
ProjectBuilder::new(paths::root().join("foo"))
447447
}
448448

449+
// Generates a project layout in given directory
450+
pub fn project_in(dir: &str) -> ProjectBuilder {
451+
ProjectBuilder::new(paths::root().join(dir).join("foo"))
452+
}
453+
449454
// Generates a project layout inside our fake home dir
450455
pub fn project_in_home(name: &str) -> ProjectBuilder {
451456
ProjectBuilder::new(paths::home().join(name))

src/cargo/ops/cargo_clean.rs

+20-7
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,16 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
138138

139139
// Clean fingerprints.
140140
for (_, layout) in &layouts_with_host {
141-
rm_rf_glob(&layout.fingerprint().join(&pkg_dir), config)?;
141+
let dir = escape_glob_path(layout.fingerprint())?;
142+
rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config)?;
142143
}
143144

144145
for target in pkg.targets() {
145146
if target.is_custom_build() {
146147
// Get both the build_script_build and the output directory.
147148
for (_, layout) in &layouts_with_host {
148-
rm_rf_glob(&layout.build().join(&pkg_dir), config)?;
149+
let dir = escape_glob_path(layout.build())?;
150+
rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config)?;
149151
}
150152
continue;
151153
}
@@ -173,18 +175,21 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
173175
// Some files include a hash in the filename, some don't.
174176
let hashed_name = file_type.output_filename(target, Some("*"));
175177
let unhashed_name = file_type.output_filename(target, None);
176-
rm_rf_glob(&dir.join(&hashed_name), config)?;
178+
let dir_glob = escape_glob_path(dir)?;
179+
let dir_glob = Path::new(&dir_glob);
180+
181+
rm_rf_glob(&dir_glob.join(&hashed_name), config)?;
177182
rm_rf(&dir.join(&unhashed_name), config)?;
178183
// Remove dep-info file generated by rustc. It is not tracked in
179184
// file_types. It does not have a prefix.
180-
let hashed_dep_info = dir.join(format!("{}-*.d", crate_name));
185+
let hashed_dep_info = dir_glob.join(format!("{}-*.d", crate_name));
181186
rm_rf_glob(&hashed_dep_info, config)?;
182187
let unhashed_dep_info = dir.join(format!("{}.d", crate_name));
183188
rm_rf(&unhashed_dep_info, config)?;
184189
// Remove split-debuginfo files generated by rustc.
185-
let split_debuginfo_obj = dir.join(format!("{}.*.o", crate_name));
190+
let split_debuginfo_obj = dir_glob.join(format!("{}.*.o", crate_name));
186191
rm_rf_glob(&split_debuginfo_obj, config)?;
187-
let split_debuginfo_dwo = dir.join(format!("{}.*.dwo", crate_name));
192+
let split_debuginfo_dwo = dir_glob.join(format!("{}.*.dwo", crate_name));
188193
rm_rf_glob(&split_debuginfo_dwo, config)?;
189194

190195
// Remove the uplifted copy.
@@ -197,7 +202,8 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
197202
}
198203
}
199204
// TODO: what to do about build_script_build?
200-
let incremental = layout.incremental().join(format!("{}-*", crate_name));
205+
let dir = escape_glob_path(layout.incremental())?;
206+
let incremental = Path::new(&dir).join(format!("{}-*", crate_name));
201207
rm_rf_glob(&incremental, config)?;
202208
}
203209
}
@@ -207,6 +213,13 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
207213
Ok(())
208214
}
209215

216+
fn escape_glob_path(pattern: &Path) -> CargoResult<String> {
217+
let pattern = pattern
218+
.to_str()
219+
.ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?;
220+
Ok(glob::Pattern::escape(pattern))
221+
}
222+
210223
fn rm_rf_glob(pattern: &Path, config: &Config) -> CargoResult<()> {
211224
// TODO: Display utf8 warning to user? Or switch to globset?
212225
let pattern = pattern

tests/testsuite/clean.rs

+40-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
33
use cargo_test_support::paths::is_symlink;
44
use cargo_test_support::registry::Package;
5-
use cargo_test_support::{basic_bin_manifest, basic_manifest, git, main_file, project, rustc_host};
5+
use cargo_test_support::{
6+
basic_bin_manifest, basic_manifest, git, main_file, project, project_in, rustc_host,
7+
};
8+
use glob::GlobError;
69
use std::env;
7-
use std::path::Path;
10+
use std::path::{Path, PathBuf};
811

912
#[cargo_test]
1013
fn cargo_clean_simple() {
@@ -86,6 +89,41 @@ fn clean_multiple_packages() {
8689
assert!(!d2_path.is_file());
8790
}
8891

92+
#[cargo_test]
93+
fn clean_multiple_packages_in_glob_char_path() {
94+
let p = project_in("[d1]")
95+
.file("Cargo.toml", &basic_bin_manifest("foo"))
96+
.file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
97+
.build();
98+
let foo_path = &p.build_dir().join("debug").join("deps");
99+
100+
// Assert that build artifacts are produced
101+
p.cargo("build").run();
102+
assert_ne!(get_build_artifacts(foo_path).len(), 0);
103+
104+
// Assert that build artifacts are destroyed
105+
p.cargo("clean -p foo").run();
106+
assert_eq!(get_build_artifacts(foo_path).len(), 0);
107+
}
108+
109+
fn get_build_artifacts(path: &PathBuf) -> Vec<Result<PathBuf, GlobError>> {
110+
let pattern = path.to_str().expect("expected utf-8 path");
111+
let pattern = glob::Pattern::escape(pattern);
112+
113+
#[cfg(not(target_env = "msvc"))]
114+
const FILE: &str = "foo-*";
115+
116+
#[cfg(target_env = "msvc")]
117+
const FILE: &str = "foo.pdb";
118+
119+
let path = PathBuf::from(pattern).join(FILE);
120+
let path = path.to_str().expect("expected utf-8 path");
121+
glob::glob(path)
122+
.expect("expected glob to run")
123+
.into_iter()
124+
.collect::<Vec<Result<PathBuf, GlobError>>>()
125+
}
126+
89127
#[cargo_test]
90128
fn clean_release() {
91129
let p = project()

0 commit comments

Comments
 (0)