Skip to content

Commit 5ae1e17

Browse files
committed
Auto merge of #6813 - matthiaskrgr:lintcheck_refactor, r=flip1995
lintcheck, do some refactoring and add more sources refactor: add a Config object don't run in parallel mode by default (it didn't make sense because cargo would lock the shared target dir anyway) show full paths (from repo root) to the source files in clippy warnings so we can just copy the path from the logfile fix more bugs add more crates by dtolnay and embark to the sources toml changelog: lintcheck: refactor some code and add more sources
2 parents ac2f041 + 25f9098 commit 5ae1e17

File tree

3 files changed

+3626
-3471
lines changed

3 files changed

+3626
-3471
lines changed

clippy_dev/lintcheck_crates.toml

+13-1
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,22 @@ bitflags = {name = "bitflags", versions = ['1.2.1']}
1414
libc = {name = "libc", versions = ['0.2.81']}
1515
log = {name = "log", versions = ['0.4.11']}
1616
proc-macro2 = {name = "proc-macro2", versions = ['1.0.24']}
17-
puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"}
1817
quote = {name = "quote", versions = ['1.0.7']}
1918
rand = {name = "rand", versions = ['0.7.3']}
2019
rand_core = {name = "rand_core", versions = ['0.6.0']}
2120
regex = {name = "regex", versions = ['1.3.2']}
2221
syn = {name = "syn", versions = ['1.0.54']}
2322
unicode-xid = {name = "unicode-xid", versions = ['0.2.1']}
23+
# some more of dtolnays crates
24+
anyhow = {name = "anyhow", versions = ['1.0.38']}
25+
async-trait = {name = "async-trait", versions = ['0.1.42']}
26+
cxx = {name = "cxx", versions = ['1.0.32']}
27+
ryu = {name = "ryu", version = ['1.0.5']}
28+
serde_yaml = {name = "serde_yaml", versions = ['0.8.17']}
29+
thiserror = {name = "thiserror", versions = ['1.0.24']}
30+
# some embark crates, there are other interesting crates but
31+
# unfortunately adding them increases lintcheck runtime drastically
32+
cfg-expr = {name = "cfg-expr", versions = ['0.7.1']}
33+
puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"}
34+
rpmalloc = {name = "rpmalloc", versions = ['0.2.0']}
35+
tame-oidc = {name = "tame-oidc", versions = ['0.1.0']}

clippy_dev/src/lintcheck.rs

+129-89
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ use rayon::prelude::*;
1919
use serde::{Deserialize, Serialize};
2020
use serde_json::Value;
2121

