Skip to content

Commit

Permalink
Auto merge of rust-lang#116581 - Kobzol:bootstrap-cmd-run, r=onur-ozkan
Browse files Browse the repository at this point in the history
Centralize command running in boostrap (part one)

This PR tries to consolidate the various `run, try_run, run_quiet, run_quiet_delaying_failure, run_delaying_failure` etc. methods on `Builder`. This PR only touches command execution which doesn't produce output that would be later read by bootstrap, and it also only refactors spawning of commands that happens after a builder is created (commands executed during download & git submodule checkout are left as-is, for now).

The `run_cmd` method is quite meaty, but I expect that it will be changing rapidly soon, so I considered it easy to kept everything in a single method, and only after things settle down a bit, then maybe again split it up a bit.

I still kept the original shortcut methods like `run_quiet_delaying_failure`, but they now only delegate to `run_cmd`. I tried to keep the original behavior (or as close to it as possible) for all the various commands, but it is a giant mess, so there may be some deviations. Notably, `cmd.output()` is now always called, instead of just `status()`, which was called previously in some situations.

Apart from the refactored methods, there is also `Config::try_run`, `check_run`, methods that run commands that produce output, oh my… that's left for follow-up PRs :)

The driving goal of this (and following) refactors is to centralize command execution in bootstrap on a single place, to make command mocking feasible.

r? `@onur-ozkan`
  • Loading branch information
bors committed Oct 12, 2023
2 parents 3ff244b + 3bb226f commit 14f0550
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 91 deletions.
52 changes: 52 additions & 0 deletions src/bootstrap/exec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::process::Command;

/// What should be done when the command fails.
#[derive(Debug, Copy, Clone)]
pub enum BehaviorOnFailure {
/// Immediately stop bootstrap.
Exit,
/// Delay failure until the end of bootstrap invocation.
DelayFail,
}

/// How should the output of the command be handled.
#[derive(Debug, Copy, Clone)]
pub enum OutputMode {
/// Print both the output (by inheriting stdout/stderr) and also the command itself, if it
/// fails.
PrintAll,
/// Print the output (by inheriting stdout/stderr).
PrintOutput,
/// Suppress the output if the command succeeds, otherwise print the output.
SuppressOnSuccess,
}

/// Wrapper around `std::process::Command`.
#[derive(Debug)]
pub struct BootstrapCommand<'a> {
pub command: &'a mut Command,
pub failure_behavior: BehaviorOnFailure,
pub output_mode: OutputMode,
}

impl<'a> BootstrapCommand<'a> {
pub fn delay_failure(self) -> Self {
Self { failure_behavior: BehaviorOnFailure::DelayFail, ..self }
}
pub fn fail_fast(self) -> Self {
Self { failure_behavior: BehaviorOnFailure::Exit, ..self }
}
pub fn output_mode(self, output_mode: OutputMode) -> Self {
Self { output_mode, ..self }
}
}

impl<'a> From<&'a mut Command> for BootstrapCommand<'a> {
fn from(command: &'a mut Command) -> Self {
Self {
command,
failure_behavior: BehaviorOnFailure::Exit,
output_mode: OutputMode::SuppressOnSuccess,
}
}
}
150 changes: 98 additions & 52 deletions src/bootstrap/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,28 @@ use std::fmt::Display;
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 build_helper::ci::{gha, CiEnv};
use build_helper::exit;
use channel::GitInfo;
use config::{DryRun, Target};
use build_helper::util::fail;
use filetime::FileTime;
use once_cell::sync::OnceCell;
use termcolor::{ColorChoice, StandardStream, WriteColor};

use channel::GitInfo;
use config::{DryRun, Target};

use crate::builder::Kind;
use crate::cache::{Interned, INTERNER};
use crate::config::{LlvmLibunwind, TargetSelection};
use crate::util::{
dir_is_empty, exe, libdir, mtime, output, run, run_suppressed, symlink_dir, try_run_suppressed,
};
use crate::exec::{BehaviorOnFailure, BootstrapCommand, OutputMode};
use crate::util::{dir_is_empty, exe, libdir, mtime, output, symlink_dir};

pub use crate::builder::PathSet;
pub use crate::config::Config;
pub use crate::flags::Subcommand;

