11use std:: collections:: { HashMap , HashSet , BTreeSet } ;
22use std:: env;
3- use std:: fs :: File ;
4- use std:: io :: Write ;
5- use std:: path:: Path ;
3+ use std:: ffi :: OsString ;
4+ use std:: fs ;
5+ use std:: path:: { Path , PathBuf } ;
66use std:: process:: { self , Command , ExitStatus } ;
77use std:: str;
88
@@ -21,6 +21,7 @@ use util::paths;
2121
2222const FIX_ENV : & str = "__CARGO_FIX_PLZ" ;
2323const BROKEN_CODE_ENV : & str = "__CARGO_FIX_BROKEN_CODE" ;
24+ const EDITION_ENV : & str = "__CARGO_FIX_EDITION" ;
2425
2526pub struct FixOptions < ' a > {
2627 pub edition : Option < & ' a str > ,
@@ -48,9 +49,10 @@ pub fn fix(ws: &Workspace, opts: &mut FixOptions) -> CargoResult<()> {
4849 }
4950
5051 if let Some ( edition) = opts. edition {
51- opts. compile_opts . build_config . extra_rustc_args . push ( "-W" . to_string ( ) ) ;
52- let lint_name = format ! ( "rust-{}-compatibility" , edition) ;
53- opts. compile_opts . build_config . extra_rustc_args . push ( lint_name) ;
52+ opts. compile_opts . build_config . extra_rustc_env . push ( (
53+ EDITION_ENV . to_string ( ) ,
54+ edition. to_string ( ) ,
55+ ) ) ;
5456 }
5557 opts. compile_opts . build_config . cargo_as_rustc_wrapper = true ;
5658 * opts. compile_opts . build_config . rustfix_diagnostic_server . borrow_mut ( ) =
@@ -115,14 +117,8 @@ pub fn fix_maybe_exec_rustc() -> CargoResult<bool> {
115117 Err ( _) => return Ok ( false ) ,
116118 } ;
117119
118- // Try to figure out what we're compiling by looking for a rust-like file
119- // that exists.
120- let filename = env:: args ( )
121- . skip ( 1 )
122- . filter ( |s| s. ends_with ( ".rs" ) )
123- . find ( |s| Path :: new ( s) . exists ( ) ) ;
124-
125- trace ! ( "cargo-fix as rustc got file {:?}" , filename) ;
120+ let args = FixArgs :: get ( ) ;
121+ trace ! ( "cargo-fix as rustc got file {:?}" , args. file) ;
126122 let rustc = env:: var_os ( "RUSTC" ) . expect ( "failed to find RUSTC env var" ) ;
127123
128124 // Our goal is to fix only the crates that the end user is interested in.
@@ -133,10 +129,10 @@ pub fn fix_maybe_exec_rustc() -> CargoResult<bool> {
133129 // compiling a Rust file and it *doesn't* have an absolute filename. That's
134130 // not the best heuristic but matches what Cargo does today at least.
135131 let mut fixes = FixedCrate :: default ( ) ;
136- if let Some ( path) = filename {
132+ if let Some ( path) = & args . file {
137133 if env:: var ( "CARGO_PRIMARY_PACKAGE" ) . is_ok ( ) {
138134 trace ! ( "start rustfixing {:?}" , path) ;
139- fixes = rustfix_crate ( & lock_addr, rustc. as_ref ( ) , & path) ?;
135+ fixes = rustfix_crate ( & lock_addr, rustc. as_ref ( ) , path, & args ) ?;
140136 }
141137 }
142138
@@ -148,11 +144,10 @@ pub fn fix_maybe_exec_rustc() -> CargoResult<bool> {
148144 // If we didn't actually make any changes then we can immediately exec the
149145 // new rustc, and otherwise we capture the output to hide it in the scenario
150146 // that we have to back it all out.
151- let mut cmd = Command :: new ( & rustc) ;
152- cmd. args ( env:: args ( ) . skip ( 1 ) ) ;
153- cmd. arg ( "--cap-lints=warn" ) ;
154- cmd. arg ( "--error-format=json" ) ;
155147 if !fixes. original_files . is_empty ( ) {
148+ let mut cmd = Command :: new ( & rustc) ;
149+ args. apply ( & mut cmd) ;
150+ cmd. arg ( "--error-format=json" ) ;
156151 let output = cmd. output ( ) . context ( "failed to spawn rustc" ) ?;
157152
158153 if output. status . success ( ) {
@@ -173,17 +168,15 @@ pub fn fix_maybe_exec_rustc() -> CargoResult<bool> {
173168 // below to recompile again.
174169 if !output. status . success ( ) {
175170 for ( k, v) in fixes. original_files {
176- File :: create ( & k)
177- . and_then ( |mut f| f. write_all ( v. as_bytes ( ) ) )
171+ fs:: write ( & k, v)
178172 . with_context ( |_| format ! ( "failed to write file `{}`" , k) ) ?;
179173 }
180174 log_failed_fix ( & output. stderr ) ?;
181175 }
182176 }
183177
184178 let mut cmd = Command :: new ( & rustc) ;
185- cmd. args ( env:: args ( ) . skip ( 1 ) ) ;
186- cmd. arg ( "--cap-lints=warn" ) ;
179+ args. apply ( & mut cmd) ;
187180 exit_with ( cmd. status ( ) . context ( "failed to spawn rustc" ) ?) ;
188181}
189182
@@ -193,9 +186,12 @@ struct FixedCrate {
193186 original_files : HashMap < String , String > ,
194187}
195188
196- fn rustfix_crate ( lock_addr : & str , rustc : & Path , filename : & str )
189+ fn rustfix_crate ( lock_addr : & str , rustc : & Path , filename : & Path , args : & FixArgs )
197190 -> Result < FixedCrate , Error >
198191{
192+ args. verify_not_preparing_for_enabled_edition ( ) ?;
193+ args. warn_if_preparing_probably_inert ( ) ?;
194+
199195 // If not empty, filter by these lints
200196 //
201197 // TODO: Implement a way to specify this
@@ -210,8 +206,8 @@ fn rustfix_crate(lock_addr: &str, rustc: &Path, filename: &str)
210206 let _lock = LockServerClient :: lock ( & lock_addr. parse ( ) ?, filename) ?;
211207
212208 let mut cmd = Command :: new ( & rustc) ;
213- cmd. args ( env :: args ( ) . skip ( 1 ) ) ;
214- cmd . arg ( "--error-format=json" ) . arg ( "--cap-lints=warn" ) ;
209+ cmd. arg ( "--error-format=json" ) ;
210+ args . apply ( & mut cmd ) ;
215211 let output = cmd. output ( )
216212 . with_context ( |_| format ! ( "failed to execute `{}`" , rustc. display( ) ) ) ?;
217213
@@ -280,7 +276,8 @@ fn rustfix_crate(lock_addr: &str, rustc: &Path, filename: &str)
280276
281277 debug ! (
282278 "collected {} suggestions for `{}`" ,
283- num_suggestion, filename
279+ num_suggestion,
280+ filename. display( ) ,
284281 ) ;
285282
286283 let mut original_files = HashMap :: with_capacity ( file_map. len ( ) ) ;
@@ -311,8 +308,7 @@ fn rustfix_crate(lock_addr: &str, rustc: &Path, filename: &str)
311308 continue ;
312309 }
313310 Ok ( new_code) => {
314- File :: create ( & file)
315- . and_then ( |mut f| f. write_all ( new_code. as_bytes ( ) ) )
311+ fs:: write ( & file, new_code)
316312 . with_context ( |_| format ! ( "failed to write file `{}`" , file) ) ?;
317313 original_files. insert ( file, code) ;
318314 }
@@ -369,3 +365,110 @@ fn log_failed_fix(stderr: &[u8]) -> Result<(), Error> {
369365
370366 Ok ( ( ) )
371367}
368+
369+ #[ derive( Default ) ]
370+ struct FixArgs {
371+ file : Option < PathBuf > ,
372+ prepare_for_edition : Option < String > ,
373+ enabled_edition : Option < String > ,
374+ other : Vec < OsString > ,
375+ }
376+
377+ impl FixArgs {
378+ fn get ( ) -> FixArgs {
379+ let mut ret = FixArgs :: default ( ) ;
380+ for arg in env:: args_os ( ) . skip ( 1 ) {
381+ let path = PathBuf :: from ( arg) ;
382+ if path. extension ( ) . and_then ( |s| s. to_str ( ) ) == Some ( "rs" ) {
383+ if path. exists ( ) {
384+ ret. file = Some ( path) ;
385+ continue
386+ }
387+ }
388+ if let Some ( s) = path. to_str ( ) {
389+ let prefix = "--edition=" ;
390+ if s. starts_with ( prefix) {
391+ ret. enabled_edition = Some ( s[ prefix. len ( ) ..] . to_string ( ) ) ;
392+ continue
393+ }
394+ }
395+ ret. other . push ( path. into ( ) ) ;
396+ }
397+ if let Ok ( s) = env:: var ( EDITION_ENV ) {
398+ ret. prepare_for_edition = Some ( s) ;
399+ }
400+ return ret
401+ }
402+
403+ fn apply ( & self , cmd : & mut Command ) {
404+ if let Some ( path) = & self . file {
405+ cmd. arg ( path) ;
406+ }
407+ cmd. args ( & self . other )
408+ . arg ( "--cap-lints=warn" ) ;
409+ if let Some ( edition) = & self . enabled_edition {
410+ cmd. arg ( "--edition" ) . arg ( edition) ;
411+ }
412+ if let Some ( prepare_for) = & self . prepare_for_edition {
413+ cmd. arg ( "-W" ) . arg ( format ! ( "rust-{}-compatibility" , prepare_for) ) ;
414+ }
415+ }
416+
417+ /// Verify that we're not both preparing for an enabled edition and enabling
418+ /// the edition.
419+ ///
420+ /// This indicates that `cargo fix --prepare-for` is being executed out of
421+ /// order with enabling the edition itself, meaning that we wouldn't
422+ /// actually be able to fix anything! If it looks like this is happening
423+ /// then yield an error to the user, indicating that this is happening.
424+ fn verify_not_preparing_for_enabled_edition ( & self ) -> CargoResult < ( ) > {
425+ let edition = match & self . prepare_for_edition {
426+ Some ( s) => s,
427+ None => return Ok ( ( ) ) ,
428+ } ;
429+ let enabled = match & self . enabled_edition {
430+ Some ( s) => s,
431+ None => return Ok ( ( ) ) ,
432+ } ;
433+ if edition != enabled {
434+ return Ok ( ( ) )
435+ }
436+ let path = match & self . file {
437+ Some ( s) => s,
438+ None => return Ok ( ( ) ) ,
439+ } ;
440+
441+ Message :: EditionAlreadyEnabled {
442+ file : path. display ( ) . to_string ( ) ,
443+ edition : edition. to_string ( ) ,
444+ } . post ( ) ?;
445+
446+ process:: exit ( 1 ) ;
447+ }
448+
449+ fn warn_if_preparing_probably_inert ( & self ) -> CargoResult < ( ) > {
450+ let edition = match & self . prepare_for_edition {
451+ Some ( s) => s,
452+ None => return Ok ( ( ) ) ,
453+ } ;
454+ let path = match & self . file {
455+ Some ( s) => s,
456+ None => return Ok ( ( ) ) ,
457+ } ;
458+ let contents = match fs:: read_to_string ( path) {
459+ Ok ( s) => s,
460+ Err ( _) => return Ok ( ( ) )
461+ } ;
462+
463+ let feature_name = format ! ( "rust_{}_preview" , edition) ;
464+ if contents. contains ( & feature_name) {
465+ return Ok ( ( ) )
466+ }
467+ Message :: PreviewNotFound {
468+ file : path. display ( ) . to_string ( ) ,
469+ edition : edition. to_string ( ) ,
470+ } . post ( ) ?;
471+
472+ Ok ( ( ) )
473+ }
474+ }
0 commit comments