@@ -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" , "7378925+rust-toolstate-update@users.noreply.github.com" ) ;
335+ git_config ( "user.name" , "Rust Toolstate Update" ) ;
336+ git_config ( "credential.helper" , "store" ) ;
337+
338+ let credential = format ! ( "https://{}:x-oauth-basic@github.com\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" , "7378925+rust-toolstate-update@users.noreply.github.com" ) ;
291- git_config ( "user.name" , "Rust Toolstate Update" ) ;
292- git_config ( "credential.helper" , "store" ) ;
293-
294- let credential = format ! (
295- "https://{}:x-oauth-basic@github.com\n " ,
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