Skip to content

Commit 1b6f4c7

Browse files
committed
Auto merge of #129413 - jieyouxu:revert-remove-dir-all, r=compiler-errors
Revert #129187 and #129302 The two PRs naively switched to `std::fs::remove_dir_all`, but failed to gracefully handle the failure case where the top-level directory entry does not exist, causing #129187 (comment) `./x clean` to fail locally when `tmp` does not exist. I plan to reland the two PRs with fixed top-level dir entry handling and more testing, but let's quickly revert to unblock people. Reverts #129187. Reverts #129302. r? bootstrap
2 parents 5ad98b4 + 3957f39 commit 1b6f4c7

File tree

2 files changed

+103
-17
lines changed

2 files changed

+103
-17
lines changed

src/bootstrap/src/core/build_steps/clean.rs

+78-15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//! directory unless the `--all` flag is present.
77
88
use std::fs;
9+
use std::io::{self, ErrorKind};
910
use std::path::Path;
1011

1112
use crate::core::builder::{crate_description, Builder, RunConfig, ShouldRun, Step};
@@ -100,11 +101,11 @@ fn clean(build: &Build, all: bool, stage: Option<u32>) {
100101
return;
101102
}
102103

103-
remove_dir_recursive("tmp");
104+
rm_rf("tmp".as_ref());
104105

105106
// Clean the entire build directory
106107
if all {
107-
remove_dir_recursive(&build.out);
108+
rm_rf(&build.out);
108109
return;
109110
}
110111

@@ -135,17 +136,17 @@ fn clean_specific_stage(build: &Build, stage: u32) {
135136
}
136137

137138
let path = t!(entry.path().canonicalize());
138-
remove_dir_recursive(&path);
139+
rm_rf(&path);
139140
}
140141
}
141142
}
142143

143144
fn clean_default(build: &Build) {
144-
remove_dir_recursive(build.out.join("tmp"));
145-
remove_dir_recursive(build.out.join("dist"));
146-
remove_dir_recursive(build.out.join("bootstrap").join(".last-warned-change-id"));
147-
remove_dir_recursive(build.out.join("bootstrap-shims-dump"));
148-
remove_dir_recursive(build.out.join("rustfmt.stamp"));
145+
rm_rf(&build.out.join("tmp"));
146+
rm_rf(&build.out.join("dist"));
147+
rm_rf(&build.out.join("bootstrap").join(".last-warned-change-id"));
148+
rm_rf(&build.out.join("bootstrap-shims-dump"));
149+
rm_rf(&build.out.join("rustfmt.stamp"));
149150

150151
let mut hosts: Vec<_> = build.hosts.iter().map(|t| build.out.join(t)).collect();
151152
// After cross-compilation, artifacts of the host architecture (which may differ from build.host)
@@ -165,16 +166,78 @@ fn clean_default(build: &Build) {
165166
continue;
166167
}
167168
let path = t!(entry.path().canonicalize());
168-
remove_dir_recursive(&path);
169+
rm_rf(&path);
169170
}
170171
}
171172
}
172173

