1
1
use std:: collections:: { HashMap , HashSet , BTreeSet } ;
2
2
use 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 } ;
6
6
use std:: process:: { self , Command , ExitStatus } ;
7
7
use std:: str;
8
8
@@ -21,6 +21,7 @@ use util::paths;
21
21
22
22
const FIX_ENV : & str = "__CARGO_FIX_PLZ" ;
23
23
const BROKEN_CODE_ENV : & str = "__CARGO_FIX_BROKEN_CODE" ;
24
+ const EDITION_ENV : & str = "__CARGO_FIX_EDITION" ;
24
25
25
26
pub struct FixOptions < ' a > {
26
27
pub edition : Option < & ' a str > ,
@@ -48,9 +49,10 @@ pub fn fix(ws: &Workspace, opts: &mut FixOptions) -> CargoResult<()> {
48
49
}
49
50
50
51
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
+ ) ) ;
54
56
}
55
57
opts. compile_opts . build_config . cargo_as_rustc_wrapper = true ;
56
58
* opts. compile_opts . build_config . rustfix_diagnostic_server . borrow_mut ( ) =
@@ -115,14 +117,8 @@ pub fn fix_maybe_exec_rustc() -> CargoResult<bool> {
115
117
Err ( _) => return Ok ( false ) ,
116
118
} ;
117
119
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) ;
126
122
let rustc = env:: var_os ( "RUSTC" ) . expect ( "failed to find RUSTC env var" ) ;
127
123
128
124
// 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> {
133
129
// compiling a Rust file and it *doesn't* have an absolute filename. That's
134
130
// not the best heuristic but matches what Cargo does today at least.
135
131
let mut fixes = FixedCrate :: default ( ) ;
136
- if let Some ( path) = filename {
137
- if ! Path :: new ( & path ) . is_absolute ( ) {
132
+ if let Some ( path) = & args . file {
133
+ if env :: var ( "CARGO_PRIMARY_PACKAGE" ) . is_ok ( ) {
138
134
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 ) ?;
140
136
}
141
137
}
142
138
@@ -148,11 +144,10 @@ pub fn fix_maybe_exec_rustc() -> CargoResult<bool> {
148
144
// If we didn't actually make any changes then we can immediately exec the
149
145
// new rustc, and otherwise we capture the output to hide it in the scenario
150
146
// 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" ) ;
155
147
if !fixes. original_files . is_empty ( ) {
148
+ let mut cmd = Command :: new ( & rustc) ;
149
+ args. apply ( & mut cmd) ;
150
+ cmd. arg ( "--error-format=json" ) ;
156
151
let output = cmd. output ( ) . context ( "failed to spawn rustc" ) ?;
157
152
158
153
if output. status . success ( ) {
@@ -173,17 +168,15 @@ pub fn fix_maybe_exec_rustc() -> CargoResult<bool> {
173
168
// below to recompile again.
174
169
if !output. status . success ( ) {
175
170
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)
178
172
. with_context ( |_| format ! ( "failed to write file `{}`" , k) ) ?;
179
173
}
180
174
log_failed_fix ( & output. stderr ) ?;
181
175
}
182
176
}
183
177
184
178
let mut cmd = Command :: new ( & rustc) ;
185
- cmd. args ( env:: args ( ) . skip ( 1 ) ) ;
186
- cmd. arg ( "--cap-lints=warn" ) ;
179
+ args. apply ( & mut cmd) ;
187
180
exit_with ( cmd. status ( ) . context ( "failed to spawn rustc" ) ?) ;
188
181
}
189
182
@@ -193,9 +186,12 @@ struct FixedCrate {
193
186
original_files : HashMap < String , String > ,
194
187
}
195
188
196
- fn rustfix_crate ( lock_addr : & str , rustc : & Path , filename : & str )
189
+ fn rustfix_crate ( lock_addr : & str , rustc : & Path , filename : & Path , args : & FixArgs )
197
190
-> Result < FixedCrate , Error >
198
191
{
192
+ args. verify_not_preparing_for_enabled_edition ( ) ?;
193
+ args. warn_if_preparing_probably_inert ( ) ?;
194
+
199
195
// If not empty, filter by these lints
200
196
//
201
197
// TODO: Implement a way to specify this
@@ -210,8 +206,8 @@ fn rustfix_crate(lock_addr: &str, rustc: &Path, filename: &str)
210
206
let _lock = LockServerClient :: lock ( & lock_addr. parse ( ) ?, filename) ?;
211
207
212
208
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 ) ;
215
211
let output = cmd. output ( )
216
212
. with_context ( |_| format ! ( "failed to execute `{}`" , rustc. display( ) ) ) ?;
217
213
@@ -280,7 +276,8 @@ fn rustfix_crate(lock_addr: &str, rustc: &Path, filename: &str)
280
276
281
277
debug ! (
282
278
"collected {} suggestions for `{}`" ,
283
- num_suggestion, filename
279
+ num_suggestion,
280
+ filename. display( ) ,
284
281
) ;
285
282
286
283
let mut original_files = HashMap :: with_capacity ( file_map. len ( ) ) ;
@@ -311,8 +308,7 @@ fn rustfix_crate(lock_addr: &str, rustc: &Path, filename: &str)
311
308
continue ;
312
309
}
313
310
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)
316
312
. with_context ( |_| format ! ( "failed to write file `{}`" , file) ) ?;
317
313
original_files. insert ( file, code) ;
318
314
}
@@ -369,3 +365,110 @@ fn log_failed_fix(stderr: &[u8]) -> Result<(), Error> {
369
365
370
366
Ok ( ( ) )
371
367
}
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