Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate incr-add-rust-src-component to rmake #134656

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/build_helper/src/fs/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Misc filesystem related helpers for use by bootstrap and tools.
use std::fs::Metadata;
use std::path::Path;
use std::{fs, io};

#[cfg(test)]
mod tests;

/// Helper to ignore [`std::io::ErrorKind::NotFound`], but still propagate other
/// [`std::io::ErrorKind`]s.
pub fn ignore_not_found<Op>(mut op: Op) -> io::Result<()>
where
Op: FnMut() -> io::Result<()>,
{
match op() {
Ok(()) => Ok(()),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(e),
}
}

/// A wrapper around [`std::fs::remove_dir_all`] that can also be used on *non-directory entries*,
/// including files and symbolic links.
///
/// - This will produce an error if the target path is not found.
/// - Like [`std::fs::remove_dir_all`], this helper does not traverse symbolic links, will remove
/// symbolic link itself.
/// - This helper is **not** robust against races on the underlying filesystem, behavior is
/// unspecified if this helper is called concurrently.
/// - This helper is not robust against TOCTOU problems.
///
/// FIXME: this implementation is insufficiently robust to replace bootstrap's clean `rm_rf`
/// implementation:
///
/// - This implementation currently does not perform retries.
#[track_caller]
pub fn recursive_remove<P: AsRef<Path>>(path: P) -> io::Result<()> {
let path = path.as_ref();
let metadata = fs::symlink_metadata(path)?;
#[cfg(windows)]
let is_dir_like = |meta: &fs::Metadata| {
use std::os::windows::fs::FileTypeExt;
meta.is_dir() || meta.file_type().is_symlink_dir()
};
#[cfg(not(windows))]
let is_dir_like = fs::Metadata::is_dir;

if is_dir_like(&metadata) {
fs::remove_dir_all(path)
} else {
try_remove_op_set_perms(fs::remove_file, path, metadata)
}
}

fn try_remove_op_set_perms<'p, Op>(mut op: Op, path: &'p Path, metadata: Metadata) -> io::Result<()>
where
Op: FnMut(&'p Path) -> io::Result<()>,
{
match op(path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
let mut perms = metadata.permissions();
perms.set_readonly(false);
fs::set_permissions(path, perms)?;
op(path)
}
Err(e) => Err(e),
}
}
214 changes: 214 additions & 0 deletions src/build_helper/src/fs/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
#![deny(unused_must_use)]

use std::{env, fs, io};

use super::recursive_remove;

