Skip to content

Commit de7c4e4

Browse files
committed
Auto merge of #62037 - Mark-Simulacrum:tidy-fast, r=eddyb
Speed up tidy master: Time (mean ± σ): 3.478 s ± 0.033 s [User: 3.298 s, System: 0.178 s] Range (min … max): 3.425 s … 3.525 s 10 runs This PR: Time (mean ± σ): 1.098 s ± 0.006 s [User: 783.7 ms, System: 310.2 ms] Range (min … max): 1.092 s … 1.113 s 10 runs Alleviates #59884. For the most part each commit stands on its own. Timings are on warm filesystem cache. r? @eddyb
2 parents 2cd5ed4 + 777951c commit de7c4e4

File tree

13 files changed

+173
-146
lines changed

13 files changed

+173
-146
lines changed

Cargo.lock

+2
Original file line numberDiff line numberDiff line change
@@ -3801,9 +3801,11 @@ dependencies = [
38013801
name = "tidy"
38023802
version = "0.1.0"
38033803
dependencies = [
3804+
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
38043805
"regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
38053806
"serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)",
38063807
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
3808+
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
38073809
]
38083810

38093811
[[package]]

src/bootstrap/test.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -709,8 +709,8 @@ impl Step for Tidy {
709709
if !builder.config.vendor {
710710
cmd.arg("--no-vendor");
711711
}
712-
if !builder.config.verbose_tests {
713-
cmd.arg("--quiet");
712+
if builder.is_verbose() {
713+
cmd.arg("--verbose");
714714
}
715715

716716
let _folder = builder.fold_output(|| "tidy");

src/tools/tidy/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ edition = "2018"
88
regex = "1"
99
serde = { version = "1.0.8", features = ["derive"] }
1010
serde_json = "1.0.2"
11+
lazy_static = "1"
12+
walkdir = "2"

src/tools/tidy/src/bins.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,17 @@ pub fn check(path: &Path, bad: &mut bool) {
2525
}
2626
}
2727

28-
super::walk(path,
28+
super::walk_no_read(path,
2929
&mut |path| super::filter_dirs(path) || path.ends_with("src/etc"),
30-
&mut |file| {
30+
&mut |entry| {
31+
let file = entry.path();
3132
let filename = file.file_name().unwrap().to_string_lossy();
3233
let extensions = [".py", ".sh"];
3334
if extensions.iter().any(|e| filename.ends_with(e)) {
3435
return;
3536
}
3637

37-
let metadata = t!(fs::symlink_metadata(&file), &file);
38+
let metadata = t!(entry.metadata(), file);
3839
if metadata.mode() & 0o111 != 0 {
3940
let rel_path = file.strip_prefix(path).unwrap();
4041
let git_friendly_path = rel_path.to_str().unwrap().replace("\\", "/");

src/tools/tidy/src/errors.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,19 @@
44
//! statistics about the error codes.
55
66
use std::collections::HashMap;
7-
use std::fs::File;
8-
use std::io::prelude::*;
97
use std::path::Path;
108

119
pub fn check(path: &Path, bad: &mut bool) {
12-
let mut contents = String::new();
1310
let mut map: HashMap<_, Vec<_>> = HashMap::new();
1411
super::walk(path,
1512
&mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
16-
&mut |file| {
13+
&mut |entry, contents| {
14+
let file = entry.path();
1715
let filename = file.file_name().unwrap().to_string_lossy();
1816
if filename != "error_codes.rs" {
1917
return
2018
}
2119

22-
contents.truncate(0);
23-
t!(t!(File::open(file)).read_to_string(&mut contents));
24-
2520
// In the `register_long_diagnostics!` macro, entries look like this:
2621
//
2722
// ```

src/tools/tidy/src/features.rs

+75-57
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@
1111
1212
use std::collections::HashMap;
1313
use std::fmt;
14-
use std::fs::{self, File};
15-
use std::io::prelude::*;
14+
use std::fs;
1615
use std::path::Path;
1716

18-
use regex::{Regex, escape};
17+
use regex::Regex;
1918

2019
mod version;
2120
use version::Version;
@@ -51,20 +50,48 @@ pub struct Feature {
5150

5251
pub type Features = HashMap<String, Feature>;
5352

54-
pub fn check(path: &Path, bad: &mut bool, quiet: bool) {
53+
pub struct CollectedFeatures {
54+
pub lib: Features,
55+
pub lang: Features,
56+
}
57+
58+
// Currently only used for unstable book generation
59+
pub fn collect_lib_features(base_src_path: &Path) -> Features {
60+
let mut lib_features = Features::new();
61+
62+
// This library feature is defined in the `compiler_builtins` crate, which
63+
// has been moved out-of-tree. Now it can no longer be auto-discovered by
64+
// `tidy`, because we need to filter out its (submodule) directory. Manually
65+
// add it to the set of known library features so we can still generate docs.
66+
lib_features.insert("compiler_builtins_lib".to_owned(), Feature {
67+
level: Status::Unstable,
68+
since: None,
69+
has_gate_test: false,
70+
tracking_issue: None,
71+
});
72+
73+
map_lib_features(base_src_path,
74+
&mut |res, _, _| {
75+
if let Ok((name, feature)) = res {
76+
lib_features.insert(name.to_owned(), feature);
77+
}
78+
});
79+
lib_features
80+
}
81+
82+
pub fn check(path: &Path, bad: &mut bool, verbose: bool) -> CollectedFeatures {
5583
let mut features = collect_lang_features(path, bad);
5684
assert!(!features.is_empty());
5785

5886
let lib_features = get_and_check_lib_features(path, bad, &features);
5987
assert!(!lib_features.is_empty());
6088

61-
let mut contents = String::new();
62-
6389
super::walk_many(&[&path.join("test/ui"),
6490
&path.join("test/ui-fulldeps"),
6591
&path.join("test/compile-fail")],
6692
&mut |path| super::filter_dirs(path),
67-
&mut |file| {
93+
&mut |entry, contents| {
94+
let file = entry.path();
6895
let filename = file.file_name().unwrap().to_string_lossy();
6996
if !filename.ends_with(".rs") || filename == "features.rs" ||
7097
filename == "diagnostic_list.rs" {
@@ -74,9 +101,6 @@ pub fn check(path: &Path, bad: &mut bool, quiet: bool) {
74101
let filen_underscore = filename.replace('-',"_").replace(".rs","");
75102
let filename_is_gate_test = test_filen_gate(&filen_underscore, &mut features);
76103

77-
contents.truncate(0);
78-
t!(t!(File::open(&file), &file).read_to_string(&mut contents));
79-
80104
for (i, line) in contents.lines().enumerate() {
81105
let mut err = |msg: &str| {
82106
tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
@@ -130,21 +154,23 @@ pub fn check(path: &Path, bad: &mut bool, quiet: bool) {
130154
}
131155

132156
if *bad {
133-
return;
134-
}
135-
if quiet {
136-
println!("* {} features", features.len());
137-
return;
157+
return CollectedFeatures { lib: lib_features, lang: features };
138158
}
139159

140-
let mut lines = Vec::new();
141-
lines.extend(format_features(&features, "lang"));
142-
lines.extend(format_features(&lib_features, "lib"));
160+
if verbose {
161+
let mut lines = Vec::new();
162+
lines.extend(format_features(&features, "lang"));
163+
lines.extend(format_features(&lib_features, "lib"));
143164

144-
lines.sort();
145-
for line in lines {
146-
println!("* {}", line);
165+
lines.sort();
166+
for line in lines {
167+
println!("* {}", line);
168+
}
169+
} else {
170+
println!("* {} features", features.len());
147171
}
172+
173+
CollectedFeatures { lib: lib_features, lang: features }
148174
}
149175

150176
fn format_features<'a>(features: &'a Features, family: &'a str) -> impl Iterator<Item = String> + 'a {
@@ -159,8 +185,19 @@ fn format_features<'a>(features: &'a Features, family: &'a str) -> impl Iterator
159185
}
160186

161187
fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
162-
let r = Regex::new(&format!(r#"{}\s*=\s*"([^"]*)""#, escape(attr)))
163-
.expect("malformed regex for find_attr_val");
188+
lazy_static::lazy_static! {
189+
static ref ISSUE: Regex = Regex::new(r#"issue\s*=\s*"([^"]*)""#).unwrap();
190+
static ref FEATURE: Regex = Regex::new(r#"feature\s*=\s*"([^"]*)""#).unwrap();
191+
static ref SINCE: Regex = Regex::new(r#"since\s*=\s*"([^"]*)""#).unwrap();
192+
}
193+
194+
let r = match attr {
195+
"issue" => &*ISSUE,
196+
"feature" => &*FEATURE,
197+
"since" => &*SINCE,
198+
_ => unimplemented!("{} not handled", attr),
199+
};
200+
164201
r.captures(line)
165202
.and_then(|c| c.get(1))
166203
.map(|m| m.as_str())
@@ -175,9 +212,11 @@ fn test_find_attr_val() {
175212
}
176213

177214
fn test_filen_gate(filen_underscore: &str, features: &mut Features) -> bool {
178-
if filen_underscore.starts_with("feature_gate") {
215+
let prefix = "feature_gate_";
216+
if filen_underscore.starts_with(prefix) {
179217
for (n, f) in features.iter_mut() {
180-
if filen_underscore == format!("feature_gate_{}", n) {
218+
// Equivalent to filen_underscore == format!("feature_gate_{}", n)
219+
if &filen_underscore[prefix.len()..] == n {
181220
f.has_gate_test = true;
182221
return true;
183222
}
@@ -295,32 +334,6 @@ pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features {
295334
.collect()
296335
}
297336

298-
pub fn collect_lib_features(base_src_path: &Path) -> Features {
299-
let mut lib_features = Features::new();
300-
301-
// This library feature is defined in the `compiler_builtins` crate, which
302-
// has been moved out-of-tree. Now it can no longer be auto-discovered by
303-
// `tidy`, because we need to filter out its (submodule) directory. Manually
304-
// add it to the set of known library features so we can still generate docs.
305-
lib_features.insert("compiler_builtins_lib".to_owned(), Feature {
306-
level: Status::Unstable,
307-
since: None,
308-
has_gate_test: false,
309-
tracking_issue: None,
310-
});
311-
312-
map_lib_features(base_src_path,
313-
&mut |res, _, _| {
314-
if let Ok((name, feature)) = res {
315-
if lib_features.contains_key(name) {
316-
return;
317-
}
318-
lib_features.insert(name.to_owned(), feature);
319-
}
320-
});
321-
lib_features
322-
}
323-
324337
fn get_and_check_lib_features(base_src_path: &Path,
325338
bad: &mut bool,
326339
lang_features: &Features) -> Features {
@@ -355,20 +368,25 @@ fn get_and_check_lib_features(base_src_path: &Path,
355368

356369
fn map_lib_features(base_src_path: &Path,
357370
mf: &mut dyn FnMut(Result<(&str, Feature), &str>, &Path, usize)) {
358-
let mut contents = String::new();
359371
super::walk(base_src_path,
360372
&mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
361-
&mut |file| {
373+
&mut |entry, contents| {
374+
let file = entry.path();
362375
let filename = file.file_name().unwrap().to_string_lossy();
363376
if !filename.ends_with(".rs") || filename == "features.rs" ||
364377
filename == "diagnostic_list.rs" {
365378
return;
366379
}
367380

368-
contents.truncate(0);
369-
t!(t!(File::open(&file), &file).read_to_string(&mut contents));
381+
// This is an early exit -- all the attributes we're concerned with must contain this:
382+
// * rustc_const_unstable(
383+
// * unstable(
384+
// * stable(
385+
if !contents.contains("stable(") {
386+
return;
387+
}
370388

371-
let mut becoming_feature: Option<(String, Feature)> = None;
389+
let mut becoming_feature: Option<(&str, Feature)> = None;
372390
for (i, line) in contents.lines().enumerate() {
373391
macro_rules! err {
374392
($msg:expr) => {{
@@ -447,7 +465,7 @@ fn map_lib_features(base_src_path: &Path,
447465
if line.contains(']') {
448466
mf(Ok((feature_name, feature)), file, i + 1);
449467
} else {
450-
becoming_feature = Some((feature_name.to_owned(), feature));
468+
becoming_feature = Some((feature_name, feature));
451469
}
452470
}
453471
});

src/tools/tidy/src/lib.rs

+26-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
//! This library contains the tidy lints and exposes it
44
//! to be used by tools.
55
6-
use std::fs;
6+
use walkdir::{DirEntry, WalkDir};
7+
use std::fs::File;
8+
use std::io::Read;
79

810
use std::path::Path;
911

@@ -65,25 +67,35 @@ fn filter_dirs(path: &Path) -> bool {
6567
skip.iter().any(|p| path.ends_with(p))
6668
}
6769

68-
fn walk_many(paths: &[&Path], skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&Path)) {
70+
71+
fn walk_many(
72+
paths: &[&Path], skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&DirEntry, &str)
73+
) {
6974
for path in paths {
7075
walk(path, skip, f);
7176
}
7277
}
7378

74-
fn walk(path: &Path, skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&Path)) {
75-
if let Ok(dir) = fs::read_dir(path) {
76-
for entry in dir {
77-
let entry = t!(entry);
78-
let kind = t!(entry.file_type());
79-
let path = entry.path();
80-
if kind.is_dir() {
81-
if !skip(&path) {
82-
walk(&path, skip, f);
83-
}
84-
} else {
85-
f(&path);
79+
fn walk(path: &Path, skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&DirEntry, &str)) {
80+
let mut contents = String::new();
81+
walk_no_read(path, skip, &mut |entry| {
82+
contents.clear();
83+
if t!(File::open(entry.path()), entry.path()).read_to_string(&mut contents).is_err() {
84+
contents.clear();
85+
}
86+
f(&entry, &contents);
87+
});
88+
}
89+
90+
fn walk_no_read(path: &Path, skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&DirEntry)) {
91+
let walker = WalkDir::new(path).into_iter()
92+
.filter_entry(|e| !skip(e.path()));
93+
for entry in walker {
94+
if let Ok(entry) = entry {
95+
if entry.file_type().is_dir() {
96+
continue;
8697
}
98+
f(&entry);
8799
}
88100
}
89101
}

src/tools/tidy/src/libcoretest.rs

+9-16
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,22 @@
44
//! item. All tests must be written externally in `libcore/tests`.
55
66
use std::path::Path;
7-
use std::fs::read_to_string;
87

98
pub fn check(path: &Path, bad: &mut bool) {
109
let libcore_path = path.join("libcore");
1110
super::walk(
1211
&libcore_path,
1312
&mut |subpath| t!(subpath.strip_prefix(&libcore_path)).starts_with("tests"),
14-
&mut |subpath| {
13+
&mut |entry, contents| {
14+
let subpath = entry.path();
1515
if let Some("rs") = subpath.extension().and_then(|e| e.to_str()) {
16-
match read_to_string(subpath) {
17-
Ok(contents) => {
18-
if contents.contains("#[test]") {
19-
tidy_error!(
20-
bad,
21-
"{} contains #[test]; libcore tests must be placed inside \
22-
`src/libcore/tests/`",
23-
subpath.display()
24-
);
25-
}
26-
}
27-
Err(err) => {
28-
panic!("failed to read file {:?}: {}", subpath, err);
29-
}
16+
if contents.contains("#[test]") {
17+
tidy_error!(
18+
bad,
19+
"{} contains #[test]; libcore tests must be placed inside \
20+
`src/libcore/tests/`",
21+
subpath.display()
22+
);
3023
}
3124
}
3225
},

0 commit comments

Comments
 (0)