Refactor command outcome handling

To handle the case of failing to start a `BootstrapCommand`.
This commit is contained in:
Jakub Beránek 2024-06-29 16:00:23 +02:00
parent e8c8860142
commit 60c20bfe0c
3 changed files with 95 additions and 53 deletions

View File

@ -13,7 +13,6 @@ use std::env;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command;
#[cfg(not(feature = "bootstrap-self-test"))] #[cfg(not(feature = "bootstrap-self-test"))]
use crate::builder::Builder; use crate::builder::Builder;
@ -25,7 +24,6 @@ use std::collections::HashSet;
use crate::builder::Kind; use crate::builder::Kind;
use crate::core::config::Target; use crate::core::config::Target;
use crate::utils::exec::BootstrapCommand; use crate::utils::exec::BootstrapCommand;
use crate::utils::helpers::output;
use crate::Build; use crate::Build;
pub struct Finder { pub struct Finder {
@ -210,11 +208,14 @@ than building it.
.or_else(|| cmd_finder.maybe_have("reuse")); .or_else(|| cmd_finder.maybe_have("reuse"));
#[cfg(not(feature = "bootstrap-self-test"))] #[cfg(not(feature = "bootstrap-self-test"))]
let stage0_supported_target_list: HashSet<String> = let stage0_supported_target_list: HashSet<String> = crate::utils::helpers::output(
output(Command::new(&build.config.initial_rustc).args(["--print", "target-list"])) &mut BootstrapCommand::new(&build.config.initial_rustc)
.lines() .args(["--print", "target-list"])
.map(|s| s.to_string()) .command,
.collect(); )
.lines()
.map(|s| s.to_string())
.collect();
// We're gonna build some custom C code here and there, host triples // We're gonna build some custom C code here and there, host triples
// also build some C++ shims for LLVM so we need a C++ compiler. // also build some C++ shims for LLVM so we need a C++ compiler.

View File

@ -23,14 +23,13 @@ use std::fmt::Display;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, Stdio}; use std::process::{Command, Output, Stdio};
use std::str; use std::str;
use std::sync::OnceLock; use std::sync::OnceLock;
use std::time::SystemTime; use std::time::SystemTime;
use build_helper::ci::{gha, CiEnv}; use build_helper::ci::{gha, CiEnv};
use build_helper::exit; use build_helper::exit;
use build_helper::util::fail;
use filetime::FileTime; use filetime::FileTime;
use sha2::digest::Digest; use sha2::digest::Digest;
use termcolor::{ColorChoice, StandardStream, WriteColor}; use termcolor::{ColorChoice, StandardStream, WriteColor};
@ -945,43 +944,61 @@ impl Build {
self.verbose(|| println!("running: {command:?}")); self.verbose(|| println!("running: {command:?}"));
let output: io::Result<CommandOutput> = match command.output_mode { let output: io::Result<Output> = match command.output_mode {
OutputMode::Print => command.command.status().map(|status| status.into()), OutputMode::Print => command.command.status().map(|status| Output {
OutputMode::CaptureAll => command.command.output().map(|o| o.into()), status,
stdout: vec![],
stderr: vec![],
}),
OutputMode::CaptureAll => command.command.output(),
OutputMode::CaptureStdout => { OutputMode::CaptureStdout => {
command.command.stderr(Stdio::inherit()); command.command.stderr(Stdio::inherit());
command.command.output().map(|o| o.into()) command.command.output()
} }
}; };
let output = match output { use std::fmt::Write;
Ok(output) => output,
Err(e) => fail(&format!("failed to execute command: {command:?}\nerror: {e}")), let mut message = String::new();
let output: CommandOutput = match output {
// Command has succeeded
Ok(output) if output.status.success() => output.into(),
// Command has started, but then it failed
Ok(output) => {
writeln!(
message,
"\n\nCommand {command:?} did not execute successfully.\
\nExpected success, got: {}",
output.status,
)
.unwrap();
let output: CommandOutput = output.into();
// If the output mode is OutputMode::Print, the output has already been printed to
// stdout/stderr, and we thus don't have anything captured to print anyway.
if matches!(command.output_mode, OutputMode::CaptureAll | OutputMode::CaptureStdout)
{
writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap();
// Stderr is added to the message only if it was captured
if matches!(command.output_mode, OutputMode::CaptureAll) {
writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap();
}
}
output
}
// The command did not even start
Err(e) => {
writeln!(
message,
"\n\nCommand {command:?} did not execute successfully.\
\nIt was not possible to execute the command: {e:?}"
)
.unwrap();
CommandOutput::did_not_start()
}
}; };
if !output.is_success() { if !output.is_success() {
use std::fmt::Write;
// Here we build an error message, and below we decide if it should be printed or not.
let mut message = String::new();
writeln!(
message,
"\n\nCommand {command:?} did not execute successfully.\
\nExpected success, got: {}",
output.status(),
)
.unwrap();
// If the output mode is OutputMode::Print, the output has already been printed to
// stdout/stderr, and we thus don't have anything captured to print anyway.
if matches!(command.output_mode, OutputMode::CaptureAll | OutputMode::CaptureStdout) {
writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap();
// Stderr is added to the message only if it was captured
if matches!(command.output_mode, OutputMode::CaptureAll) {
writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap();
}
}
match command.failure_behavior { match command.failure_behavior {
BehaviorOnFailure::DelayFail => { BehaviorOnFailure::DelayFail => {
if self.fail_fast { if self.fail_fast {

View File

@ -132,25 +132,47 @@ impl From<Command> for BootstrapCommand {
} }
} }
/// Represents the outcome of starting a command.
enum CommandOutcome {
/// The command has started and finished with some status.
Finished(ExitStatus),
/// It was not even possible to start the command.
DidNotStart,
}
/// Represents the output of an executed process. /// Represents the output of an executed process.
#[allow(unused)] #[allow(unused)]
pub struct CommandOutput(Output); pub struct CommandOutput {
outcome: CommandOutcome,
stdout: Vec<u8>,
stderr: Vec<u8>,
}
impl CommandOutput { impl CommandOutput {
pub fn did_not_start() -> Self {
Self { outcome: CommandOutcome::DidNotStart, stdout: vec![], stderr: vec![] }
}
pub fn is_success(&self) -> bool { pub fn is_success(&self) -> bool {
self.0.status.success() match self.outcome {
CommandOutcome::Finished(status) => status.success(),
CommandOutcome::DidNotStart => false,
}
} }
pub fn is_failure(&self) -> bool { pub fn is_failure(&self) -> bool {
!self.is_success() !self.is_success()
} }
pub fn status(&self) -> ExitStatus { pub fn status(&self) -> Option<ExitStatus> {
self.0.status match self.outcome {
CommandOutcome::Finished(status) => Some(status),
CommandOutcome::DidNotStart => None,
}
} }
pub fn stdout(&self) -> String { pub fn stdout(&self) -> String {
String::from_utf8(self.0.stdout.clone()).expect("Cannot parse process stdout as UTF-8") String::from_utf8(self.stdout.clone()).expect("Cannot parse process stdout as UTF-8")
} }
pub fn stdout_if_ok(&self) -> Option<String> { pub fn stdout_if_ok(&self) -> Option<String> {
@ -158,24 +180,26 @@ impl CommandOutput {
} }
pub fn stderr(&self) -> String { pub fn stderr(&self) -> String {
String::from_utf8(self.0.stderr.clone()).expect("Cannot parse process stderr as UTF-8") String::from_utf8(self.stderr.clone()).expect("Cannot parse process stderr as UTF-8")
} }
} }
impl Default for CommandOutput { impl Default for CommandOutput {
fn default() -> Self { fn default() -> Self {
Self(Output { status: Default::default(), stdout: vec![], stderr: vec![] }) Self {
outcome: CommandOutcome::Finished(ExitStatus::default()),
stdout: vec![],
stderr: vec![],
}
} }
} }
impl From<Output> for CommandOutput { impl From<Output> for CommandOutput {
fn from(output: Output) -> Self { fn from(output: Output) -> Self {
Self(output) Self {
} outcome: CommandOutcome::Finished(output.status),
} stdout: output.stdout,
stderr: output.stderr,
impl From<ExitStatus> for CommandOutput { }
fn from(status: ExitStatus) -> Self {
Self(Output { status, stdout: vec![], stderr: vec![] })
} }
} }