35
35
//! applied cleanly, rustc is run again to verify the suggestions didn't
36
36
//! break anything. The change will be backed out if it fails (unless
37
37
//! `--broken-code` is used).
38
- //! - If there are any warnings or errors, rustc will be run one last time to
39
- //! show them to the user.
40
38
41
39
use std:: collections:: { BTreeSet , HashMap , HashSet } ;
42
40
use std:: ffi:: OsString ;
41
+ use std:: io:: Write ;
43
42
use std:: path:: { Path , PathBuf } ;
44
- use std:: process:: { self , ExitStatus } ;
43
+ use std:: process:: { self , ExitStatus , Output } ;
45
44
use std:: { env, fs, str} ;
46
45
47
46
use anyhow:: { bail, Context as _} ;
@@ -388,73 +387,94 @@ pub fn fix_exec_rustc(config: &Config, lock_addr: &str) -> CargoResult<()> {
388
387
trace ! ( "start rustfixing {:?}" , args. file) ;
389
388
let fixes = rustfix_crate ( & lock_addr, & rustc, & args. file , & args, config) ?;
390
389
391
- // Ok now we have our final goal of testing out the changes that we applied.
392
- // If these changes went awry and actually started to cause the crate to
393
- // *stop* compiling then we want to back them out and continue to print
394
- // warnings to the user.
395
- //
396
- // If we didn't actually make any changes then we can immediately execute the
397
- // new rustc, and otherwise we capture the output to hide it in the scenario
398
- // that we have to back it all out.
399
- if !fixes. files . is_empty ( ) {
400
- debug ! ( "calling rustc for final verification: {rustc}" ) ;
401
- let output = rustc. output ( ) ?;
402
-
403
- if output. status . success ( ) {
404
- for ( path, file) in fixes. files . iter ( ) {
405
- Message :: Fixed {
406
- file : path. clone ( ) ,
407
- fixes : file. fixes_applied ,
408
- }
409
- . post ( config) ?;
390
+ if fixes. last_output . status . success ( ) {
391
+ for ( path, file) in fixes. files . iter ( ) {
392
+ Message :: Fixed {
393
+ file : path. clone ( ) ,
394
+ fixes : file. fixes_applied ,
410
395
}
396
+ . post ( config) ?;
411
397
}
398
+ // Display any remaining diagnostics.
399
+ emit_output ( & fixes. last_output ) ?;
400
+ return Ok ( ( ) ) ;
401
+ }
412
402
413
- // If we succeeded then we'll want to commit to the changes we made, if
414
- // any. If stderr is empty then there's no need for the final exec at
415
- // the end, we just bail out here.
416
- if output. status . success ( ) && output. stderr . is_empty ( ) {
417
- return Ok ( ( ) ) ;
403
+ let allow_broken_code = config. get_env_os ( BROKEN_CODE_ENV_INTERNAL ) . is_some ( ) ;
404
+
405
+ // There was an error running rustc during the last run.
406
+ //
407
+ // Back out all of the changes unless --broken-code was used.
408
+ if !allow_broken_code {
409
+ for ( path, file) in fixes. files . iter ( ) {
410
+ debug ! ( "reverting {:?} due to errors" , path) ;
411
+ paths:: write ( path, & file. original_code ) ?;
418
412
}
413
+ }
419
414
420
- // Otherwise, if our rustc just failed, then that means that we broke the
421
- // user's code with our changes. Back out everything and fall through
422
- // below to recompile again.
423
- if !output. status . success ( ) {
424
- if config. get_env_os ( BROKEN_CODE_ENV_INTERNAL ) . is_none ( ) {
425
- for ( path, file) in fixes. files . iter ( ) {
426
- debug ! ( "reverting {:?} due to errors" , path) ;
427
- paths:: write ( path, & file. original_code ) ?;
415
+ // If there were any fixes, let the user know that there was a failure
416
+ // attempting to apply them, and to ask for a bug report.
417
+ //
418
+ // FIXME: The error message here is not correct with --broken-code.
419
+ // https://github.com/rust-lang/cargo/issues/10955
420
+ if fixes. files . is_empty ( ) {
421
+ // No fixes were available. Display whatever errors happened.
422
+ emit_output ( & fixes. last_output ) ?;
423
+ exit_with ( fixes. last_output . status ) ;
424
+ } else {
425
+ let krate = {
426
+ let mut iter = rustc. get_args ( ) ;
427
+ let mut krate = None ;
428
+ while let Some ( arg) = iter. next ( ) {
429
+ if arg == "--crate-name" {
430
+ krate = iter. next ( ) . and_then ( |s| s. to_owned ( ) . into_string ( ) . ok ( ) ) ;
428
431
}
429
432
}
430
-
431
- let krate = {
432
- let mut iter = rustc. get_args ( ) ;
433
- let mut krate = None ;
434
- while let Some ( arg) = iter. next ( ) {
435
- if arg == "--crate-name" {
436
- krate = iter. next ( ) . and_then ( |s| s. to_owned ( ) . into_string ( ) . ok ( ) ) ;
437
- }
438
- }
439
- krate
440
- } ;
441
- log_failed_fix ( config, krate, & output. stderr , output. status ) ?;
442
- }
433
+ krate
434
+ } ;
435
+ log_failed_fix (
436
+ config,
437
+ krate,
438
+ & fixes. last_output . stderr ,
439
+ fixes. last_output . status ,
440
+ ) ?;
441
+ // Display the diagnostics that appeared at the start, before the
442
+ // fixes failed. This can help with diagnosing which suggestions
443
+ // caused the failure.
444
+ emit_output ( & fixes. first_output ) ?;
445
+ // Exit with whatever exit code we initially started with. `cargo fix`
446
+ // treats this as a warning, and shouldn't return a failure code
447
+ // unless the code didn't compile in the first place.
448
+ exit_with ( fixes. first_output . status ) ;
443
449
}
450
+ }
444
451
445
- // This final fall-through handles multiple cases;
446
- // - If the fix failed, show the original warnings and suggestions.
447
- // - If `--broken-code`, show the error messages.
448
- // - If the fix succeeded, show any remaining warnings.
449
- debug ! ( "calling rustc to display remaining diagnostics: {rustc}" ) ;
450
- exit_with ( rustc. status ( ) ?) ;
452
+ fn emit_output ( output : & Output ) -> CargoResult < ( ) > {
453
+ // Unfortunately if there is output on stdout, this does not preserve the
454
+ // order of output relative to stderr. In practice, rustc should never
455
+ // print to stdout unless some proc-macro does it.
456
+ std:: io:: stderr ( ) . write_all ( & output. stderr ) ?;
457
+ std:: io:: stdout ( ) . write_all ( & output. stdout ) ?;
458
+ Ok ( ( ) )
451
459
}
452
460
453
- #[ derive( Default ) ]
454
461
struct FixedCrate {
462
+ /// Map of file path to some information about modifications made to that file.
455
463
files : HashMap < String , FixedFile > ,
464
+ /// The output from rustc from the first time it was called.
465
+ ///
466
+ /// This is needed when fixes fail to apply, so that it can display the
467
+ /// original diagnostics to the user which can help with diagnosing which
468
+ /// suggestions caused the failure.
469
+ first_output : Output ,
470
+ /// The output from rustc from the last time it was called.
471
+ ///
472
+ /// This will be displayed to the user to show any remaining diagnostics
473
+ /// or errors.
474
+ last_output : Output ,
456
475
}
457
476
477
+ #[ derive( Debug ) ]
458
478
struct FixedFile {
459
479
errors_applying_fixes : Vec < String > ,
460
480
fixes_applied : u32 ,
@@ -472,11 +492,6 @@ fn rustfix_crate(
472
492
args : & FixArgs ,
473
493
config : & Config ,
474
494
) -> CargoResult < FixedCrate > {
475
- if !args. can_run_rustfix ( config) ? {
476
- // This fix should not be run. Skipping...
477
- return Ok ( FixedCrate :: default ( ) ) ;
478
- }
479
-
480
495
// First up, we want to make sure that each crate is only checked by one
481
496
// process at a time. If two invocations concurrently check a crate then
482
497
// it's likely to corrupt it.
@@ -488,6 +503,23 @@ fn rustfix_crate(
488
503
// modification.
489
504
let _lock = LockServerClient :: lock ( & lock_addr. parse ( ) ?, "global" ) ?;
490
505
506
+ // Map of files that have been modified.
507
+ let mut files = HashMap :: new ( ) ;
508
+
509
+ if !args. can_run_rustfix ( config) ? {
510
+ // This fix should not be run. Skipping...
511
+ // We still need to run rustc at least once to make sure any potential
512
+ // rmeta gets generated, and diagnostics get displayed.
513
+ debug ! ( "can't fix {filename:?}, running rustc: {rustc}" ) ;
514
+ let last_output = rustc. output ( ) ?;
515
+ let fixes = FixedCrate {
516
+ files,
517
+ first_output : last_output. clone ( ) ,
518
+ last_output,
519
+ } ;
520
+ return Ok ( fixes) ;
521
+ }
522
+
491
523
// Next up, this is a bit suspicious, but we *iteratively* execute rustc and
492
524
// collect suggestions to feed to rustfix. Once we hit our limit of times to
493
525
// execute rustc or we appear to be reaching a fixed point we stop running
@@ -521,41 +553,53 @@ fn rustfix_crate(
521
553
// Detect this when a fix fails to get applied *and* no suggestions
522
554
// successfully applied to the same file. In that case looks like we
523
555
// definitely can't make progress, so bail out.
524
- let mut fixes = FixedCrate :: default ( ) ;
525
- let mut last_fix_counts = HashMap :: new ( ) ;
526
- let iterations = config
556
+ let max_iterations = config
527
557
. get_env ( "CARGO_FIX_MAX_RETRIES" )
528
558
. ok ( )
529
559
. and_then ( |n| n. parse ( ) . ok ( ) )
530
560
. unwrap_or ( 4 ) ;
531
- for _ in 0 ..iterations {
532
- last_fix_counts. clear ( ) ;
533
- for ( path, file) in fixes. files . iter_mut ( ) {
534
- last_fix_counts. insert ( path. clone ( ) , file. fixes_applied ) ;
561
+ let mut last_output;
562
+ let mut last_made_changes;
563
+ let mut first_output = None ;
564
+ let mut current_iteration = 0 ;
565
+ loop {
566
+ for file in files. values_mut ( ) {
535
567
// We'll generate new errors below.
536
568
file. errors_applying_fixes . clear ( ) ;
537
569
}
538
- rustfix_and_fix ( & mut fixes, rustc, filename, config) ?;
570
+ ( last_output, last_made_changes) = rustfix_and_fix ( & mut files, rustc, filename, config) ?;
571
+ if current_iteration == 0 {
572
+ first_output = Some ( last_output. clone ( ) ) ;
573
+ }
539
574
let mut progress_yet_to_be_made = false ;
540
- for ( path, file) in fixes . files . iter_mut ( ) {
575
+ for ( path, file) in files. iter_mut ( ) {
541
576
if file. errors_applying_fixes . is_empty ( ) {
542
577
continue ;
543
578
}
579
+ debug ! ( "had rustfix apply errors in {path:?} {file:?}" ) ;
544
580
// If anything was successfully fixed *and* there's at least one
545
581
// error, then assume the error was spurious and we'll try again on
546
582
// the next iteration.
547
- if file . fixes_applied != * last_fix_counts . get ( path ) . unwrap_or ( & 0 ) {
583
+ if last_made_changes {
548
584
progress_yet_to_be_made = true ;
549
585
}
550
586
}
551
587
if !progress_yet_to_be_made {
552
588
break ;
553
589
}
590
+ current_iteration += 1 ;
591
+ if current_iteration >= max_iterations {
592
+ break ;
593
+ }
594
+ }
595
+ if last_made_changes {
596
+ debug ! ( "calling rustc one last time for final results: {rustc}" ) ;
597
+ last_output = rustc. output ( ) ?;
554
598
}
555
599
556
600
// Any errors still remaining at this point need to be reported as probably
557
601
// bugs in Cargo and/or rustfix.
558
- for ( path, file) in fixes . files . iter_mut ( ) {
602
+ for ( path, file) in files. iter_mut ( ) {
559
603
for error in file. errors_applying_fixes . drain ( ..) {
560
604
Message :: ReplaceFailed {
561
605
file : path. clone ( ) ,
@@ -565,19 +609,23 @@ fn rustfix_crate(
565
609
}
566
610
}
567
611
568
- Ok ( fixes)
612
+ Ok ( FixedCrate {
613
+ files,
614
+ first_output : first_output. expect ( "at least one iteration" ) ,
615
+ last_output,
616
+ } )
569
617
}
570
618
571
619
/// Executes `rustc` to apply one round of suggestions to the crate in question.
572
620
///
573
621
/// This will fill in the `fixes` map with original code, suggestions applied,
574
622
/// and any errors encountered while fixing files.
575
623
fn rustfix_and_fix (
576
- fixes : & mut FixedCrate ,
624
+ files : & mut HashMap < String , FixedFile > ,
577
625
rustc : & ProcessBuilder ,
578
626
filename : & Path ,
579
627
config : & Config ,
580
- ) -> CargoResult < ( ) > {
628
+ ) -> CargoResult < ( Output , bool ) > {
581
629
// If not empty, filter by these lints.
582
630
// TODO: implement a way to specify this.
583
631
let only = HashSet :: new ( ) ;
@@ -596,7 +644,7 @@ fn rustfix_and_fix(
596
644
filename,
597
645
output. status. code( )
598
646
) ;
599
- return Ok ( ( ) ) ;
647
+ return Ok ( ( output , false ) ) ;
600
648
}
601
649
602
650
let fix_mode = config
@@ -664,6 +712,7 @@ fn rustfix_and_fix(
664
712
filename. display( ) ,
665
713
) ;
666
714
715
+ let mut made_changes = false ;
667
716
for ( file, suggestions) in file_map {
668
717
// Attempt to read the source code for this file. If this fails then
669
718
// that'd be pretty surprising, so log a message and otherwise keep
@@ -682,14 +731,11 @@ fn rustfix_and_fix(
682
731
// code, so save it. If the file already exists then the original code
683
732
// doesn't need to be updated as we've just read an interim state with
684
733
// some fixes but perhaps not all.
685
- let fixed_file = fixes
686
- . files
687
- . entry ( file. clone ( ) )
688
- . or_insert_with ( || FixedFile {
689
- errors_applying_fixes : Vec :: new ( ) ,
690
- fixes_applied : 0 ,
691
- original_code : code. clone ( ) ,
692
- } ) ;
734
+ let fixed_file = files. entry ( file. clone ( ) ) . or_insert_with ( || FixedFile {
735
+ errors_applying_fixes : Vec :: new ( ) ,
736
+ fixes_applied : 0 ,
737
+ original_code : code. clone ( ) ,
738
+ } ) ;
693
739
let mut fixed = CodeFix :: new ( & code) ;
694
740
695
741
// As mentioned above in `rustfix_crate`, we don't immediately warn
@@ -701,17 +747,19 @@ fn rustfix_and_fix(
701
747
Err ( e) => fixed_file. errors_applying_fixes . push ( e. to_string ( ) ) ,
702
748
}
703
749
}
704
- let new_code = fixed. finish ( ) ?;
705
- paths:: write ( & file, new_code) ?;
750
+ if fixed. modified ( ) {
751
+ made_changes = true ;
752
+ let new_code = fixed. finish ( ) ?;
753
+ paths:: write ( & file, new_code) ?;
754
+ }
706
755
}
707
756
708
- Ok ( ( ) )
757
+ Ok ( ( output , made_changes ) )
709
758
}
710
759
711
760
fn exit_with ( status : ExitStatus ) -> ! {
712
761
#[ cfg( unix) ]
713
762
{
714
- use std:: io:: Write ;
715
763
use std:: os:: unix:: prelude:: * ;
716
764
if let Some ( signal) = status. signal ( ) {
717
765
drop ( writeln ! (
0 commit comments