mod builder;
mod cache;
Expand All @@ -50,6 +57,7 @@ mod config;
mod dist;
mod doc;
mod download;
mod exec;
mod flags;
mod format;
mod install;
Expand Down Expand Up @@ -87,12 +95,6 @@ mod job {
pub unsafe fn setup(_build: &mut crate::Build) {}
}

pub use crate::builder::PathSet;
use crate::cache::{Interned, INTERNER};
pub use crate::config::Config;
pub use crate::flags::Subcommand;
use termcolor::{ColorChoice, StandardStream, WriteColor};

const LLVM_TOOLS: &[&str] = &[
"llvm-cov", // used to generate coverage report
"llvm-nm", // used to inspect binaries; it shows symbol names, their sizes and visibility
Expand Down Expand Up @@ -628,15 +630,12 @@ impl Build {
}

// Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
#[allow(deprecated)] // diff-index reports the modifications through the exit status
let has_local_modifications = self
.config
.try_run(
Command::new("git")
.args(&["diff-index", "--quiet", "HEAD"])
.current_dir(&absolute_path),
)
.is_err();
// diff-index reports the modifications through the exit status
let has_local_modifications = !self.run_cmd(
Command::new("git")
.args(&["diff-index", "--quiet", "HEAD"])
.current_dir(&absolute_path),
);
if has_local_modifications {
self.run(Command::new("git").args(&["stash", "push"]).current_dir(&absolute_path));
}
Expand Down Expand Up @@ -965,55 +964,102 @@ impl Build {

/// Runs a command, printing out nice contextual information if it fails.
fn run(&self, cmd: &mut Command) {
if self.config.dry_run() {
return;
}
self.verbose(&format!("running: {cmd:?}"));
run(cmd, self.is_verbose())
self.run_cmd(BootstrapCommand::from(cmd).fail_fast().output_mode(
match self.is_verbose() {
true => OutputMode::PrintAll,
false => OutputMode::PrintOutput,
},
));
}

/// Runs a command, printing out contextual info if it fails, and delaying errors until the build finishes.
pub(crate) fn run_delaying_failure(&self, cmd: &mut Command) -> bool {
self.run_cmd(BootstrapCommand::from(cmd).delay_failure().output_mode(
match self.is_verbose() {
true => OutputMode::PrintAll,
false => OutputMode::PrintOutput,
},
))
}

/// Runs a command, printing out nice contextual information if it fails.
fn run_quiet(&self, cmd: &mut Command) {
if self.config.dry_run() {
return;
}
self.verbose(&format!("running: {cmd:?}"));
run_suppressed(cmd)
self.run_cmd(
BootstrapCommand::from(cmd).fail_fast().output_mode(OutputMode::SuppressOnSuccess),
);
}

/// Runs a command, printing out nice contextual information if it fails.
/// Exits if the command failed to execute at all, otherwise returns its
/// `status.success()`.
fn run_quiet_delaying_failure(&self, cmd: &mut Command) -> bool {
self.run_cmd(
BootstrapCommand::from(cmd).delay_failure().output_mode(OutputMode::SuppressOnSuccess),
)
}

/// A centralized function for running commands that do not return output.
pub(crate) fn run_cmd<'a, C: Into<BootstrapCommand<'a>>>(&self, cmd: C) -> bool {
if self.config.dry_run() {
return true;
}
if !self.fail_fast {
self.verbose(&format!("running: {cmd:?}"));
if !try_run_suppressed(cmd) {
let mut failures = self.delayed_failures.borrow_mut();
failures.push(format!("{cmd:?}"));
return false;

let command = cmd.into();
self.verbose(&format!("running: {command:?}"));

let (output, print_error) = match command.output_mode {
mode @ (OutputMode::PrintAll | OutputMode::PrintOutput) => (
command.command.status().map(|status| Output {
status,
stdout: Vec::new(),
stderr: Vec::new(),
}),
matches!(mode, OutputMode::PrintAll),
),
OutputMode::SuppressOnSuccess => (command.command.output(), true),
};

let output = match output {
Ok(output) => output,
Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", command, e)),
};
let result = if !output.status.success() {
if print_error {
println!(
"\n\ncommand did not execute successfully: {:?}\n\
expected success, got: {}\n\n\
stdout ----\n{}\n\
stderr ----\n{}\n\n",
command.command,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
Err(())
} else {
self.run_quiet(cmd);
}
true
}
Ok(())
};

/// Runs a command, printing out contextual info if it fails, and delaying errors until the build finishes.
pub(crate) fn run_delaying_failure(&self, cmd: &mut Command) -> bool {
if !self.fail_fast {
#[allow(deprecated)] // can't use Build::try_run, that's us
if self.config.try_run(cmd).is_err() {
let mut failures = self.delayed_failures.borrow_mut();
failures.push(format!("{cmd:?}"));
return false;
match result {
Ok(_) => true,
Err(_) => {
match command.failure_behavior {
BehaviorOnFailure::DelayFail => {
if self.fail_fast {
exit!(1);
}

let mut failures = self.delayed_failures.borrow_mut();
failures.push(format!("{command:?}"));
}
BehaviorOnFailure::Exit => {
exit!(1);
}
}
false
}
} else {
self.run(cmd);
}
true
}

pub fn is_verbose_than(&self, level: usize) -> bool {
Expand Down
9 changes: 5 additions & 4 deletions src/bootstrap/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::compile;
use crate::config::TargetSelection;
use crate::dist;
use crate::doc::DocumentationFormat;
use crate::exec::BootstrapCommand;
use crate::flags::Subcommand;
use crate::llvm;
use crate::render_tests::add_flags_and_try_run_tests;
Expand Down Expand Up @@ -801,8 +802,8 @@ impl Step for Clippy {

let _guard = builder.msg_sysroot_tool(Kind::Test, compiler.stage, "clippy", host, host);

#[allow(deprecated)] // Clippy reports errors if it blessed the outputs
if builder.config.try_run(&mut cargo).is_ok() {
// Clippy reports errors if it blessed the outputs
if builder.run_cmd(&mut cargo) {
// The tests succeeded; nothing to do.
return;
}
Expand Down Expand Up @@ -3087,7 +3088,7 @@ impl Step for CodegenCranelift {
.arg("testsuite.extended_sysroot");
cargo.args(builder.config.test_args());

#[allow(deprecated)]
builder.config.try_run(&mut cargo.into()).unwrap();
let mut cmd: Command = cargo.into();
builder.run_cmd(BootstrapCommand::from(&mut cmd).fail_fast());
}
}
4 changes: 2 additions & 2 deletions src/bootstrap/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ impl Step for ToolBuild {
);

let mut cargo = Command::from(cargo);
#[allow(deprecated)] // we check this in `is_optional_tool` in a second
let is_expected = builder.config.try_run(&mut cargo).is_ok();
// we check this in `is_optional_tool` in a second
let is_expected = builder.run_cmd(&mut cargo);

builder.save_toolstate(
tool,
Expand Down
34 changes: 1 addition & 33 deletions src/bootstrap/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Simple things like testing the various filesystem operations here and there,
//! not a lot of interesting happenings here unfortunately.

use build_helper::util::{fail, try_run};
use build_helper::util::fail;
use std::env;
use std::fs;
use std::io;
Expand Down Expand Up @@ -216,12 +216,6 @@ pub fn is_valid_test_suite_arg<'a, P: AsRef<Path>>(
}
}

pub fn run(cmd: &mut Command, print_cmd_on_fail: bool) {
if try_run(cmd, print_cmd_on_fail).is_err() {
crate::exit!(1);
}
}

pub fn check_run(cmd: &mut Command, print_cmd_on_fail: bool) -> bool {
let status = match cmd.status() {
Ok(status) => status,
Expand All @@ -239,32 +233,6 @@ pub fn check_run(cmd: &mut Command, print_cmd_on_fail: bool) -> bool {
status.success()
}

pub fn run_suppressed(cmd: &mut Command) {
if !try_run_suppressed(cmd) {
crate::exit!(1);
}
}

pub fn try_run_suppressed(cmd: &mut Command) -> bool {
let output = match cmd.output() {
Ok(status) => status,
Err(e) => fail(&format!("failed to execute command: {cmd:?}\nerror: {e}")),
};
if !output.status.success() {
println!(
"\n\ncommand did not execute successfully: {:?}\n\
expected success, got: {}\n\n\
stdout ----\n{}\n\
stderr ----\n{}\n\n",
cmd,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
output.status.success()
}

pub fn make(host: &str) -> PathBuf {
if host.contains("dragonfly")
|| host.contains("freebsd")
Expand Down

0 comments on commit 14f0550

Please sign in to comment.