@@ -6,7 +6,7 @@ use std::env;
66use  std:: fmt; 
77use  std:: fs; 
88use  std:: io:: { Seek ,  SeekFrom } ; 
9- use  std:: path:: PathBuf ; 
9+ use  std:: path:: { Path ,   PathBuf } ; 
1010use  std:: process:: Command ; 
1111use  std:: time; 
1212
@@ -24,7 +24,7 @@ const OS: Option<&str> = None;
2424
2525type  ToolstateData  = HashMap < Box < str > ,  ToolState > ; 
2626
27- #[ derive( Copy ,  Clone ,  Debug ,  Deserialize ,  Serialize ,  PartialEq ,  Eq ) ]  
27+ #[ derive( Copy ,  Clone ,  Debug ,  Deserialize ,  Serialize ,  PartialEq ,  Eq ,   PartialOrd ) ]  
2828#[ serde( rename_all = "kebab-case" ) ]  
2929/// Whether a tool can be compiled, tested or neither 
3030pub  enum  ToolState  { 
@@ -143,10 +143,31 @@ pub struct ToolStateCheck;
143143impl  Step  for  ToolStateCheck  { 
144144    type  Output  = ( ) ; 
145145
146-     /// Runs the `linkchecker`  tool as compiled in `stage` by the `host` compiler . 
146+     /// Checks  tool state status . 
147147     /// 
148-      /// This tool in `src/tools` will verify the validity of all our links in the 
149-      /// documentation to ensure we don't have a bunch of dead ones. 
148+      /// This is intended to be used in the `checktools.sh` script. To use 
149+      /// this, set `save-toolstates` in `config.toml` so that tool status will 
150+      /// be saved to a JSON file. Then, run `x.py test --no-fail-fast` for all 
151+      /// of the tools to populate the JSON file. After that is done, this 
152+      /// command can be run to check for any status failures, and exits with an 
153+      /// error if there are any. 
154+      /// 
155+      /// This also handles publishing the results to the `history` directory of 
156+      /// the toolstate repo https://github.com/rust-lang-nursery/rust-toolstate 
157+      /// if the env var `TOOLSTATE_PUBLISH` is set. Note that there is a 
158+      /// *separate* step of updating the `latest.json` file and creating GitHub 
159+      /// issues and comments in `src/ci/publish_toolstate.sh`, which is only 
160+      /// performed on master. (The shell/python code is intended to be migrated 
161+      /// here eventually.) 
162+      /// 
163+      /// The rules for failure are: 
164+      /// * If the PR modifies a tool, the status must be test-pass. 
165+      ///   NOTE: There is intent to change this, see 
166+      ///   https://github.com/rust-lang/rust/issues/65000. 
167+      /// * All "stable" tools must be test-pass on the stable or beta branches. 
168+      /// * During beta promotion week, a PR is not allowed to "regress" a 
169+      ///   stable tool. That is, the status is not allowed to get worse 
170+      ///   (test-pass to test-fail or build-fail). 
150171     fn  run ( self ,  builder :  & Builder < ' _ > )  { 
151172        if  builder. config . dry_run  { 
152173            return ; 
@@ -171,6 +192,8 @@ impl Step for ToolStateCheck {
171192        } 
172193
173194        check_changed_files ( & toolstates) ; 
195+         checkout_toolstate_repo ( ) ; 
196+         let  old_toolstate = read_old_toolstate ( ) ; 
174197
175198        for  ( tool,  _)  in  STABLE_TOOLS . iter ( )  { 
176199            let  state = toolstates[ * tool] ; 
@@ -180,11 +203,24 @@ impl Step for ToolStateCheck {
180203                    did_error = true ; 
181204                    eprintln ! ( "error: Tool `{}` should be test-pass but is {}" ,  tool,  state) ; 
182205                }  else  if  in_beta_week { 
183-                     did_error = true ; 
184-                     eprintln ! ( 
185-                         "error: Tool `{}` should be test-pass but is {} during beta week." , 
186-                         tool,  state
187-                     ) ; 
206+                     let  old_state = old_toolstate
207+                         . iter ( ) 
208+                         . find ( |ts| ts. tool  == * tool) 
209+                         . expect ( "latest.json missing tool" ) 
210+                         . state ( ) ; 
211+                     if  state < old_state { 
212+                         did_error = true ; 
213+                         eprintln ! ( 
214+                             "error: Tool `{}` has regressed from {} to {} during beta week." , 
215+                             tool,  old_state,  state
216+                         ) ; 
217+                     }  else  { 
218+                         eprintln ! ( 
219+                             "warning: Tool `{}` is not test-pass (is `{}`), \  
220+                              this should be fixed before beta is branched.", 
221+                             tool,  state
222+                         ) ; 
223+                     } 
188224                } 
189225            } 
190226        } 
@@ -247,6 +283,70 @@ impl Builder<'_> {
247283    } 
248284} 
249285
286+ fn  toolstate_repo ( )  -> String  { 
287+     env:: var ( "TOOLSTATE_REPO" ) 
288+         . unwrap_or_else ( |_| "https://github.com/rust-lang-nursery/rust-toolstate.git" . to_string ( ) ) 
289+ } 
290+ 
291+ /// Directory where the toolstate repo is checked out. 
292+ const  TOOLSTATE_DIR :  & str  = "rust-toolstate" ; 
293+ 
294+ /// Checks out the toolstate repo into `TOOLSTATE_DIR`. 
295+ fn  checkout_toolstate_repo ( )  { 
296+     if  let  Ok ( token)  = env:: var ( "TOOLSTATE_REPO_ACCESS_TOKEN" )  { 
297+         prepare_toolstate_config ( & token) ; 
298+     } 
299+     if  Path :: new ( TOOLSTATE_DIR ) . exists ( )  { 
300+         eprintln ! ( "Cleaning old toolstate directory..." ) ; 
301+         t ! ( fs:: remove_dir_all( TOOLSTATE_DIR ) ) ; 
302+     } 
303+ 
304+     let  status = Command :: new ( "git" ) 
305+         . arg ( "clone" ) 
306+         . arg ( "--depth=1" ) 
307+         . arg ( toolstate_repo ( ) ) 
308+         . arg ( TOOLSTATE_DIR ) 
309+         . status ( ) ; 
310+     let  success = match  status { 
311+         Ok ( s)  => s. success ( ) , 
312+         Err ( _)  => false , 
313+     } ; 
314+     if  !success { 
315+         panic ! ( "git clone unsuccessful (status: {:?})" ,  status) ; 
316+     } 
317+ } 
318+ 
319+ /// Sets up config and authentication for modifying the toolstate repo. 
320+ fn  prepare_toolstate_config ( token :  & str )  { 
321+     fn  git_config ( key :  & str ,  value :  & str )  { 
322+         let  status = Command :: new ( "git" ) . arg ( "config" ) . arg ( "--global" ) . arg ( key) . arg ( value) . status ( ) ; 
323+         let  success = match  status { 
324+             Ok ( s)  => s. success ( ) , 
325+             Err ( _)  => false , 
326+         } ; 
327+         if  !success { 
328+             panic ! ( "git config key={} value={} successful (status: {:?})" ,  key,  value,  status) ; 
329+         } 
330+     } 
331+ 
332+     // If changing anything here, then please check that src/ci/publish_toolstate.sh is up to date 
333+     // as well. 
334+     git_config ( "user.email" ,  "[email protected] " ) ;  335+     git_config ( "user.name" ,  "Rust Toolstate Update" ) ; 
336+     git_config ( "credential.helper" ,  "store" ) ; 
337+ 
338+     let  credential = 
format ! ( "https://{}:[email protected] \n " ,  token
, ) ;  339+     let  git_credential_path = PathBuf :: from ( t ! ( env:: var( "HOME" ) ) ) . join ( ".git-credentials" ) ; 
340+     t ! ( fs:: write( & git_credential_path,  credential) ) ; 
341+ } 
342+ 
343+ /// Reads the latest toolstate from the toolstate repo. 
344+ fn  read_old_toolstate ( )  -> Vec < RepoState >  { 
345+     let  latest_path = Path :: new ( TOOLSTATE_DIR ) . join ( "_data" ) . join ( "latest.json" ) ; 
346+     let  old_toolstate = t ! ( fs:: read( latest_path) ) ; 
347+     t ! ( serde_json:: from_slice( & old_toolstate) ) 
348+ } 
349+ 
250350/// This function `commit_toolstate_change` provides functionality for pushing a change 
251351/// to the `rust-toolstate` repository. 
252352/// 
@@ -274,45 +374,7 @@ impl Builder<'_> {
274374///       * See <https://help.github.com/articles/about-commit-email-addresses/> 
275375///           if a private email by GitHub is wanted. 
276376fn  commit_toolstate_change ( current_toolstate :  & ToolstateData ,  in_beta_week :  bool )  { 
277-     fn  git_config ( key :  & str ,  value :  & str )  { 
278-         let  status = Command :: new ( "git" ) . arg ( "config" ) . arg ( "--global" ) . arg ( key) . arg ( value) . status ( ) ; 
279-         let  success = match  status { 
280-             Ok ( s)  => s. success ( ) , 
281-             Err ( _)  => false , 
282-         } ; 
283-         if  !success { 
284-             panic ! ( "git config key={} value={} successful (status: {:?})" ,  key,  value,  status) ; 
285-         } 
286-     } 
287- 
288-     // If changing anything here, then please check that src/ci/publish_toolstate.sh is up to date 
289-     // as well. 
290-     git_config ( "user.email" ,  "[email protected] " ) ;  291-     git_config ( "user.name" ,  "Rust Toolstate Update" ) ; 
292-     git_config ( "credential.helper" ,  "store" ) ; 
293- 
294-     let  credential = format ! ( 
295- 296-         t!( env:: var( "TOOLSTATE_REPO_ACCESS_TOKEN" ) ) , 
297-     ) ; 
298-     let  git_credential_path = PathBuf :: from ( t ! ( env:: var( "HOME" ) ) ) . join ( ".git-credentials" ) ; 
299-     t ! ( fs:: write( & git_credential_path,  credential) ) ; 
300- 
301-     let  status = Command :: new ( "git" ) 
302-         . arg ( "clone" ) 
303-         . arg ( "--depth=1" ) 
304-         . arg ( t ! ( env:: var( "TOOLSTATE_REPO" ) ) ) 
305-         . status ( ) ; 
306-     let  success = match  status { 
307-         Ok ( s)  => s. success ( ) , 
308-         Err ( _)  => false , 
309-     } ; 
310-     if  !success { 
311-         panic ! ( "git clone successful (status: {:?})" ,  status) ; 
312-     } 
313- 
314-     let  old_toolstate = t ! ( fs:: read( "rust-toolstate/_data/latest.json" ) ) ; 
315-     let  old_toolstate:  Vec < RepoState >  = t ! ( serde_json:: from_slice( & old_toolstate) ) ; 
377+     let  old_toolstate = read_old_toolstate ( ) ; 
316378
317379    let  message = format ! ( "({} CI update)" ,  OS . expect( "linux/windows only" ) ) ; 
318380    let  mut  success = false ; 
@@ -322,7 +384,7 @@ fn commit_toolstate_change(current_toolstate: &ToolstateData, in_beta_week: bool
322384
323385        // `git commit` failing means nothing to commit. 
324386        let  status = t ! ( Command :: new( "git" ) 
325-             . current_dir( "rust-toolstate" ) 
387+             . current_dir( TOOLSTATE_DIR ) 
326388            . arg( "commit" ) 
327389            . arg( "-a" ) 
328390            . arg( "-m" ) 
@@ -334,7 +396,7 @@ fn commit_toolstate_change(current_toolstate: &ToolstateData, in_beta_week: bool
334396        } 
335397
336398        let  status = t ! ( Command :: new( "git" ) 
337-             . current_dir( "rust-toolstate" ) 
399+             . current_dir( TOOLSTATE_DIR ) 
338400            . arg( "push" ) 
339401            . arg( "origin" ) 
340402            . arg( "master" ) 
@@ -347,14 +409,14 @@ fn commit_toolstate_change(current_toolstate: &ToolstateData, in_beta_week: bool
347409        eprintln ! ( "Sleeping for 3 seconds before retrying push" ) ; 
348410        std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 3 ) ) ; 
349411        let  status = t ! ( Command :: new( "git" ) 
350-             . current_dir( "rust-toolstate" ) 
412+             . current_dir( TOOLSTATE_DIR ) 
351413            . arg( "fetch" ) 
352414            . arg( "origin" ) 
353415            . arg( "master" ) 
354416            . status( ) ) ; 
355417        assert ! ( status. success( ) ) ; 
356418        let  status = t ! ( Command :: new( "git" ) 
357-             . current_dir( "rust-toolstate" ) 
419+             . current_dir( TOOLSTATE_DIR ) 
358420            . arg( "reset" ) 
359421            . arg( "--hard" ) 
360422            . arg( "origin/master" ) 
@@ -375,18 +437,12 @@ fn change_toolstate(
375437    let  mut  regressed = false ; 
376438    for  repo_state in  old_toolstate { 
377439        let  tool = & repo_state. tool ; 
378-         let  state = if  cfg ! ( target_os = "linux" )  { 
379-             & repo_state. linux 
380-         }  else  if  cfg ! ( windows)  { 
381-             & repo_state. windows 
382-         }  else  { 
383-             unimplemented ! ( ) 
384-         } ; 
440+         let  state = repo_state. state ( ) ; 
385441        let  new_state = current_toolstate[ tool. as_str ( ) ] ; 
386442
387-         if  new_state != * state { 
443+         if  new_state != state { 
388444            eprintln ! ( "The state of `{}` has changed from `{}` to `{}`" ,  tool,  state,  new_state) ; 
389-             if  ( new_state as   u8 )  <  ( * state  as   u8 )  { 
445+             if  new_state <  state { 
390446                if  ![ "rustc-guide" ,  "miri" ,  "embedded-book" ] . contains ( & tool. as_str ( ) )  { 
391447                    regressed = true ; 
392448                } 
@@ -403,7 +459,9 @@ fn change_toolstate(
403459
404460    let  toolstate_serialized = t ! ( serde_json:: to_string( & current_toolstate) ) ; 
405461
406-     let  history_path = format ! ( "rust-toolstate/history/{}.tsv" ,  OS . expect( "linux/windows only" ) ) ; 
462+     let  history_path = Path :: new ( TOOLSTATE_DIR ) 
463+         . join ( "history" ) 
464+         . join ( format ! ( "{}.tsv" ,  OS . expect( "linux/windows only" ) ) ) ; 
407465    let  mut  file = t ! ( fs:: read_to_string( & history_path) ) ; 
408466    let  end_of_first_line = file. find ( '\n' ) . unwrap ( ) ; 
409467    file. insert_str ( end_of_first_line,  & format ! ( "\n {}\t {}" ,  commit. trim( ) ,  toolstate_serialized) ) ; 
@@ -418,3 +476,15 @@ struct RepoState {
418476    commit :  String , 
419477    datetime :  String , 
420478} 
479+ 
480+ impl  RepoState  { 
481+     fn  state ( & self )  -> ToolState  { 
482+         if  cfg ! ( target_os = "linux" )  { 
483+             self . linux 
484+         }  else  if  cfg ! ( windows)  { 
485+             self . windows 
486+         }  else  { 
487+             unimplemented ! ( ) 
488+         } 
489+     } 
490+ } 
0 commit comments