173-
/// Wrapper for [`std::fs::remove_dir_all`] that panics on failure and prints the `path` we failed
174-
/// on.
175-
fn remove_dir_recursive<P: AsRef<Path>>(path: P) {
176-
let path = path.as_ref();
177-
if let Err(e) = fs::remove_dir_all(path) {
178-
panic!("failed to `remove_dir_all` at `{}`: {e}", path.display());
174+
fn rm_rf(path: &Path) {
175+
match path.symlink_metadata() {
176+
Err(e) => {
177+
if e.kind() == ErrorKind::NotFound {
178+
return;
179+
}
180+
panic!("failed to get metadata for file {}: {}", path.display(), e);
181+
}
182+
Ok(metadata) => {
183+
if metadata.file_type().is_file() || metadata.file_type().is_symlink() {
184+
do_op(path, "remove file", |p| match fs::remove_file(p) {
185+
#[cfg(windows)]
186+
Err(e)
187+
if e.kind() == std::io::ErrorKind::PermissionDenied
188+
&& p.file_name().and_then(std::ffi::OsStr::to_str)
189+
== Some("bootstrap.exe") =>
190+
{
191+
eprintln!("WARNING: failed to delete '{}'.", p.display());
192+
Ok(())
193+
}
194+
r => r,
195+
});
196+
197+
return;
198+
}
199+
200+
for file in t!(fs::read_dir(path)) {
201+
rm_rf(&t!(file).path());
202+
}
203+
204+
do_op(path, "remove dir", |p| match fs::remove_dir(p) {
205+
// Check for dir not empty on Windows
206+
// FIXME: Once `ErrorKind::DirectoryNotEmpty` is stabilized,
207+
// match on `e.kind()` instead.
208+
#[cfg(windows)]
209+
Err(e) if e.raw_os_error() == Some(145) => Ok(()),
210+
r => r,
211+
});
212+
}
213+
};
214+
}
215+
216+
fn do_op<F>(path: &Path, desc: &str, mut f: F)
217+
where
218+
F: FnMut(&Path) -> io::Result<()>,
219+
{
220+
match f(path) {
221+
Ok(()) => {}
222+
// On windows we can't remove a readonly file, and git will often clone files as readonly.
223+
// As a result, we have some special logic to remove readonly files on windows.
224+
// This is also the reason that we can't use things like fs::remove_dir_all().
225+
#[cfg(windows)]
226+
Err(ref e) if e.kind() == ErrorKind::PermissionDenied => {
227+
let m = t!(path.symlink_metadata());
228+
let mut p = m.permissions();
229+
p.set_readonly(false);
230+
t!(fs::set_permissions(path, p));
231+
f(path).unwrap_or_else(|e| {
232+
// Delete symlinked directories on Windows
233+
if m.file_type().is_symlink() && path.is_dir() && fs::remove_dir(path).is_ok() {
234+
return;
235+
}
236+
panic!("failed to {} {}: {}", desc, path.display(), e);
237+
});
238+
}
239+
Err(e) => {
240+
panic!("failed to {} {}: {}", desc, path.display(), e);
241+
}
179242
}
180243
}

src/tools/compiletest/src/runtest.rs

+25-2
Original file line numberDiff line numberDiff line change
@@ -3265,7 +3265,7 @@ impl<'test> TestCx<'test> {
32653265

32663266
let tmpdir = cwd.join(self.output_base_name());
32673267
if tmpdir.exists() {
3268-
fs::remove_dir_all(&tmpdir).unwrap();
3268+
self.aggressive_rm_rf(&tmpdir).unwrap();
32693269
}
32703270
create_dir_all(&tmpdir).unwrap();
32713271

@@ -3404,6 +3404,29 @@ impl<'test> TestCx<'test> {
34043404
}
34053405
}
34063406

3407+
fn aggressive_rm_rf(&self, path: &Path) -> io::Result<()> {
3408+
for e in path.read_dir()? {
3409+
let entry = e?;
3410+
let path = entry.path();
3411+
if entry.file_type()?.is_dir() {
3412+
self.aggressive_rm_rf(&path)?;
3413+
} else {
3414+
// Remove readonly files as well on windows (by default we can't)
3415+
fs::remove_file(&path).or_else(|e| {
3416+
if cfg!(windows) && e.kind() == io::ErrorKind::PermissionDenied {
3417+
let mut meta = entry.metadata()?.permissions();
3418+
meta.set_readonly(false);
3419+
fs::set_permissions(&path, meta)?;
3420+
fs::remove_file(&path)
3421+
} else {
3422+
Err(e)
3423+
}
3424+
})?;
3425+
}
3426+
}
3427+
fs::remove_dir(path)
3428+
}
3429+
34073430
fn run_rmake_v2_test(&self) {
34083431
// For `run-make` V2, we need to perform 2 steps to build and run a `run-make` V2 recipe
34093432
// (`rmake.rs`) to run the actual tests. The support library is already built as a tool rust
@@ -3452,7 +3475,7 @@ impl<'test> TestCx<'test> {
34523475
// This setup intentionally diverges from legacy Makefile run-make tests.
34533476
let base_dir = self.output_base_name();
34543477
if base_dir.exists() {
3455-
fs::remove_dir_all(&base_dir).unwrap();
3478+
self.aggressive_rm_rf(&base_dir).unwrap();
34563479
}
34573480
let rmake_out_dir = base_dir.join("rmake_out");
34583481
create_dir_all(&rmake_out_dir).unwrap();

0 commit comments

Comments
 (0)