22+
const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver";
23+
const CARGO_CLIPPY_PATH: &str = "target/debug/cargo-clippy";
24+
25+
const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads";
26+
const LINTCHECK_SOURCES: &str = "target/lintcheck/sources";
27+
2228
/// List of sources to check, loaded from a .toml file
2329
#[derive(Debug, Serialize, Deserialize)]
2430
struct SourceList {
@@ -86,7 +92,7 @@ impl std::fmt::Display for ClippyWarning {
8692
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8793
writeln!(
8894
f,
89-
r#"{}-{}/{}:{}:{} {} "{}""#,
95+
r#"target/lintcheck/sources/{}-{}/{}:{}:{} {} "{}""#,
9096
&self.crate_name, &self.crate_version, &self.file, &self.line, &self.column, &self.linttype, &self.message
9197
)
9298
}
@@ -99,8 +105,8 @@ impl CrateSource {
99105
fn download_and_extract(&self) -> Crate {
100106
match self {
101107
CrateSource::CratesIo { name, version, options } => {
102-
let extract_dir = PathBuf::from("target/lintcheck/crates");
103-
let krate_download_dir = PathBuf::from("target/lintcheck/downloads");
108+
let extract_dir = PathBuf::from(LINTCHECK_SOURCES);
109+
let krate_download_dir = PathBuf::from(LINTCHECK_DOWNLOADS);
104110

105111
// url to download the crate from crates.io
106112
let url = format!("https://crates.io/api/v1/crates/{}/{}/download", name, version);
@@ -140,7 +146,7 @@ impl CrateSource {
140146
options,
141147
} => {
142148
let repo_path = {
143-
let mut repo_path = PathBuf::from("target/lintcheck/crates");
149+
let mut repo_path = PathBuf::from(LINTCHECK_SOURCES);
144150
// add a -git suffix in case we have the same crate from crates.io and a git repo
145151
repo_path.push(format!("{}-git", name));
146152
repo_path
@@ -182,7 +188,7 @@ impl CrateSource {
182188
use fs_extra::dir;
183189

184190
// simply copy the entire directory into our target dir
185-
let copy_dest = PathBuf::from("target/lintcheck/crates/");
191+
let copy_dest = PathBuf::from(format!("{}/", LINTCHECK_SOURCES));
186192

187193
// the source path of the crate we copied, ${copy_dest}/crate_name
188194
let crate_root = copy_dest.join(name); // .../crates/local_crate
@@ -287,6 +293,64 @@ impl Crate {
287293
}
288294
}
289295

296+
#[derive(Debug)]
297+
struct LintcheckConfig {
298+
// max number of jobs to spawn (default 1)
299+
max_jobs: usize,
300+
// we read the sources to check from here
301+
sources_toml_path: PathBuf,
302+
// we save the clippy lint results here
303+
lintcheck_results_path: PathBuf,
304+
}
305+
306+
impl LintcheckConfig {
307+
fn from_clap(clap_config: &ArgMatches) -> Self {
308+
// first, check if we got anything passed via the LINTCHECK_TOML env var,
309+
// if not, ask clap if we got any value for --crates-toml <foo>
310+
// if not, use the default "clippy_dev/lintcheck_crates.toml"
311+
let sources_toml = env::var("LINTCHECK_TOML").unwrap_or(
312+
clap_config
313+
.value_of("crates-toml")
314+
.clone()
315+
.unwrap_or("clippy_dev/lintcheck_crates.toml")
316+
.to_string(),
317+
);
318+
319+
let sources_toml_path = PathBuf::from(sources_toml);
320+
321+
// for the path where we save the lint results, get the filename without extension (so for
322+
// wasd.toml, use "wasd"...)
323+
let filename: PathBuf = sources_toml_path.file_stem().unwrap().into();
324+
let lintcheck_results_path = PathBuf::from(format!("lintcheck-logs/{}_logs.txt", filename.display()));
325+
326+
// look at the --threads arg, if 0 is passed, ask rayon rayon how many threads it would spawn and
327+
// use half of that for the physical core count
328+
// by default use a single thread
329+
let max_jobs = match clap_config.value_of("threads") {
330+
Some(threads) => {
331+
let threads: usize = threads
332+
.parse()
333+
.expect(&format!("Failed to parse '{}' to a digit", threads));
334+
if threads == 0 {
335+
// automatic choice
336+
// Rayon seems to return thread count so half that for core count
337+
(rayon::current_num_threads() / 2) as usize
338+
} else {
339+
threads
340+
}
341+
},
342+
// no -j passed, use a single thread
343+
None => 1,
344+
};
345+
346+
LintcheckConfig {
347+
max_jobs,
348+
sources_toml_path,
349+
lintcheck_results_path,
350+
}
351+
}
352+
}
353+
290354
/// takes a single json-formatted clippy warnings and returns true (we are interested in that line)
291355
/// or false (we aren't)
292356
fn filter_clippy_warnings(line: &str) -> bool {
@@ -310,19 +374,6 @@ fn filter_clippy_warnings(line: &str) -> bool {
310374
false
311375
}
312376

313-
/// get the path to lintchecks crate sources .toml file, check LINTCHECK_TOML first but if it's
314-
/// empty use the default path
315-
fn lintcheck_config_toml(toml_path: Option<&str>) -> PathBuf {
316-
PathBuf::from(
317-
env::var("LINTCHECK_TOML").unwrap_or(
318-
toml_path
319-
.clone()
320-
.unwrap_or("clippy_dev/lintcheck_crates.toml")
321-
.to_string(),
322-
),
323-
)
324-
}
325-
326377
/// Builds clippy inside the repo to make sure we have a clippy executable we can use.
327378
fn build_clippy() {
328379
let status = Command::new("cargo")
@@ -336,9 +387,7 @@ fn build_clippy() {
336387
}
337388

338389
/// Read a `toml` file and return a list of `CrateSources` that we want to check with clippy
339-
fn read_crates(toml_path: &PathBuf) -> (String, Vec<CrateSource>) {
340-
// save it so that we can use the name of the sources.toml as name for the logfile later.
341-
let toml_filename = toml_path.file_stem().unwrap().to_str().unwrap().to_string();
390+
fn read_crates(toml_path: &PathBuf) -> Vec<CrateSource> {
342391
let toml_content: String =
343392
std::fs::read_to_string(&toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display()));
344393
let crate_list: SourceList =
@@ -398,7 +447,7 @@ fn read_crates(toml_path: &PathBuf) -> (String, Vec<CrateSource>) {
398447
// sort the crates
399448
crate_sources.sort();
400449

401-
(toml_filename, crate_sources)
450+
crate_sources
402451
}
403452

404453
/// Parse the json output of clippy and return a `ClippyWarning`
@@ -450,42 +499,39 @@ fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String,
450499

451500
/// check if the latest modification of the logfile is older than the modification date of the
452501
/// clippy binary, if this is true, we should clean the lintchec shared target directory and recheck
453-
fn lintcheck_needs_rerun(toml_path: &PathBuf) -> bool {
502+
fn lintcheck_needs_rerun(lintcheck_logs_path: &PathBuf) -> bool {
454503
let clippy_modified: std::time::SystemTime = {
455-
let mut times = ["target/debug/clippy-driver", "target/debug/cargo-clippy"]
456-
.iter()
457-
.map(|p| {
458-
std::fs::metadata(p)
459-
.expect("failed to get metadata of file")
460-
.modified()
461-
.expect("failed to get modification date")
462-
});
504+
let mut times = [CLIPPY_DRIVER_PATH, CARGO_CLIPPY_PATH].iter().map(|p| {
505+
std::fs::metadata(p)
506+
.expect("failed to get metadata of file")
507+
.modified()
508+
.expect("failed to get modification date")
509+
});
463510
// the oldest modification of either of the binaries
464-
std::cmp::min(times.next().unwrap(), times.next().unwrap())
511+
std::cmp::max(times.next().unwrap(), times.next().unwrap())
465512
};
466513

467-
let logs_modified: std::time::SystemTime = std::fs::metadata(toml_path)
514+
let logs_modified: std::time::SystemTime = std::fs::metadata(lintcheck_logs_path)
468515
.expect("failed to get metadata of file")
469516
.modified()
470517
.expect("failed to get modification date");
471518

472-
// if clippys modification time is smaller (older) than the logs mod time, we need to rerun
473-
// lintcheck
474-
clippy_modified < logs_modified
519+
// time is represented in seconds since X
520+
// logs_modified 2 and clippy_modified 5 means clippy binary is older and we need to recheck
521+
logs_modified < clippy_modified
475522
}
476523

477524
/// lintchecks `main()` function
478525
pub fn run(clap_config: &ArgMatches) {
526+
let config = LintcheckConfig::from_clap(clap_config);
527+
479528
println!("Compiling clippy...");
480529
build_clippy();
481530
println!("Done compiling");
482531

483-
let clap_toml_path: Option<&str> = clap_config.value_of("crates-toml");
484-
let toml_path: PathBuf = lintcheck_config_toml(clap_toml_path);
485-
486532
// if the clippy bin is newer than our logs, throw away target dirs to force clippy to
487533
// refresh the logs
488-
if lintcheck_needs_rerun(&toml_path) {
534+
if lintcheck_needs_rerun(&config.lintcheck_results_path) {
489535
let shared_target_dir = "target/lintcheck/shared_target_dir";
490536
match std::fs::metadata(&shared_target_dir) {
491537
Ok(metadata) => {
@@ -495,12 +541,11 @@ pub fn run(clap_config: &ArgMatches) {
495541
.expect("failed to remove target/lintcheck/shared_target_dir");
496542
}
497543
},
498-
Err(_) => { // dir probably does not exist, don't remove anything
499-
},
544+
Err(_) => { /* dir probably does not exist, don't remove anything */ },
500545
}
501546
}
502547

503-
let cargo_clippy_path: PathBuf = PathBuf::from("target/debug/cargo-clippy")
548+
let cargo_clippy_path: PathBuf = PathBuf::from(CARGO_CLIPPY_PATH)
504549
.canonicalize()
505550
.expect("failed to canonicalize path to clippy binary");
506551

@@ -511,7 +556,7 @@ pub fn run(clap_config: &ArgMatches) {
511556
cargo_clippy_path.display()
512557
);
513558

514-
let clippy_ver = std::process::Command::new("target/debug/cargo-clippy")
559+
let clippy_ver = std::process::Command::new(CARGO_CLIPPY_PATH)
515560
.arg("--version")
516561
.output()
517562
.map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
@@ -520,9 +565,10 @@ pub fn run(clap_config: &ArgMatches) {
520565
// download and extract the crates, then run clippy on them and collect clippys warnings
521566
// flatten into one big list of warnings
522567

523-
let (filename, crates) = read_crates(&toml_path);
524-
let file = format!("lintcheck-logs/{}_logs.txt", filename);
525-
let old_stats = read_stats_from_file(&file);
568+
let crates = read_crates(&config.sources_toml_path);
569+
let old_stats = read_stats_from_file(&config.lintcheck_results_path);
570+
571+
let counter = AtomicUsize::new(1);
526572

527573
let clippy_warnings: Vec<ClippyWarning> = if let Some(only_one_crate) = clap_config.value_of("only") {
528574
// if we don't have the specified crate in the .toml, throw an error
@@ -550,44 +596,39 @@ pub fn run(clap_config: &ArgMatches) {
550596
.flatten()
551597
.collect()
552598
} else {
553-
let counter = std::sync::atomic::AtomicUsize::new(0);
554-
555-
// Ask rayon for thread count. Assume that half of that is the number of physical cores
556-
// Use one target dir for each core so that we can run N clippys in parallel.
557-
// We need to use different target dirs because cargo would lock them for a single build otherwise,
558-
// killing the parallelism. However this also means that deps will only be reused half/a
559-
// quarter of the time which might result in a longer wall clock runtime
560-
561-
// This helps when we check many small crates with dep-trees that don't have a lot of branches in
562-
// order to achive some kind of parallelism
563-
564-
// by default, use a single thread
565-
let num_cpus = match clap_config.value_of("threads") {
566-
Some(threads) => {
567-
let threads: usize = threads
568-
.parse()
569-
.expect(&format!("Failed to parse '{}' to a digit", threads));
570-
if threads == 0 {
571-
// automatic choice
572-
// Rayon seems to return thread count so half that for core count
573-
(rayon::current_num_threads() / 2) as usize
574-
} else {
575-
threads
576-
}
577-
},
578-
// no -j passed, use a single thread
579-
None => 1,
580-
};
581-
582-
let num_crates = crates.len();
583-
584-
// check all crates (default)
585-
crates
586-
.into_par_iter()
587-
.map(|krate| krate.download_and_extract())
588-
.map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &counter, num_cpus, num_crates))
589-
.flatten()
590-
.collect()
599+
if config.max_jobs > 1 {
600+
// run parallel with rayon
601+
602+
// Ask rayon for thread count. Assume that half of that is the number of physical cores
603+
// Use one target dir for each core so that we can run N clippys in parallel.
604+
// We need to use different target dirs because cargo would lock them for a single build otherwise,
605+
// killing the parallelism. However this also means that deps will only be reused half/a
606+
// quarter of the time which might result in a longer wall clock runtime
607+
608+
// This helps when we check many small crates with dep-trees that don't have a lot of branches in
609+
// order to achive some kind of parallelism
610+
611+
// by default, use a single thread
612+
let num_cpus = config.max_jobs;
613+
let num_crates = crates.len();
614+
615+
// check all crates (default)
616+
crates
617+
.into_par_iter()
618+
.map(|krate| krate.download_and_extract())
619+
.map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &counter, num_cpus, num_crates))
620+
.flatten()
621+
.collect()
622+
} else {
623+
// run sequential
624+
let num_crates = crates.len();
625+
crates
626+
.into_iter()
627+
.map(|krate| krate.download_and_extract())
628+
.map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &counter, 1, num_crates))
629+
.flatten()
630+
.collect()
631+
}
591632
};
592633

593634
// generate some stats
@@ -612,15 +653,14 @@ pub fn run(clap_config: &ArgMatches) {
612653
ices.iter()
613654
.for_each(|(cratename, msg)| text.push_str(&format!("{}: '{}'", cratename, msg)));
614655

615-
println!("Writing logs to {}", file);
616-
write(&file, text).unwrap();
656+
println!("Writing logs to {}", config.lintcheck_results_path.display());
657+
write(&config.lintcheck_results_path, text).unwrap();
617658

618659
print_stats(old_stats, new_stats);
619660
}
620661

621662
/// read the previous stats from the lintcheck-log file
622-
fn read_stats_from_file(file_path: &String) -> HashMap<String, usize> {
623-
let file_path = PathBuf::from(file_path);
663+
fn read_stats_from_file(file_path: &PathBuf) -> HashMap<String, usize> {
624664
let file_content: String = match std::fs::read_to_string(file_path).ok() {
625665
Some(content) => content,
626666
None => {

0 commit comments

Comments
 (0)