@@ -80,11 +80,21 @@ impl CommandFingerprint {
80
80
/// Helper method to format both Command and BootstrapCommand as a short execution line,
81
81
/// without all the other details (e.g. environment variables).
82
82
pub fn format_short_cmd ( & self ) -> String {
83
- let program = Path :: new ( & self . program ) ;
84
- let mut line = vec ! [ program. file_name( ) . unwrap( ) . to_str( ) . unwrap( ) . to_owned( ) ] ;
85
- line. extend ( self . args . iter ( ) . map ( |arg| arg. to_string_lossy ( ) . into_owned ( ) ) ) ;
86
- line. extend ( self . cwd . iter ( ) . map ( |p| p. to_string_lossy ( ) . into_owned ( ) ) ) ;
87
- line. join ( " " )
83
+ use std:: fmt:: Write ;
84
+
85
+ let mut cmd = self . program . to_string_lossy ( ) . to_string ( ) ;
86
+ for arg in & self . args {
87
+ let arg = arg. to_string_lossy ( ) ;
88
+ if arg. contains ( ' ' ) {
89
+ write ! ( cmd, " '{arg}'" ) . unwrap ( ) ;
90
+ } else {
91
+ write ! ( cmd, " {arg}" ) . unwrap ( ) ;
92
+ }
93
+ }
94
+ if let Some ( cwd) = & self . cwd {
95
+ write ! ( cmd, " [workdir={}]" , cwd. to_string_lossy( ) ) . unwrap ( ) ;
96
+ }
97
+ cmd
88
98
}
89
99
}
90
100
@@ -434,8 +444,8 @@ impl From<Command> for BootstrapCommand {
434
444
enum CommandStatus {
435
445
/// The command has started and finished with some status.
436
446
Finished ( ExitStatus ) ,
437
- /// It was not even possible to start the command.
438
- DidNotStart ,
447
+ /// It was not even possible to start the command or wait for it to finish .
448
+ DidNotStartOrFinish ,
439
449
}
440
450
441
451
/// Create a new BootstrapCommand. This is a helper function to make command creation
@@ -456,9 +466,9 @@ pub struct CommandOutput {
456
466
457
467
impl CommandOutput {
458
468
#[ must_use]
459
- pub fn did_not_start ( stdout : OutputMode , stderr : OutputMode ) -> Self {
469
+ pub fn not_finished ( stdout : OutputMode , stderr : OutputMode ) -> Self {
460
470
Self {
461
- status : CommandStatus :: DidNotStart ,
471
+ status : CommandStatus :: DidNotStartOrFinish ,
462
472
stdout : match stdout {
463
473
OutputMode :: Print => None ,
464
474
OutputMode :: Capture => Some ( vec ! [ ] ) ,
@@ -489,7 +499,7 @@ impl CommandOutput {
489
499
pub fn is_success ( & self ) -> bool {
490
500
match self . status {
491
501
CommandStatus :: Finished ( status) => status. success ( ) ,
492
- CommandStatus :: DidNotStart => false ,
502
+ CommandStatus :: DidNotStartOrFinish => false ,
493
503
}
494
504
}
495
505
@@ -501,7 +511,7 @@ impl CommandOutput {
501
511
pub fn status ( & self ) -> Option < ExitStatus > {
502
512
match self . status {
503
513
CommandStatus :: Finished ( status) => Some ( status) ,
504
- CommandStatus :: DidNotStart => None ,
514
+ CommandStatus :: DidNotStartOrFinish => None ,
505
515
}
506
516
}
507
517
@@ -745,25 +755,11 @@ impl ExecutionContext {
745
755
self . start ( command, stdout, stderr) . wait_for_output ( self )
746
756
}
747
757
748
- fn fail ( & self , message : & str , output : CommandOutput ) -> ! {
749
- if self . is_verbose ( ) {
750
- println ! ( "{message}" ) ;
751
- } else {
752
- let ( stdout, stderr) = ( output. stdout_if_present ( ) , output. stderr_if_present ( ) ) ;
753
- // If the command captures output, the user would not see any indication that
754
- // it has failed. In this case, print a more verbose error, since to provide more
755
- // context.
756
- if stdout. is_some ( ) || stderr. is_some ( ) {
757
- if let Some ( stdout) = output. stdout_if_present ( ) . take_if ( |s| !s. trim ( ) . is_empty ( ) ) {
758
- println ! ( "STDOUT:\n {stdout}\n " ) ;
759
- }
760
- if let Some ( stderr) = output. stderr_if_present ( ) . take_if ( |s| !s. trim ( ) . is_empty ( ) ) {
761
- println ! ( "STDERR:\n {stderr}\n " ) ;
762
- }
763
- println ! ( "Command has failed. Rerun with -v to see more details." ) ;
764
- } else {
765
- println ! ( "Command has failed. Rerun with -v to see more details." ) ;
766
- }
758
+ fn fail ( & self , message : & str ) -> ! {
759
+ println ! ( "{message}" ) ;
760
+
761
+ if !self . is_verbose ( ) {
762
+ println ! ( "Command has failed. Rerun with -v to see more details." ) ;
767
763
}
768
764
exit ! ( 1 ) ;
769
765
}
@@ -856,7 +852,7 @@ impl<'a> DeferredCommand<'a> {
856
852
&& command. should_cache
857
853
{
858
854
exec_ctx. command_cache . insert ( fingerprint. clone ( ) , output. clone ( ) ) ;
859
- exec_ctx. profiler . record_execution ( fingerprint. clone ( ) , start_time) ;
855
+ exec_ctx. profiler . record_execution ( fingerprint, start_time) ;
860
856
}
861
857
862
858
output
@@ -872,6 +868,8 @@ impl<'a> DeferredCommand<'a> {
872
868
executed_at : & ' a std:: panic:: Location < ' a > ,
873
869
exec_ctx : & ExecutionContext ,
874
870
) -> CommandOutput {
871
+ use std:: fmt:: Write ;
872
+
875
873
command. mark_as_executed ( ) ;
876
874
877
875
let process = match process. take ( ) {
@@ -881,79 +879,82 @@ impl<'a> DeferredCommand<'a> {
881
879
882
880
let created_at = command. get_created_location ( ) ;
883
881
884
- let mut message = String :: new ( ) ;
882
+ #[ allow( clippy:: enum_variant_names) ]
883
+ enum FailureReason {
884
+ FailedAtRuntime ( ExitStatus ) ,
885
+ FailedToFinish ( std:: io:: Error ) ,
886
+ FailedToStart ( std:: io:: Error ) ,
887
+ }
885
888
886
- let output = match process {
889
+ let ( output, fail_reason ) = match process {
887
890
Ok ( child) => match child. wait_with_output ( ) {
888
- Ok ( result ) if result . status . success ( ) => {
891
+ Ok ( output ) if output . status . success ( ) => {
889
892
// Successful execution
890
- CommandOutput :: from_output ( result , stdout, stderr)
893
+ ( CommandOutput :: from_output ( output , stdout, stderr) , None )
891
894
}
892
- Ok ( result) => {
893
- // Command ran but failed
894
- use std:: fmt:: Write ;
895
-
896
- writeln ! (
897
- message,
898
- r#"
899
- Command {command:?} did not execute successfully.
900
- Expected success, got {}
901
- Created at: {created_at}
902
- Executed at: {executed_at}"# ,
903
- result. status,
895
+ Ok ( output) => {
896
+ // Command started, but then it failed
897
+ let status = output. status ;
898
+ (
899
+ CommandOutput :: from_output ( output, stdout, stderr) ,
900
+ Some ( FailureReason :: FailedAtRuntime ( status) ) ,
904
901
)
905
- . unwrap ( ) ;
906
-
907
- let output = CommandOutput :: from_output ( result, stdout, stderr) ;
908
-
909
- if stdout. captures ( ) {
910
- writeln ! ( message, "\n STDOUT ----\n {}" , output. stdout( ) . trim( ) ) . unwrap ( ) ;
911
- }
912
- if stderr. captures ( ) {
913
- writeln ! ( message, "\n STDERR ----\n {}" , output. stderr( ) . trim( ) ) . unwrap ( ) ;
914
- }
915
-
916
- output
917
902
}
918
903
Err ( e) => {
919
904
// Failed to wait for output
920
- use std:: fmt:: Write ;
921
-
922
- writeln ! (
923
- message,
924
- "\n \n Command {command:?} did not execute successfully.\
925
- \n It was not possible to execute the command: {e:?}"
905
+ (
906
+ CommandOutput :: not_finished ( stdout, stderr) ,
907
+ Some ( FailureReason :: FailedToFinish ( e) ) ,
926
908
)
927
- . unwrap ( ) ;
928
-
929
- CommandOutput :: did_not_start ( stdout, stderr)
930
909
}
931
910
} ,
932
911
Err ( e) => {
933
912
// Failed to spawn the command
934
- use std:: fmt:: Write ;
935
-
936
- writeln ! (
937
- message,
938
- "\n \n Command {command:?} did not execute successfully.\
939
- \n It was not possible to execute the command: {e:?}"
940
- )
941
- . unwrap ( ) ;
942
-
943
- CommandOutput :: did_not_start ( stdout, stderr)
913
+ ( CommandOutput :: not_finished ( stdout, stderr) , Some ( FailureReason :: FailedToStart ( e) ) )
944
914
}
945
915
} ;
946
916
947
- if !output. is_success ( ) {
917
+ if let Some ( fail_reason) = fail_reason {
918
+ let mut error_message = String :: new ( ) ;
919
+ let command_str = if exec_ctx. is_verbose ( ) {
920
+ format ! ( "{command:?}" )
921
+ } else {
922
+ command. fingerprint ( ) . format_short_cmd ( )
923
+ } ;
924
+ let action = match fail_reason {
925
+ FailureReason :: FailedAtRuntime ( e) => {
926
+ format ! ( "failed with exit code {}" , e. code( ) . unwrap_or( 1 ) )
927
+ }
928
+ FailureReason :: FailedToFinish ( e) => {
929
+ format ! ( "failed to finish: {e:?}" )
930
+ }
931
+ FailureReason :: FailedToStart ( e) => {
932
+ format ! ( "failed to start: {e:?}" )
933
+ }
934
+ } ;
935
+ writeln ! (
936
+ error_message,
937
+ r#"Command `{command_str}` {action}
938
+ Created at: {created_at}
939
+ Executed at: {executed_at}"# ,
940
+ )
941
+ . unwrap ( ) ;
942
+ if stdout. captures ( ) {
943
+ writeln ! ( error_message, "\n --- STDOUT vvv\n {}" , output. stdout( ) . trim( ) ) . unwrap ( ) ;
944
+ }
945
+ if stderr. captures ( ) {
946
+ writeln ! ( error_message, "\n --- STDERR vvv\n {}" , output. stderr( ) . trim( ) ) . unwrap ( ) ;
947
+ }
948
+
948
949
match command. failure_behavior {
949
950
BehaviorOnFailure :: DelayFail => {
950
951
if exec_ctx. fail_fast {
951
- exec_ctx. fail ( & message , output ) ;
952
+ exec_ctx. fail ( & error_message ) ;
952
953
}
953
- exec_ctx. add_to_delay_failure ( message ) ;
954
+ exec_ctx. add_to_delay_failure ( error_message ) ;
954
955
}
955
956
BehaviorOnFailure :: Exit => {
956
- exec_ctx. fail ( & message , output ) ;
957
+ exec_ctx. fail ( & error_message ) ;
957
958
}
958
959
BehaviorOnFailure :: Ignore => {
959
960
// If failures are allowed, either the error has been printed already
0 commit comments