11use  crate :: builder:: { Builder ,  RunConfig ,  ShouldRun ,  Step } ; 
22use  crate :: Config ; 
33use  crate :: { t,  VERSION } ; 
4+ use  sha2:: Digest ; 
45use  std:: env:: consts:: EXE_SUFFIX ; 
56use  std:: fmt:: Write  as  _; 
67use  std:: fs:: File ; 
@@ -10,6 +11,9 @@ use std::process::Command;
1011use  std:: str:: FromStr ; 
1112use  std:: { fmt,  fs,  io} ; 
1213
14+ #[ cfg( test) ]  
15+ mod  tests; 
16+ 
1317#[ derive( Clone ,  Copy ,  Debug ,  Eq ,  PartialEq ,  Hash ) ]  
1418pub  enum  Profile  { 
1519    Compiler , 
@@ -19,6 +23,13 @@ pub enum Profile {
1923    User , 
2024} 
2125
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+ 
2233impl  Profile  { 
2334    fn  include_path ( & self ,  src_path :  & Path )  -> PathBuf  { 
2435        PathBuf :: from ( format ! ( "{}/src/bootstrap/defaults/config.{}.toml" ,  src_path. display( ) ,  self ) ) 
@@ -155,6 +166,7 @@ pub fn setup(config: &Config, profile: Profile) {
155166
156167    if  !config. dry_run ( )  { 
157168        t ! ( install_git_hook_maybe( & config) ) ; 
169+         t ! ( create_vscode_settings_maybe( & config) ) ; 
158170    } 
159171
160172    println ! ( ) ; 
@@ -351,6 +363,34 @@ pub fn interactive_path() -> io::Result<Profile> {
351363    Ok ( template) 
352364} 
353365
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+ 
354394// install a git hook to automatically run tidy, if they want 
355395fn  install_git_hook_maybe ( config :  & Config )  -> io:: Result < ( ) >  { 
356396    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<()> {
363403        return  Ok ( ( ) ) ; 
364404    } 
365405
366-     let  mut  input = String :: new ( ) ; 
367-     println ! ( ) ; 
368406    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. 
370408If you'd like, x.py can install a git hook for you that will automatically run `test tidy` before 
371409pushing your code to ensure your code is up to par. If you decide later that this behavior is 
372410undesirable, simply delete the `pre-push` file from .git/hooks." 
373411    ) ; 
374412
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 ! ( 
395421                "error: could not create hook {}: do you already have the git hook installed?\n {}" , 
396422                dst. display( ) , 
397423                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" , 
400493        } ; 
494+         fs:: write ( & vscode_settings,  & VSCODE_SETTINGS ) ?; 
495+         println ! ( "{verb} `.vscode/settings.json`" ) ; 
401496    }  else  { 
402-         println ! ( "Ok, skipping installation! " ) ; 
497+         println ! ( "\n {VSCODE_SETTINGS} " ) ; 
403498    } 
404499    Ok ( ( ) ) 
405500} 
0 commit comments