Skip to content

Commit f98135b

Browse files
committed
Auto merge of #82347 - the8472:parallelize-tidy, r=Mark-Simulacrum
Parallelize tidy Split off from #81833 While that PR brings wall time of `x.py test tidy` down to 0m2.847s adding this one on top should bring it down to 0m1.673s. r? `@Mark-Simulacrum` Previous concerns can be found at #81833 (comment) and #81833 (comment)
2 parents 88e7862 + 0513ba4 commit f98135b

File tree

5 files changed

+189
-104
lines changed

5 files changed

+189
-104
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -5308,6 +5308,7 @@ name = "tidy"
53085308
version = "0.1.0"
53095309
dependencies = [
53105310
"cargo_metadata 0.11.1",
5311+
"crossbeam-utils 0.8.0",
53115312
"lazy_static",
53125313
"regex",
53135314
"walkdir",

src/bootstrap/test.rs

+1
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@ impl Step for Tidy {
854854
cmd.arg(&builder.src);
855855
cmd.arg(&builder.initial_cargo);
856856
cmd.arg(&builder.out);
857+
cmd.arg(builder.jobs().to_string());
857858
if builder.is_verbose() {
858859
cmd.arg("--verbose");
859860
}

src/tools/tidy/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ cargo_metadata = "0.11"
1010
regex = "1"
1111
lazy_static = "1"
1212
walkdir = "2"
13+
crossbeam-utils = "0.8.0"
1314

1415
[[bin]]
1516
name = "rust-tidy"

src/tools/tidy/src/bins.rs

+102-67
Original file line numberDiff line numberDiff line change
@@ -5,91 +5,126 @@
55
//! huge amount of bloat to the Git history, so it's good to just ensure we
66
//! don't do that again.
77
8-
use std::path::Path;
8+
pub use os_impl::*;
99

1010
// All files are executable on Windows, so just check on Unix.
1111
#[cfg(windows)]
12-
pub fn check(_path: &Path, _output: &Path, _bad: &mut bool) {}
12+
mod os_impl {
13+
use std::path::Path;
14+
15+
pub fn check_filesystem_support(_sources: &[&Path], _output: &Path) -> bool {
16+
return false;
17+
}
18+
19+
pub fn check(_path: &Path, _bad: &mut bool) {}
20+
}
1321

1422
#[cfg(unix)]
15-
pub fn check(path: &Path, output: &Path, bad: &mut bool) {
23+
mod os_impl {
1624
use std::fs;
1725
use std::os::unix::prelude::*;
26+
use std::path::Path;
1827
use std::process::{Command, Stdio};
1928

29+
enum FilesystemSupport {
30+
Supported,
31+
Unsupported,
32+
ReadOnlyFs,
33+
}
34+
35+
use FilesystemSupport::*;
36+
2037
fn is_executable(path: &Path) -> std::io::Result<bool> {
2138
Ok(path.metadata()?.mode() & 0o111 != 0)
2239
}
2340

24-
// We want to avoid false positives on filesystems that do not support the
25-
// executable bit. This occurs on some versions of Window's linux subsystem,
26-
// for example.
27-
//
28-
// We try to create the temporary file first in the src directory, which is
29-
// the preferred location as it's most likely to be on the same filesystem,
30-
// and then in the output (`build`) directory if that fails. Sometimes we
31-
// see the source directory mounted as read-only which means we can't
32-
// readily create a file there to test.
33-
//
34-
// See #36706 and #74753 for context.
35-
let mut temp_path = path.join("tidy-test-file");
36-
match fs::File::create(&temp_path).or_else(|_| {
37-
temp_path = output.join("tidy-test-file");
38-
fs::File::create(&temp_path)
39-
}) {
40-
Ok(file) => {
41-
let exec = is_executable(&temp_path).unwrap_or(false);
42-
std::mem::drop(file);
43-
std::fs::remove_file(&temp_path).expect("Deleted temp file");
44-
if exec {
45-
// If the file is executable, then we assume that this
46-
// filesystem does not track executability, so skip this check.
47-
return;
48-
}
41+
pub fn check_filesystem_support(sources: &[&Path], output: &Path) -> bool {
42+
// We want to avoid false positives on filesystems that do not support the
43+
// executable bit. This occurs on some versions of Window's linux subsystem,
44+
// for example.
45+
//
46+
// We try to create the temporary file first in the src directory, which is
47+
// the preferred location as it's most likely to be on the same filesystem,
48+
// and then in the output (`build`) directory if that fails. Sometimes we
49+
// see the source directory mounted as read-only which means we can't
50+
// readily create a file there to test.
51+
//
52+
// See #36706 and #74753 for context.
53+
54+
fn check_dir(dir: &Path) -> FilesystemSupport {
55+
let path = dir.join("tidy-test-file");
56+
match fs::File::create(&path) {
57+
Ok(file) => {
58+
let exec = is_executable(&path).unwrap_or(false);
59+
std::mem::drop(file);
60+
std::fs::remove_file(&path).expect("Deleted temp file");
61+
// If the file is executable, then we assume that this
62+
// filesystem does not track executability, so skip this check.
63+
return if exec { Unsupported } else { Supported };
64+
}
65+
Err(e) => {
66+
// If the directory is read-only or we otherwise don't have rights,
67+
// just don't run this check.
68+
//
69+
// 30 is the "Read-only filesystem" code at least in one CI
70+
// environment.
71+
if e.raw_os_error() == Some(30) {
72+
eprintln!("tidy: Skipping binary file check, read-only filesystem");
73+
return ReadOnlyFs;
74+
}
75+
76+
panic!("unable to create temporary file `{:?}`: {:?}", path, e);
77+
}
78+
};
4979
}
50-
Err(e) => {
51-
// If the directory is read-only or we otherwise don't have rights,
52-
// just don't run this check.
53-
//
54-
// 30 is the "Read-only filesystem" code at least in one CI
55-
// environment.
56-
if e.raw_os_error() == Some(30) {
57-
eprintln!("tidy: Skipping binary file check, read-only filesystem");
58-
return;
59-
}
6080

61-
panic!("unable to create temporary file `{:?}`: {:?}", temp_path, e);
81+
for &source_dir in sources {
82+
match check_dir(source_dir) {
83+
Unsupported => return false,
84+
ReadOnlyFs => {
85+
return match check_dir(output) {
86+
Supported => true,
87+
_ => false,
88+
};
89+
}
90+
_ => {}
91+
}
6292
}
93+
94+
return true;
6395
}
6496

65-
super::walk_no_read(
66-
path,
67-
&mut |path| super::filter_dirs(path) || path.ends_with("src/etc"),
68-
&mut |entry| {
69-
let file = entry.path();
70-
let filename = file.file_name().unwrap().to_string_lossy();
71-
let extensions = [".py", ".sh"];
72-
if extensions.iter().any(|e| filename.ends_with(e)) {
73-
return;
74-
}
97+
#[cfg(unix)]
98+
pub fn check(path: &Path, bad: &mut bool) {
99+
crate::walk_no_read(
100+
path,
101+
&mut |path| crate::filter_dirs(path) || path.ends_with("src/etc"),
102+
&mut |entry| {
103+
let file = entry.path();
104+
let filename = file.file_name().unwrap().to_string_lossy();
105+
let extensions = [".py", ".sh"];
106+
if extensions.iter().any(|e| filename.ends_with(e)) {
107+
return;
108+
}
75109

76-
if t!(is_executable(&file), file) {
77-
let rel_path = file.strip_prefix(path).unwrap();
78-
let git_friendly_path = rel_path.to_str().unwrap().replace("\\", "/");
79-
let output = Command::new("git")
80-
.arg("ls-files")
81-
.arg(&git_friendly_path)
82-
.current_dir(path)
83-
.stderr(Stdio::null())
84-
.output()
85-
.unwrap_or_else(|e| {
86-
panic!("could not run git ls-files: {}", e);
87-
});
88-
let path_bytes = rel_path.as_os_str().as_bytes();
89-
if output.status.success() && output.stdout.starts_with(path_bytes) {
90-
tidy_error!(bad, "binary checked into source: {}", file.display());
110+
if t!(is_executable(&file), file) {
111+
let rel_path = file.strip_prefix(path).unwrap();
112+
let git_friendly_path = rel_path.to_str().unwrap().replace("\\", "/");
113+
let output = Command::new("git")
114+
.arg("ls-files")
115+
.arg(&git_friendly_path)
116+
.current_dir(path)
117+
.stderr(Stdio::null())
118+
.output()
119+
.unwrap_or_else(|e| {
120+
panic!("could not run git ls-files: {}", e);
121+
});
122+
let path_bytes = rel_path.as_os_str().as_bytes();
123+
if output.status.success() && output.stdout.starts_with(path_bytes) {
124+
tidy_error!(bad, "binary checked into source: {}", file.display());
125+
}
91126
}
92-
}
93-
},
94-
)
127+
},
128+
)
129+
}
95130
}

src/tools/tidy/src/main.rs

+84-37
Original file line numberDiff line numberDiff line change
@@ -6,61 +6,108 @@
66
77
use tidy::*;
88

9+
use crossbeam_utils::thread::{scope, ScopedJoinHandle};
10+
use std::collections::VecDeque;
911
use std::env;
12+
use std::num::NonZeroUsize;
1013
use std::path::PathBuf;
1114
use std::process;
15+
use std::str::FromStr;
16+
use std::sync::atomic::{AtomicBool, Ordering};
1217

1318
fn main() {
1419
let root_path: PathBuf = env::args_os().nth(1).expect("need path to root of repo").into();
1520
let cargo: PathBuf = env::args_os().nth(2).expect("need path to cargo").into();
1621
let output_directory: PathBuf =
1722
env::args_os().nth(3).expect("need path to output directory").into();
23+
let concurrency: NonZeroUsize =
24+
FromStr::from_str(&env::args().nth(4).expect("need concurrency"))
25+
.expect("concurrency must be a number");
1826

1927
let src_path = root_path.join("src");
2028
let library_path = root_path.join("library");
2129
let compiler_path = root_path.join("compiler");
2230

2331
let args: Vec<String> = env::args().skip(1).collect();
2432

25-
let mut bad = false;
2633
let verbose = args.iter().any(|s| *s == "--verbose");
2734

28-
// Checks over tests.
29-
debug_artifacts::check(&src_path, &mut bad);
30-
ui_tests::check(&src_path, &mut bad);
31-
32-
// Checks that only make sense for the compiler.
33-
errors::check(&compiler_path, &mut bad);
34-
error_codes_check::check(&src_path, &mut bad);
35-
36-
// Checks that only make sense for the std libs.
37-
pal::check(&library_path, &mut bad);
38-
39-
// Checks that need to be done for both the compiler and std libraries.
40-
unit_tests::check(&src_path, &mut bad);
41-
unit_tests::check(&compiler_path, &mut bad);
42-
unit_tests::check(&library_path, &mut bad);
43-
44-
bins::check(&src_path, &output_directory, &mut bad);
45-
bins::check(&compiler_path, &output_directory, &mut bad);
46-
bins::check(&library_path, &output_directory, &mut bad);
47-
48-
style::check(&src_path, &mut bad);
49-
style::check(&compiler_path, &mut bad);
50-
style::check(&library_path, &mut bad);
51-
52-
edition::check(&src_path, &mut bad);
53-
edition::check(&compiler_path, &mut bad);
54-
edition::check(&library_path, &mut bad);
55-
56-
let collected = features::check(&src_path, &compiler_path, &library_path, &mut bad, verbose);
57-
unstable_book::check(&src_path, collected, &mut bad);
58-
59-
// Checks that are done on the cargo workspace.
60-
deps::check(&root_path, &cargo, &mut bad);
61-
extdeps::check(&root_path, &mut bad);
62-
63-
if bad {
35+
let bad = std::sync::Arc::new(AtomicBool::new(false));
36+
37+
scope(|s| {
38+
let mut handles: VecDeque<ScopedJoinHandle<'_, ()>> =
39+
VecDeque::with_capacity(concurrency.get());
40+
41+
macro_rules! check {
42+
($p:ident $(, $args:expr)* ) => {
43+
while handles.len() >= concurrency.get() {
44+
handles.pop_front().unwrap().join().unwrap();
45+
}
46+
47+
let handle = s.spawn(|_| {
48+
let mut flag = false;
49+
$p::check($($args),* , &mut flag);
50+
if (flag) {
51+
bad.store(true, Ordering::Relaxed);
52+
}
53+
});
54+
handles.push_back(handle);
55+
}
56+
}
57+
58+
// Checks that are done on the cargo workspace.
59+
check!(deps, &root_path, &cargo);
60+
check!(extdeps, &root_path);
61+
62+
// Checks over tests.
63+
check!(debug_artifacts, &src_path);
64+
check!(ui_tests, &src_path);
65+
66+
// Checks that only make sense for the compiler.
67+
check!(errors, &compiler_path);
68+
check!(error_codes_check, &src_path);
69+
70+
// Checks that only make sense for the std libs.
71+
check!(pal, &library_path);
72+
73+
// Checks that need to be done for both the compiler and std libraries.
74+
check!(unit_tests, &src_path);
75+
check!(unit_tests, &compiler_path);
76+
check!(unit_tests, &library_path);
77+
78+
if bins::check_filesystem_support(
79+
&[&src_path, &compiler_path, &library_path],
80+
&output_directory,
81+
) {
82+
check!(bins, &src_path);
83+
check!(bins, &compiler_path);
84+
check!(bins, &library_path);
85+
}
86+
87+
check!(style, &src_path);
88+
check!(style, &compiler_path);
89+
check!(style, &library_path);
90+
91+
check!(edition, &src_path);
92+
check!(edition, &compiler_path);
93+
check!(edition, &library_path);
94+
95+
let collected = {
96+
while handles.len() >= concurrency.get() {
97+
handles.pop_front().unwrap().join().unwrap();
98+
}
99+
let mut flag = false;
100+
let r = features::check(&src_path, &compiler_path, &library_path, &mut flag, verbose);
101+
if flag {
102+
bad.store(true, Ordering::Relaxed);
103+
}
104+
r
105+
};
106+
check!(unstable_book, &src_path, collected);
107+
})
108+
.unwrap();
109+
110+
if bad.load(Ordering::Relaxed) {
64111
eprintln!("some tidy checks failed");
65112
process::exit(1);
66113
}

0 commit comments

Comments
 (0)