1
1
use crate :: builder:: { Builder , RunConfig , ShouldRun , Step } ;
2
2
use crate :: Config ;
3
3
use crate :: { t, VERSION } ;
4
+ use sha2:: Digest ;
4
5
use std:: env:: consts:: EXE_SUFFIX ;
5
6
use std:: fmt:: Write as _;
6
7
use std:: fs:: File ;
@@ -10,6 +11,9 @@ use std::process::Command;
10
11
use std:: str:: FromStr ;
11
12
use std:: { fmt, fs, io} ;
12
13
14
+ #[ cfg( test) ]
15
+ mod tests;
16
+
13
17
#[ derive( Clone , Copy , Debug , Eq , PartialEq , Hash ) ]
14
18
pub enum Profile {
15
19
Compiler ,
@@ -19,6 +23,13 @@ pub enum Profile {
19
23
User ,
20
24
}
21
25
26
+ /// A list of historical hashes of `src/etc/vscode_settings.json`.
27
+ /// New entries should be appended whenever this is updated so we can detected
28
+ /// outdated vs. user-modified settings files.
29
+ static SETTINGS_HASHES : & [ & str ] =
30
+ & [ "ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8" ] ;
31
+ static VSCODE_SETTINGS : & str = include_str ! ( "../etc/vscode_settings.json" ) ;
32
+
22
33
impl Profile {
23
34
fn include_path ( & self , src_path : & Path ) -> PathBuf {
24
35
PathBuf :: from ( format ! ( "{}/src/bootstrap/defaults/config.{}.toml" , src_path. display( ) , self ) )
@@ -155,6 +166,7 @@ pub fn setup(config: &Config, profile: Profile) {
155
166
156
167
if !config. dry_run ( ) {
157
168
t ! ( install_git_hook_maybe( & config) ) ;
169
+ t ! ( create_vscode_settings_maybe( & config) ) ;
158
170
}
159
171
160
172
println ! ( ) ;
@@ -351,6 +363,34 @@ pub fn interactive_path() -> io::Result<Profile> {
351
363
Ok ( template)
352
364
}
353
365
366
+ #[ derive( PartialEq ) ]
367
+ enum PromptResult {
368
+ Yes , // y/Y/yes
369
+ No , // n/N/no
370
+ Print , // p/P/print
371
+ }
372
+
373
+ /// Prompt a user for a answer, looping until they enter an accepted input or nothing
374
+ fn prompt_user ( prompt : & str ) -> io:: Result < Option < PromptResult > > {
375
+ let mut input = String :: new ( ) ;
376
+ loop {
377
+ print ! ( "{prompt} " ) ;
378
+ io:: stdout ( ) . flush ( ) ?;
379
+ input. clear ( ) ;
380
+ io:: stdin ( ) . read_line ( & mut input) ?;
381
+ match input. trim ( ) . to_lowercase ( ) . as_str ( ) {
382
+ "y" | "yes" => return Ok ( Some ( PromptResult :: Yes ) ) ,
383
+ "n" | "no" => return Ok ( Some ( PromptResult :: No ) ) ,
384
+ "p" | "print" => return Ok ( Some ( PromptResult :: Print ) ) ,
385
+ "" => return Ok ( None ) ,
386
+ _ => {
387
+ eprintln ! ( "error: unrecognized option '{}'" , input. trim( ) ) ;
388
+ eprintln ! ( "note: press Ctrl+C to exit" ) ;
389
+ }
390
+ } ;
391
+ }
392
+ }
393
+
354
394
// install a git hook to automatically run tidy, if they want
355
395
fn install_git_hook_maybe ( config : & Config ) -> io:: Result < ( ) > {
356
396
let git = t ! ( config. git( ) . args( & [ "rev-parse" , "--git-common-dir" ] ) . output( ) . map( |output| {
@@ -363,43 +403,98 @@ fn install_git_hook_maybe(config: &Config) -> io::Result<()> {
363
403
return Ok ( ( ) ) ;
364
404
}
365
405
366
- let mut input = String :: new ( ) ;
367
- println ! ( ) ;
368
406
println ! (
369
- "Rust 's CI will automatically fail if it doesn't pass `tidy`, the internal tool for ensuring code quality.
407
+ "\n Rust 's CI will automatically fail if it doesn't pass `tidy`, the internal tool for ensuring code quality.
370
408
If you'd like, x.py can install a git hook for you that will automatically run `test tidy` before
371
409
pushing your code to ensure your code is up to par. If you decide later that this behavior is
372
410
undesirable, simply delete the `pre-push` file from .git/hooks."
373
411
) ;
374
412
375
- let should_install = loop {
376
- print ! ( "Would you like to install the git hook?: [y/N] " ) ;
377
- io:: stdout ( ) . flush ( ) ?;
378
- input. clear ( ) ;
379
- io:: stdin ( ) . read_line ( & mut input) ?;
380
- break match input. trim ( ) . to_lowercase ( ) . as_str ( ) {
381
- "y" | "yes" => true ,
382
- "n" | "no" | "" => false ,
383
- _ => {
384
- eprintln ! ( "error: unrecognized option '{}'" , input. trim( ) ) ;
385
- eprintln ! ( "note: press Ctrl+C to exit" ) ;
386
- continue ;
387
- }
388
- } ;
389
- } ;
390
-
391
- if should_install {
392
- let src = config. src . join ( "src" ) . join ( "etc" ) . join ( "pre-push.sh" ) ;
393
- match fs:: hard_link ( src, & dst) {
394
- Err ( e) => eprintln ! (
413
+ if prompt_user ( "Would you like to install the git hook?: [y/N]" ) ? != Some ( PromptResult :: Yes ) {
414
+ println ! ( "Ok, skipping installation!" ) ;
415
+ return Ok ( ( ) ) ;
416
+ }
417
+ let src = config. src . join ( "src" ) . join ( "etc" ) . join ( "pre-push.sh" ) ;
418
+ match fs:: hard_link ( src, & dst) {
419
+ Err ( e) => {
420
+ eprintln ! (
395
421
"error: could not create hook {}: do you already have the git hook installed?\n {}" ,
396
422
dst. display( ) ,
397
423
e
398
- ) ,
399
- Ok ( _) => println ! ( "Linked `src/etc/pre-push.sh` to `.git/hooks/pre-push`" ) ,
424
+ ) ;
425
+ return Err ( e) ;
426
+ }
427
+ Ok ( _) => println ! ( "Linked `src/etc/pre-push.sh` to `.git/hooks/pre-push`" ) ,
428
+ } ;
429
+ Ok ( ( ) )
430
+ }
431
+
432
+ /// Create a `.vscode/settings.json` file for rustc development, or just print it
433
+ fn create_vscode_settings_maybe ( config : & Config ) -> io:: Result < ( ) > {
434
+ let ( current_hash, historical_hashes) = SETTINGS_HASHES . split_last ( ) . unwrap ( ) ;
435
+ let vscode_settings = config. src . join ( ".vscode" ) . join ( "settings.json" ) ;
436
+ // If None, no settings.json exists
437
+ // If Some(true), is a previous version of settings.json
438
+ // If Some(false), is not a previous version (i.e. user modified)
439
+ // If it's up to date we can just skip this
440
+ let mut mismatched_settings = None ;
441
+ if let Ok ( current) = fs:: read_to_string ( & vscode_settings) {
442
+ let mut hasher = sha2:: Sha256 :: new ( ) ;
443
+ hasher. update ( & current) ;
444
+ let hash = hex:: encode ( hasher. finalize ( ) . as_slice ( ) ) ;
445
+ if hash == * current_hash {
446
+ return Ok ( ( ) ) ;
447
+ } else if historical_hashes. contains ( & hash. as_str ( ) ) {
448
+ mismatched_settings = Some ( true ) ;
449
+ } else {
450
+ mismatched_settings = Some ( false ) ;
451
+ }
452
+ }
453
+ println ! (
454
+ "\n x.py can automatically install the recommended `.vscode/settings.json` file for rustc development"
455
+ ) ;
456
+ match mismatched_settings {
457
+ Some ( true ) => eprintln ! (
458
+ "warning: existing `.vscode/settings.json` is out of date, x.py will update it"
459
+ ) ,
460
+ Some ( false ) => eprintln ! (
461
+ "warning: existing `.vscode/settings.json` has been modified by user, x.py will back it up and replace it"
462
+ ) ,
463
+ _ => ( ) ,
464
+ }
465
+ let should_create = match prompt_user (
466
+ "Would you like to create/update `settings.json`, or only print suggested settings?: [y/p/N]" ,
467
+ ) ? {
468
+ Some ( PromptResult :: Yes ) => true ,
469
+ Some ( PromptResult :: Print ) => false ,
470
+ _ => {
471
+ println ! ( "Ok, skipping settings!" ) ;
472
+ return Ok ( ( ) ) ;
473
+ }
474
+ } ;
475
+ if should_create {
476
+ let path = config. src . join ( ".vscode" ) ;
477
+ if !path. exists ( ) {
478
+ fs:: create_dir ( & path) ?;
479
+ }
480
+ let verb = match mismatched_settings {
481
+ // exists but outdated, we can replace this
482
+ Some ( true ) => "Updated" ,
483
+ // exists but user modified, back it up
484
+ Some ( false ) => {
485
+ // exists and is not current version or outdated, so back it up
486
+ let mut backup = vscode_settings. clone ( ) ;
487
+ backup. set_extension ( "bak" ) ;
488
+ eprintln ! ( "warning: copying `settings.json` to `settings.json.bak`" ) ;
489
+ fs:: copy ( & vscode_settings, & backup) ?;
490
+ "Updated"
491
+ }
492
+ _ => "Created" ,
400
493
} ;
494
+ fs:: write ( & vscode_settings, & VSCODE_SETTINGS ) ?;
495
+ println ! ( "{verb} `.vscode/settings.json`" ) ;
401
496
} else {
402
- println ! ( "Ok, skipping installation! " ) ;
497
+ println ! ( "\n {VSCODE_SETTINGS} " ) ;
403
498
}
404
499
Ok ( ( ) )
405
500
}
0 commit comments