mod recursive_remove_tests {
use super::*;

// Basic cases

#[test]
fn nonexistent_path() {
let tmpdir = env::temp_dir();
let path = tmpdir.join("__INTERNAL_BOOTSTRAP_nonexistent_path");
assert!(fs::symlink_metadata(&path).is_err_and(|e| e.kind() == io::ErrorKind::NotFound));
assert!(recursive_remove(&path).is_err_and(|e| e.kind() == io::ErrorKind::NotFound));
}

#[test]
fn file() {
let tmpdir = env::temp_dir();
let path = tmpdir.join("__INTERNAL_BOOTSTRAP_file");
fs::write(&path, b"").unwrap();
assert!(fs::symlink_metadata(&path).is_ok());
assert!(recursive_remove(&path).is_ok());
assert!(fs::symlink_metadata(&path).is_err_and(|e| e.kind() == io::ErrorKind::NotFound));
}

mod dir_tests {
use super::*;

#[test]
fn dir_empty() {
let tmpdir = env::temp_dir();
let path = tmpdir.join("__INTERNAL_BOOTSTRAP_dir_tests_dir_empty");
fs::create_dir_all(&path).unwrap();
assert!(fs::symlink_metadata(&path).is_ok());
assert!(recursive_remove(&path).is_ok());
assert!(
fs::symlink_metadata(&path).is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);
}

#[test]
fn dir_recursive() {
let tmpdir = env::temp_dir();
let path = tmpdir.join("__INTERNAL_BOOTSTRAP_dir_tests_dir_recursive");
fs::create_dir_all(&path).unwrap();
assert!(fs::symlink_metadata(&path).is_ok());

let file_a = path.join("a.txt");
fs::write(&file_a, b"").unwrap();
assert!(fs::symlink_metadata(&file_a).is_ok());

let dir_b = path.join("b");
fs::create_dir_all(&dir_b).unwrap();
assert!(fs::symlink_metadata(&dir_b).is_ok());

let file_c = dir_b.join("c.rs");
fs::write(&file_c, b"").unwrap();
assert!(fs::symlink_metadata(&file_c).is_ok());

assert!(recursive_remove(&path).is_ok());

assert!(
fs::symlink_metadata(&file_a).is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);
assert!(
fs::symlink_metadata(&dir_b).is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);
assert!(
fs::symlink_metadata(&file_c).is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);
}
}

/// Check that [`recursive_remove`] does not traverse symlinks and only removes symlinks
/// themselves.
///
/// Symlink-to-file versus symlink-to-dir is a distinction that's important on Windows, but not
/// on Unix.
mod symlink_tests {
use super::*;

#[cfg(unix)]
#[test]
fn unix_symlink() {
let tmpdir = env::temp_dir();
let path = tmpdir.join("__INTERNAL_BOOTSTRAP_symlink_tests_unix_symlink");
let symlink_path =
tmpdir.join("__INTERNAL_BOOTSTRAP__symlink_tests_unix_symlink_symlink");
fs::write(&path, b"").unwrap();

assert!(fs::symlink_metadata(&path).is_ok());
assert!(
fs::symlink_metadata(&symlink_path)
.is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);

std::os::unix::fs::symlink(&path, &symlink_path).unwrap();

assert!(recursive_remove(&symlink_path).is_ok());

// Check that the symlink got removed...
assert!(
fs::symlink_metadata(&symlink_path)
.is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);
// ... but pointed-to file still exists.
assert!(fs::symlink_metadata(&path).is_ok());

fs::remove_file(&path).unwrap();
}

#[cfg(windows)]
#[test]
fn windows_symlink_to_file() {
let tmpdir = env::temp_dir();
let path = tmpdir.join("__INTERNAL_BOOTSTRAP_symlink_tests_windows_symlink_to_file");
let symlink_path = tmpdir
.join("__INTERNAL_BOOTSTRAP_SYMLINK_symlink_tests_windows_symlink_to_file_symlink");
fs::write(&path, b"").unwrap();

assert!(fs::symlink_metadata(&path).is_ok());
assert!(
fs::symlink_metadata(&symlink_path)
.is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);

std::os::windows::fs::symlink_file(&path, &symlink_path).unwrap();

assert!(recursive_remove(&symlink_path).is_ok());

// Check that the symlink-to-file got removed...
assert!(
fs::symlink_metadata(&symlink_path)
.is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);
// ... but pointed-to file still exists.
assert!(fs::symlink_metadata(&path).is_ok());

fs::remove_file(&path).unwrap();
}

#[cfg(windows)]
#[test]
fn windows_symlink_to_dir() {
let tmpdir = env::temp_dir();
let path = tmpdir.join("__INTERNAL_BOOTSTRAP_symlink_tests_windows_symlink_to_dir");
let symlink_path =
tmpdir.join("__INTERNAL_BOOTSTRAP_symlink_tests_windows_symlink_to_dir_symlink");
fs::create_dir_all(&path).unwrap();

assert!(fs::symlink_metadata(&path).is_ok());
assert!(
fs::symlink_metadata(&symlink_path)
.is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);

std::os::windows::fs::symlink_dir(&path, &symlink_path).unwrap();

assert!(recursive_remove(&symlink_path).is_ok());

// Check that the symlink-to-dir got removed...
assert!(
fs::symlink_metadata(&symlink_path)
.is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);
// ... but pointed-to dir still exists.
assert!(fs::symlink_metadata(&path).is_ok());

fs::remove_dir_all(&path).unwrap();
}
}

/// Read-only file and directories only need special handling on Windows.
#[cfg(windows)]
mod readonly_tests {
use super::*;

#[test]
fn overrides_readonly() {
let tmpdir = env::temp_dir();
let path = tmpdir.join("__INTERNAL_BOOTSTRAP_readonly_tests_overrides_readonly");

// In case of a previous failed test:
if let Ok(mut perms) = fs::symlink_metadata(&path).map(|m| m.permissions()) {
perms.set_readonly(false);
fs::set_permissions(&path, perms).unwrap();
fs::remove_file(&path).unwrap();
}

fs::write(&path, b"").unwrap();

let mut perms = fs::symlink_metadata(&path).unwrap().permissions();
perms.set_readonly(true);
fs::set_permissions(&path, perms).unwrap();

// Check that file exists but is read-only, and that normal `std::fs::remove_file` fails
// to delete the file.
assert!(fs::symlink_metadata(&path).is_ok_and(|m| m.permissions().readonly()));
assert!(
fs::remove_file(&path).is_err_and(|e| e.kind() == io::ErrorKind::PermissionDenied)
);

assert!(recursive_remove(&path).is_ok());

assert!(
fs::symlink_metadata(&path).is_err_and(|e| e.kind() == io::ErrorKind::NotFound)
);
}
}
}
1 change: 1 addition & 0 deletions src/build_helper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod ci;
pub mod drop_bomb;
pub mod fs;
pub mod git;
pub mod metrics;
pub mod stage0_parser;
Expand Down
2 changes: 1 addition & 1 deletion src/tools/compiletest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ indexmap = "2.0.0"
miropt-test-tools = { path = "../miropt-test-tools" }
build_helper = { path = "../../build_helper" }
tracing = "0.1"
tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] }
tracing-subscriber = { version = "0.3.3", default-features = false, features = ["ansi", "env-filter", "fmt", "parking_lot", "smallvec"] }
regex = "1.0"
semver = { version = "1.0.23", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
Expand Down
23 changes: 0 additions & 23 deletions src/tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2809,29 +2809,6 @@ impl<'test> TestCx<'test> {
println!("init_incremental_test: incremental_dir={}", incremental_dir.display());
}
}

