Skip to content

Commit e73eaeb

Browse files
committed
Auto merge of rust-lang#129187 - jieyouxu:squeaky-clean-windows-symlinks, r=<try>
bootstrap: fix clean's remove_dir_all implementation It turns out bootstrap's `clean.rs`'s hand-rolled `rm_rf` (which probably comes before `std::fs::remove_dir_all` was stable) is very broken on native Windows around both read-only files/directories and especially symbolic links. So instead of rolling our own, just use `std::fs::remove_dir_all`. Because I wasn't super sure about `std::fs::remove_dir_all`'s behavior and to catch `std::fs::remove_dir_all`'s behavioral changes here on forward, I added a collection of tests that checks if our expectation of the behavior of `std::fs::remove_dir_all` and its underlying Unix syscalls and Win32 APIs matches with its actual behavior. This is a blocker for compiletest's own `rm_rf` implementation rust-lang#129155 which happens to be also buggy, which in turn is a blocker for the rmake.rs test port rust-lang#128562 that heavily exercises symlinks (I was reviewing rust-lang#128562 and testing it on native Windows which is how I found out). I also left a FIXME for `detect_src_and_out` due to a failing assertion on native Windows: ``` ---- core::config::tests::detect_src_and_out stdout ---- thread 'core::config::tests::detect_src_and_out' panicked at src\core\config\tests.rs:72:13: assertion `left == right` failed left: "E:\\tmp" right: "C:\\tmp" ``` Fixes rust-lang#112544 (because now we handle Windows symlinks properly). try-job: x86_64-msvc try-job: i686-mingw try-job: test-various try-job: armhf-gnu try-job: aarch64-apple try-job: aarch64-gnu
2 parents 54a50bd + e736cb4 commit e73eaeb

File tree

3 files changed

+422
-78
lines changed

3 files changed

+422
-78
lines changed

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

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

1211
use crate::core::builder::{crate_description, Builder, RunConfig, ShouldRun, Step};
1312
use crate::utils::helpers::t;
1413
use crate::{Build, Compiler, Kind, Mode, Subcommand};
1514

15+
#[cfg(test)]
16+
mod tests;
17+
1618
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1719
pub struct CleanAll {}
1820

@@ -101,11 +103,11 @@ fn clean(build: &Build, all: bool, stage: Option<u32>) {
101103
return;
102104
}
103105

104-
rm_rf("tmp".as_ref());
106+
remove_dir_all_wrapper("tmp");
105107

106108
// Clean the entire build directory
107109
if all {
108-
rm_rf(&build.out);
110+
remove_dir_all_wrapper(&build.out);
109111
return;
110112
}
111113

@@ -136,17 +138,17 @@ fn clean_specific_stage(build: &Build, stage: u32) {
136138
}
137139

138140
let path = t!(entry.path().canonicalize());
139-
rm_rf(&path);
141+
remove_dir_all_wrapper(&path);
140142
}
141143
}
142144
}
143145

144146
fn clean_default(build: &Build) {
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"));
147+
remove_dir_all_wrapper(&build.out.join("tmp"));
148+
remove_dir_all_wrapper(&build.out.join("dist"));
149+
remove_dir_all_wrapper(&build.out.join("bootstrap").join(".last-warned-change-id"));
150+
remove_dir_all_wrapper(&build.out.join("bootstrap-shims-dump"));
151+
remove_dir_all_wrapper(&build.out.join("rustfmt.stamp"));
150152

151153
let mut hosts: Vec<_> = build.hosts.iter().map(|t| build.out.join(t)).collect();
152154
// After cross-compilation, artifacts of the host architecture (which may differ from build.host)
@@ -166,78 +168,16 @@ fn clean_default(build: &Build) {
166168
continue;
167169
}
168170
let path = t!(entry.path().canonicalize());
169-
rm_rf(&path);
171+
remove_dir_all_wrapper(&path);
170172
}
171173
}
172174
}
173175

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-
}
176+
/// Wrapper for [`std::fs::remove_dir_all`] that panics on failure and prints the `path` we failed
177+
/// on.
178+
fn remove_dir_all_wrapper<P: AsRef<Path>>(path: P) {
179+
let path = path.as_ref();
180+
if let Err(e) = fs::remove_dir_all(&path) {
181+
panic!("failed to `remove_dir_all` at `{}`: {e}", path.display());
242182
}
243183
}

0 commit comments

Comments
 (0)