Skip to content

Commit ed84282

Browse files
authored
Rollup merge of rust-lang#145089 - Kobzol:bootstrap-cmd-error, r=jieyouxu
Improve error output when a command fails in bootstrap I fixed this because it was being an issue for debugging CI failures. We try to print as much information as possible, just with a slightly less verbose command description in non-verbose mode. The code is now more unified and hopefully simpler to understand. I also fixed the `format_short_cmd` logic, it was a bit weird after some recent refactors. Fixes: rust-lang#145002 r? ``````@jieyouxu`````` CC ``````@Shourya742``````
2 parents d9c745c + d9b725a commit ed84282

File tree

1 file changed

+83
-82
lines changed

1 file changed

+83
-82
lines changed

src/bootstrap/src/utils/exec.rs

Lines changed: 83 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,21 @@ impl CommandFingerprint {
8080
/// Helper method to format both Command and BootstrapCommand as a short execution line,
8181
/// without all the other details (e.g. environment variables).
8282
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
8898
}
8999
}
90100

@@ -434,8 +444,8 @@ impl From<Command> for BootstrapCommand {
434444
enum CommandStatus {
435445
/// The command has started and finished with some status.
436446
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,
439449
}
440450

441451
/// Create a new BootstrapCommand. This is a helper function to make command creation
@@ -456,9 +466,9 @@ pub struct CommandOutput {
456466

457467
impl CommandOutput {
458468
#[must_use]
459-
pub fn did_not_start(stdout: OutputMode, stderr: OutputMode) -> Self {
469+
pub fn not_finished(stdout: OutputMode, stderr: OutputMode) -> Self {
460470
Self {
461-
status: CommandStatus::DidNotStart,
471+
status: CommandStatus::DidNotStartOrFinish,
462472
stdout: match stdout {
463473
OutputMode::Print => None,
464474
OutputMode::Capture => Some(vec![]),
@@ -489,7 +499,7 @@ impl CommandOutput {
489499
pub fn is_success(&self) -> bool {
490500
match self.status {
491501
CommandStatus::Finished(status) => status.success(),
492-
CommandStatus::DidNotStart => false,
502+
CommandStatus::DidNotStartOrFinish => false,
493503
}
494504
}
495505

@@ -501,7 +511,7 @@ impl CommandOutput {
501511
pub fn status(&self) -> Option<ExitStatus> {
502512
match self.status {
503513
CommandStatus::Finished(status) => Some(status),
504-
CommandStatus::DidNotStart => None,
514+
CommandStatus::DidNotStartOrFinish => None,
505515
}
506516
}
507517

@@ -745,25 +755,11 @@ impl ExecutionContext {
745755
self.start(command, stdout, stderr).wait_for_output(self)
746756
}
747757

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.");
767763
}
768764
exit!(1);
769765
}
@@ -856,7 +852,7 @@ impl<'a> DeferredCommand<'a> {
856852
&& command.should_cache
857853
{
858854
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);
860856
}
861857

862858
output
@@ -872,6 +868,8 @@ impl<'a> DeferredCommand<'a> {
872868
executed_at: &'a std::panic::Location<'a>,
873869
exec_ctx: &ExecutionContext,
874870
) -> CommandOutput {
871+
use std::fmt::Write;
872+
875873
command.mark_as_executed();
876874

877875
let process = match process.take() {
@@ -881,79 +879,82 @@ impl<'a> DeferredCommand<'a> {
881879

882880
let created_at = command.get_created_location();
883881

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+
}
885888

886-
let output = match process {
889+
let (output, fail_reason) = match process {
887890
Ok(child) => match child.wait_with_output() {
888-
Ok(result) if result.status.success() => {
891+
Ok(output) if output.status.success() => {
889892
// Successful execution
890-
CommandOutput::from_output(result, stdout, stderr)
893+
(CommandOutput::from_output(output, stdout, stderr), None)
891894
}
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)),
904901
)
905-
.unwrap();
906-
907-
let output = CommandOutput::from_output(result, stdout, stderr);
908-
909-
if stdout.captures() {
910-
writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap();
911-
}
912-
if stderr.captures() {
913-
writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap();
914-
}
915-
916-
output
917902
}
918903
Err(e) => {
919904
// Failed to wait for output
920-
use std::fmt::Write;
921-
922-
writeln!(
923-
message,
924-
"\n\nCommand {command:?} did not execute successfully.\
925-
\nIt was not possible to execute the command: {e:?}"
905+
(
906+
CommandOutput::not_finished(stdout, stderr),
907+
Some(FailureReason::FailedToFinish(e)),
926908
)
927-
.unwrap();
928-
929-
CommandOutput::did_not_start(stdout, stderr)
930909
}
931910
},
932911
Err(e) => {
933912
// Failed to spawn the command
934-
use std::fmt::Write;
935-
936-
writeln!(
937-
message,
938-
"\n\nCommand {command:?} did not execute successfully.\
939-
\nIt 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)))
944914
}
945915
};
946916

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+
948949
match command.failure_behavior {
949950
BehaviorOnFailure::DelayFail => {
950951
if exec_ctx.fail_fast {
951-
exec_ctx.fail(&message, output);
952+
exec_ctx.fail(&error_message);
952953
}
953-
exec_ctx.add_to_delay_failure(message);
954+
exec_ctx.add_to_delay_failure(error_message);
954955
}
955956
BehaviorOnFailure::Exit => {
956-
exec_ctx.fail(&message, output);
957+
exec_ctx.fail(&error_message);
957958
}
958959
BehaviorOnFailure::Ignore => {
959960
// If failures are allowed, either the error has been printed already

0 commit comments

Comments
 (0)