@@ -19,6 +19,12 @@ use rayon::prelude::*;
19
19
use serde:: { Deserialize , Serialize } ;
20
20
use serde_json:: Value ;
21
21
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
+
22
28
/// List of sources to check, loaded from a .toml file
23
29
#[ derive( Debug , Serialize , Deserialize ) ]
24
30
struct SourceList {
@@ -86,7 +92,7 @@ impl std::fmt::Display for ClippyWarning {
86
92
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
87
93
writeln ! (
88
94
f,
89
- r#"{}-{}/{}:{}:{} {} "{}""# ,
95
+ r#"target/lintcheck/sources/ {}-{}/{}:{}:{} {} "{}""# ,
90
96
& self . crate_name, & self . crate_version, & self . file, & self . line, & self . column, & self . linttype, & self . message
91
97
)
92
98
}
@@ -99,8 +105,8 @@ impl CrateSource {
99
105
fn download_and_extract ( & self ) -> Crate {
100
106
match self {
101
107
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 ) ;
104
110
105
111
// url to download the crate from crates.io
106
112
let url = format ! ( "https://crates.io/api/v1/crates/{}/{}/download" , name, version) ;
@@ -140,7 +146,7 @@ impl CrateSource {
140
146
options,
141
147
} => {
142
148
let repo_path = {
143
- let mut repo_path = PathBuf :: from ( "target/lintcheck/crates" ) ;
149
+ let mut repo_path = PathBuf :: from ( LINTCHECK_SOURCES ) ;
144
150
// add a -git suffix in case we have the same crate from crates.io and a git repo
145
151
repo_path. push ( format ! ( "{}-git" , name) ) ;
146
152
repo_path
@@ -182,7 +188,7 @@ impl CrateSource {
182
188
use fs_extra:: dir;
183
189
184
190
// 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 ) ) ;
186
192
187
193
// the source path of the crate we copied, ${copy_dest}/crate_name
188
194
let crate_root = copy_dest. join ( name) ; // .../crates/local_crate
@@ -287,6 +293,64 @@ impl Crate {
287
293
}
288
294
}
289
295
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
+
290
354
/// takes a single json-formatted clippy warnings and returns true (we are interested in that line)
291
355
/// or false (we aren't)
292
356
fn filter_clippy_warnings ( line : & str ) -> bool {
@@ -310,19 +374,6 @@ fn filter_clippy_warnings(line: &str) -> bool {
310
374
false
311
375
}
312
376
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
-
326
377
/// Builds clippy inside the repo to make sure we have a clippy executable we can use.
327
378
fn build_clippy ( ) {
328
379
let status = Command :: new ( "cargo" )
@@ -336,9 +387,7 @@ fn build_clippy() {
336
387
}
337
388
338
389
/// 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 > {
342
391
let toml_content: String =
343
392
std:: fs:: read_to_string ( & toml_path) . unwrap_or_else ( |_| panic ! ( "Failed to read {}" , toml_path. display( ) ) ) ;
344
393
let crate_list: SourceList =
@@ -398,7 +447,7 @@ fn read_crates(toml_path: &PathBuf) -> (String, Vec<CrateSource>) {
398
447
// sort the crates
399
448
crate_sources. sort ( ) ;
400
449
401
- ( toml_filename , crate_sources)
450
+ crate_sources
402
451
}
403
452
404
453
/// Parse the json output of clippy and return a `ClippyWarning`
@@ -450,42 +499,39 @@ fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String,
450
499
451
500
/// check if the latest modification of the logfile is older than the modification date of the
452
501
/// 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 {
454
503
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
+ } ) ;
463
510
// 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 ( ) )
465
512
} ;
466
513
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 )
468
515
. expect ( "failed to get metadata of file" )
469
516
. modified ( )
470
517
. expect ( "failed to get modification date" ) ;
471
518
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
475
522
}
476
523
477
524
/// lintchecks `main()` function
478
525
pub fn run ( clap_config : & ArgMatches ) {
526
+ let config = LintcheckConfig :: from_clap ( clap_config) ;
527
+
479
528
println ! ( "Compiling clippy..." ) ;
480
529
build_clippy ( ) ;
481
530
println ! ( "Done compiling" ) ;
482
531
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
-
486
532
// if the clippy bin is newer than our logs, throw away target dirs to force clippy to
487
533
// refresh the logs
488
- if lintcheck_needs_rerun ( & toml_path ) {
534
+ if lintcheck_needs_rerun ( & config . lintcheck_results_path ) {
489
535
let shared_target_dir = "target/lintcheck/shared_target_dir" ;
490
536
match std:: fs:: metadata ( & shared_target_dir) {
491
537
Ok ( metadata) => {
@@ -495,12 +541,11 @@ pub fn run(clap_config: &ArgMatches) {
495
541
. expect ( "failed to remove target/lintcheck/shared_target_dir" ) ;
496
542
}
497
543
} ,
498
- Err ( _) => { // dir probably does not exist, don't remove anything
499
- } ,
544
+ Err ( _) => { /* dir probably does not exist, don't remove anything */ } ,
500
545
}
501
546
}
502
547
503
- let cargo_clippy_path: PathBuf = PathBuf :: from ( "target/debug/cargo-clippy" )
548
+ let cargo_clippy_path: PathBuf = PathBuf :: from ( CARGO_CLIPPY_PATH )
504
549
. canonicalize ( )
505
550
. expect ( "failed to canonicalize path to clippy binary" ) ;
506
551
@@ -511,7 +556,7 @@ pub fn run(clap_config: &ArgMatches) {
511
556
cargo_clippy_path. display( )
512
557
) ;
513
558
514
- let clippy_ver = std:: process:: Command :: new ( "target/debug/cargo-clippy" )
559
+ let clippy_ver = std:: process:: Command :: new ( CARGO_CLIPPY_PATH )
515
560
. arg ( "--version" )
516
561
. output ( )
517
562
. map ( |o| String :: from_utf8_lossy ( & o. stdout ) . into_owned ( ) )
@@ -520,9 +565,10 @@ pub fn run(clap_config: &ArgMatches) {
520
565
// download and extract the crates, then run clippy on them and collect clippys warnings
521
566
// flatten into one big list of warnings
522
567
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 ) ;
526
572
527
573
let clippy_warnings: Vec < ClippyWarning > = if let Some ( only_one_crate) = clap_config. value_of ( "only" ) {
528
574
// if we don't have the specified crate in the .toml, throw an error
@@ -550,44 +596,39 @@ pub fn run(clap_config: &ArgMatches) {
550
596
. flatten ( )
551
597
. collect ( )
552
598
} 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
+ }
591
632
} ;
592
633
593
634
// generate some stats
@@ -612,15 +653,14 @@ pub fn run(clap_config: &ArgMatches) {
612
653
ices. iter ( )
613
654
. for_each ( |( cratename, msg) | text. push_str ( & format ! ( "{}: '{}'" , cratename, msg) ) ) ;
614
655
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 ( ) ;
617
658
618
659
print_stats ( old_stats, new_stats) ;
619
660
}
620
661
621
662
/// 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 > {
624
664
let file_content: String = match std:: fs:: read_to_string ( file_path) . ok ( ) {
625
665
Some ( content) => content,
626
666
None => {
0 commit comments