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::ffi::{OsStr, OsString};
use std::fs;
use std::path::PathBuf;
use std::process::Command;
#[cfg(not(feature = "bootstrap-self-test"))]
use crate::builder::Builder;
@ -25,7 +24,6 @@
use crate::builder::Kind;
use crate::core::config::Target;
use crate::utils::exec::BootstrapCommand;
use crate::utils::helpers::output;
use crate::Build;
pub struct Finder {
@ -210,11 +208,14 @@ pub fn check(build: &mut Build) {
.or_else(|| cmd_finder.maybe_have("reuse"));
#[cfg(not(feature = "bootstrap-self-test"))]
let stage0_supported_target_list: HashSet<String> =
output(Command::new(&build.config.initial_rustc).args(["--print", "target-list"]))
.lines()
.map(|s| s.to_string())
.collect();
let stage0_supported_target_list: HashSet<String> = crate::utils::helpers::output(
&mut BootstrapCommand::new(&build.config.initial_rustc)
.args(["--print", "target-list"])
.command,
)
.lines()
.map(|s| s.to_string())
.collect();
// 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.

View File

@ -23,14 +23,13 @@
use std::fs::{self, File};
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::process::{Command, Output, Stdio};
use std::str;
use std::sync::OnceLock;
use std::time::SystemTime;
use build_helper::ci::{gha, CiEnv};
use build_helper::exit;
use build_helper::util::fail;
use filetime::FileTime;
use sha2::digest::Digest;
use termcolor::{ColorChoice, StandardStream, WriteColor};
@ -945,43 +944,61 @@ fn run<C: AsMut<BootstrapCommand>>(&self, mut command: C) -> CommandOutput {
self.verbose(|| println!("running: {command:?}"));
let output: io::Result<CommandOutput> = match command.output_mode {
OutputMode::Print => command.command.status().map(|status| status.into()),
OutputMode::CaptureAll => command.command.output().map(|o| o.into()),
let output: io::Result<Output> = match command.output_mode {
OutputMode::Print => command.command.status().map(|status| Output {
status,
stdout: vec![],
stderr: vec![],
}),
OutputMode::CaptureAll => command.command.output(),
OutputMode::CaptureStdout => {
command.command.stderr(Stdio::inherit());
command.command.output().map(|o| o.into())
command.command.output()
}
};
let output = match output {
Ok(output) => output,
Err(e) => fail(&format!("failed to execute command: {command:?}\nerror: {e}")),
use std::fmt::Write;
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() {
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 {
BehaviorOnFailure::DelayFail => {
if self.fail_fast {

View File

@ -132,25 +132,47 @@ fn from(command: Command) -> Self {
}
}
/// 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.
#[allow(unused)]
pub struct CommandOutput(Output);
pub struct CommandOutput {
outcome: CommandOutcome,
stdout: Vec<u8>,
stderr: Vec<u8>,
}
impl CommandOutput {
pub fn did_not_start() -> Self {
Self { outcome: CommandOutcome::DidNotStart, stdout: vec![], stderr: vec![] }
}
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 {
!self.is_success()
}
pub fn status(&self) -> ExitStatus {
self.0.status
pub fn status(&self) -> Option<ExitStatus> {
match self.outcome {
CommandOutcome::Finished(status) => Some(status),
CommandOutcome::DidNotStart => None,
}
}
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> {
@ -158,24 +180,26 @@ pub fn stdout_if_ok(&self) -> Option<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 {
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 {
fn from(output: Output) -> Self {
Self(output)
}
}
impl From<ExitStatus> for CommandOutput {
fn from(status: ExitStatus) -> Self {
Self(Output { status, stdout: vec![], stderr: vec![] })
Self {
outcome: CommandOutcome::Finished(output.status),
stdout: output.stdout,
stderr: output.stderr,
}
}
}