fn aggressive_rm_rf(&self, path: &Path) -> io::Result<()> {
for e in path.read_dir()? {
let entry = e?;
let path = entry.path();
if entry.file_type()?.is_dir() {
self.aggressive_rm_rf(&path)?;
} else {
// Remove readonly files as well on windows (by default we can't)
fs::remove_file(&path).or_else(|e| {
if cfg!(windows) && e.kind() == io::ErrorKind::PermissionDenied {
let mut meta = entry.metadata()?.permissions();
meta.set_readonly(false);
fs::set_permissions(&path, meta)?;
fs::remove_file(&path)
} else {
Err(e)
}
})?;
}
}
fs::remove_dir(path)
}
}

struct ProcArgs {
Expand Down
12 changes: 6 additions & 6 deletions src/tools/compiletest/src/runtest/run_make.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::path::Path;
use std::process::{Command, Output, Stdio};
use std::{env, fs};

use build_helper::fs::{ignore_not_found, recursive_remove};

use super::{ProcRes, TestCx, disable_error_reporting};
use crate::util::{copy_dir_all, dylib_env_var};

Expand All @@ -27,9 +29,8 @@ impl TestCx<'_> {
// are hopefully going away, it seems safer to leave this perilous code
// as-is until it can all be deleted.
let tmpdir = cwd.join(self.output_base_name());
if tmpdir.exists() {
self.aggressive_rm_rf(&tmpdir).unwrap();
}
ignore_not_found(|| recursive_remove(&tmpdir)).unwrap();

fs::create_dir_all(&tmpdir).unwrap();

let host = &self.config.host;
Expand Down Expand Up @@ -218,9 +219,8 @@ impl TestCx<'_> {
//
// This setup intentionally diverges from legacy Makefile run-make tests.
let base_dir = self.output_base_dir();
if base_dir.exists() {
self.aggressive_rm_rf(&base_dir).unwrap();
}
ignore_not_found(|| recursive_remove(&base_dir)).unwrap();

let rmake_out_dir = base_dir.join("rmake_out");
fs::create_dir_all(&rmake_out_dir).unwrap();

Expand Down
Loading
Loading