@@ -6,7 +6,7 @@ use std::env;
6
6
use std:: fmt;
7
7
use std:: fs;
8
8
use std:: io:: { Seek , SeekFrom } ;
9
- use std:: path:: PathBuf ;
9
+ use std:: path:: { Path , PathBuf } ;
10
10
use std:: process:: Command ;
11
11
use std:: time;
12
12
@@ -24,7 +24,7 @@ const OS: Option<&str> = None;
24
24
25
25
type ToolstateData = HashMap < Box < str > , ToolState > ;
26
26
27
- #[ derive( Copy , Clone , Debug , Deserialize , Serialize , PartialEq , Eq ) ]
27
+ #[ derive( Copy , Clone , Debug , Deserialize , Serialize , PartialEq , Eq , PartialOrd ) ]
28
28
#[ serde( rename_all = "kebab-case" ) ]
29
29
/// Whether a tool can be compiled, tested or neither
30
30
pub enum ToolState {
@@ -143,10 +143,31 @@ pub struct ToolStateCheck;
143
143
impl Step for ToolStateCheck {
144
144
type Output = ( ) ;
145
145
146
- /// Runs the `linkchecker` tool as compiled in `stage` by the `host` compiler .
146
+ /// Checks tool state status .
147
147
///
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).
150
171
fn run ( self , builder : & Builder < ' _ > ) {
151
172
if builder. config . dry_run {
152
173
return ;
@@ -171,6 +192,8 @@ impl Step for ToolStateCheck {
171
192
}
172
193
173
194
check_changed_files ( & toolstates) ;
195
+ checkout_toolstate_repo ( ) ;
196
+ let old_toolstate = read_old_toolstate ( ) ;
174
197
175
198
for ( tool, _) in STABLE_TOOLS . iter ( ) {
176
199
let state = toolstates[ * tool] ;
@@ -180,11 +203,24 @@ impl Step for ToolStateCheck {
180
203
did_error = true ;
181
204
eprintln ! ( "error: Tool `{}` should be test-pass but is {}" , tool, state) ;
182
205
} 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
+ }
188
224
}
189
225
}
190
226
}
@@ -247,6 +283,70 @@ impl Builder<'_> {
247
283
}
248
284
}
249
285
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
+
250
350
/// This function `commit_toolstate_change` provides functionality for pushing a change
251
351
/// to the `rust-toolstate` repository.
252
352
///
@@ -274,45 +374,7 @@ impl Builder<'_> {
274
374
/// * See <https://help.github.com/articles/about-commit-email-addresses/>
275
375
/// if a private email by GitHub is wanted.
276
376
fn 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 ( ) ;
316
378
317
379
let message = format ! ( "({} CI update)" , OS . expect( "linux/windows only" ) ) ;
318
380
let mut success = false ;
@@ -322,7 +384,7 @@ fn commit_toolstate_change(current_toolstate: &ToolstateData, in_beta_week: bool
322
384
323
385
// `git commit` failing means nothing to commit.
324
386
let status = t ! ( Command :: new( "git" )
325
- . current_dir( "rust-toolstate" )
387
+ . current_dir( TOOLSTATE_DIR )
326
388
. arg( "commit" )
327
389
. arg( "-a" )
328
390
. arg( "-m" )
@@ -334,7 +396,7 @@ fn commit_toolstate_change(current_toolstate: &ToolstateData, in_beta_week: bool
334
396
}
335
397
336
398
let status = t ! ( Command :: new( "git" )
337
- . current_dir( "rust-toolstate" )
399
+ . current_dir( TOOLSTATE_DIR )
338
400
. arg( "push" )
339
401
. arg( "origin" )
340
402
. arg( "master" )
@@ -347,14 +409,14 @@ fn commit_toolstate_change(current_toolstate: &ToolstateData, in_beta_week: bool
347
409
eprintln ! ( "Sleeping for 3 seconds before retrying push" ) ;
348
410
std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 3 ) ) ;
349
411
let status = t ! ( Command :: new( "git" )
350
- . current_dir( "rust-toolstate" )
412
+ . current_dir( TOOLSTATE_DIR )
351
413
. arg( "fetch" )
352
414
. arg( "origin" )
353
415
. arg( "master" )
354
416
. status( ) ) ;
355
417
assert ! ( status. success( ) ) ;
356
418
let status = t ! ( Command :: new( "git" )
357
- . current_dir( "rust-toolstate" )
419
+ . current_dir( TOOLSTATE_DIR )
358
420
. arg( "reset" )
359
421
. arg( "--hard" )
360
422
. arg( "origin/master" )
@@ -375,18 +437,12 @@ fn change_toolstate(
375
437
let mut regressed = false ;
376
438
for repo_state in old_toolstate {
377
439
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 ( ) ;
385
441
let new_state = current_toolstate[ tool. as_str ( ) ] ;
386
442
387
- if new_state != * state {
443
+ if new_state != state {
388
444
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 {
390
446
if ![ "rustc-guide" , "miri" , "embedded-book" ] . contains ( & tool. as_str ( ) ) {
391
447
regressed = true ;
392
448
}
@@ -403,7 +459,9 @@ fn change_toolstate(
403
459
404
460
let toolstate_serialized = t ! ( serde_json:: to_string( & current_toolstate) ) ;
405
461
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" ) ) ) ;
407
465
let mut file = t ! ( fs:: read_to_string( & history_path) ) ;
408
466
let end_of_first_line = file. find ( '\n' ) . unwrap ( ) ;
409
467
file. insert_str ( end_of_first_line, & format ! ( "\n {}\t {}" , commit. trim( ) , toolstate_serialized) ) ;
@@ -418,3 +476,15 @@ struct RepoState {
418
476
commit : String ,
419
477
datetime : String ,
420
478
}
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