1+ use crate :: utils:: { ErrAction , expect_action} ;
2+ use core:: fmt:: Display ;
3+ use core:: mem;
14use std:: path:: Path ;
25use std:: process:: Command ;
36use std:: time:: { Duration , SystemTime } ;
4- use std:: { env, thread} ;
7+ use std:: { env, fs, thread} ;
8+ use walkdir:: WalkDir ;
59
610#[ cfg( windows) ]
711const PYTHON : & str = "python" ;
@@ -18,56 +22,85 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
1822 Some ( lint) => format ! ( "http://localhost:{port}/#{lint}" ) ,
1923 } ) ;
2024
25+ let mut last_update = mtime ( "util/gh-pages/index.html" ) ;
2126 loop {
22- let index_time = mtime ( "util/gh-pages/index.html" ) ;
23- let times = [
24- "clippy_lints/src" ,
25- "util/gh-pages/index_template.html" ,
26- "tests/compile-test.rs" ,
27- ]
28- . map ( mtime) ;
29-
30- if times. iter ( ) . any ( |& time| index_time < time) {
31- Command :: new ( env:: var ( "CARGO" ) . unwrap_or_else ( |_| "cargo" . into ( ) ) )
32- . arg ( "collect-metadata" )
33- . spawn ( )
34- . unwrap ( )
35- . wait ( )
36- . unwrap ( ) ;
27+ if is_metadata_outdated ( mem:: replace ( & mut last_update, SystemTime :: now ( ) ) ) {
28+ // Ignore the command result; we'll fall back to displaying the old metadata.
29+ let _ = expect_action (
30+ Command :: new ( env:: var ( "CARGO" ) . unwrap_or_else ( |_| "cargo" . into ( ) ) )
31+ . arg ( "collect-metadata" )
32+ . status ( ) ,
33+ ErrAction :: Run ,
34+ "cargo collect-metadata" ,
35+ ) ;
36+ last_update = SystemTime :: now ( ) ;
3737 }
38+
39+ // Only start the web server the first time around.
3840 if let Some ( url) = url. take ( ) {
3941 thread:: spawn ( move || {
40- let mut child = Command :: new ( PYTHON )
41- . arg ( "-m" )
42- . arg ( "http.server" )
43- . arg ( port. to_string ( ) )
44- . current_dir ( "util/gh-pages" )
45- . spawn ( )
46- . unwrap ( ) ;
42+ let mut child = expect_action (
43+ Command :: new ( PYTHON )
44+ . args ( [ "-m" , "http.server" , port. to_string ( ) . as_str ( ) ] )
45+ . current_dir ( "util/gh-pages" )
46+ . spawn ( ) ,
47+ ErrAction :: Run ,
48+ "python -m http.server" ,
49+ ) ;
4750 // Give some time for python to start
4851 thread:: sleep ( Duration :: from_millis ( 500 ) ) ;
4952 // Launch browser after first export.py has completed and http.server is up
5053 let _result = opener:: open ( url) ;
51- child. wait ( ) . unwrap ( ) ;
54+ expect_action ( child. wait ( ) , ErrAction :: Run , "python -m http.server" ) ;
5255 } ) ;
5356 }
57+
58+ // Delay to avoid updating the metadata too aggressively.
5459 thread:: sleep ( Duration :: from_millis ( 1000 ) ) ;
5560 }
5661}
5762
58- fn mtime ( path : impl AsRef < Path > ) -> SystemTime {
59- let path = path. as_ref ( ) ;
60- if path. is_dir ( ) {
61- path. read_dir ( )
62- . into_iter ( )
63- . flatten ( )
64- . flatten ( )
65- . map ( |entry| mtime ( entry. path ( ) ) )
66- . max ( )
67- . unwrap_or ( SystemTime :: UNIX_EPOCH )
68- } else {
69- path. metadata ( )
70- . and_then ( |metadata| metadata. modified ( ) )
71- . unwrap_or ( SystemTime :: UNIX_EPOCH )
63+ fn log_err_and_continue < T > ( res : Result < T , impl Display > , path : & Path ) -> Option < T > {
64+ match res {
65+ Ok ( x) => Some ( x) ,
66+ Err ( ref e) => {
67+ eprintln ! ( "error reading `{}`: {e}" , path. display( ) ) ;
68+ None
69+ } ,
7270 }
7371}
72+
73+ fn mtime ( path : & str ) -> SystemTime {
74+ log_err_and_continue ( fs:: metadata ( path) , path. as_ref ( ) )
75+ . and_then ( |metadata| log_err_and_continue ( metadata. modified ( ) , path. as_ref ( ) ) )
76+ . unwrap_or ( SystemTime :: UNIX_EPOCH )
77+ }
78+
79+ fn is_metadata_outdated ( time : SystemTime ) -> bool {
80+ // Ignore all IO errors here. We don't want to stop them from hosting the server.
81+ if time < mtime ( "util/gh-pages/index_template.html" ) || time < mtime ( "tests/compile-test.rs" ) {
82+ return true ;
83+ }
84+ let Some ( dir) = log_err_and_continue ( fs:: read_dir ( "." ) , "." . as_ref ( ) ) else {
85+ return false ;
86+ } ;
87+ dir. map_while ( |e| log_err_and_continue ( e, "." . as_ref ( ) ) ) . any ( |e| {
88+ let name = e. file_name ( ) ;
89+ let name_bytes = name. as_encoded_bytes ( ) ;
90+ if ( name_bytes. starts_with ( b"clippy_lints" ) && name_bytes != b"clippy_lints_internal" )
91+ || name_bytes == b"clippy_config"
92+ {
93+ WalkDir :: new ( & name)
94+ . into_iter ( )
95+ . map_while ( |e| log_err_and_continue ( e, name. as_ref ( ) ) )
96+ . filter ( |e| e. file_type ( ) . is_file ( ) )
97+ . filter_map ( |e| {
98+ log_err_and_continue ( e. metadata ( ) , e. path ( ) )
99+ . and_then ( |m| log_err_and_continue ( m. modified ( ) , e. path ( ) ) )
100+ } )
101+ . any ( |ftime| time < ftime)
102+ } else {
103+ false
104+ }
105+ } )
106+ }
0 commit comments