Skip to content

Commit aa1a71e

Browse files
committed
Auto merge of rust-lang#116581 - Kobzol:bootstrap-cmd-run, r=onur-ozkan
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`
2 parents 8396efe + 5d7fca1 commit aa1a71e

File tree

6 files changed

+158
-80
lines changed

6 files changed

+158
-80
lines changed

src/bootstrap/src/core/build_steps/test.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use crate::core::config::flags::Subcommand;
2727
use crate::core::config::TargetSelection;
2828
use crate::utils;
2929
use crate::utils::cache::{Interned, INTERNER};
30+
use crate::utils::exec::BootstrapCommand;
3031
use crate::utils::helpers::{
3132
self, add_link_lib_path, dylib_path, dylib_path_var, output, t, up_to_date,
3233
};
@@ -808,8 +809,8 @@ impl Step for Clippy {
808809

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

811-
#[allow(deprecated)] // Clippy reports errors if it blessed the outputs
812-
if builder.config.try_run(&mut cargo).is_ok() {
812+
// Clippy reports errors if it blessed the outputs
813+
if builder.run_cmd(BootstrapCommand::from(&mut cargo).allow_failure()) {
813814
// The tests succeeded; nothing to do.
814815
return;
815816
}
@@ -3094,7 +3095,7 @@ impl Step for CodegenCranelift {
30943095
.arg("testsuite.extended_sysroot");
30953096
cargo.args(builder.config.test_args());
30963097

3097-
#[allow(deprecated)]
3098-
builder.config.try_run(&mut cargo.into()).unwrap();
3098+
let mut cmd: Command = cargo.into();
3099+
builder.run_cmd(BootstrapCommand::from(&mut cmd).fail_fast());
30993100
}
31003101
}

src/bootstrap/src/core/build_steps/tool.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::core::build_steps::toolstate::ToolState;
88
use crate::core::builder::{Builder, Cargo as CargoCommand, RunConfig, ShouldRun, Step};
99
use crate::core::config::TargetSelection;
1010
use crate::utils::channel::GitInfo;
11+
use crate::utils::exec::BootstrapCommand;
1112
use crate::utils::helpers::{add_dylib_path, exe, t};
1213
use crate::Compiler;
1314
use crate::Mode;
@@ -108,8 +109,8 @@ impl Step for ToolBuild {
108109
);
109110

110111
let mut cargo = Command::from(cargo);
111-
#[allow(deprecated)] // we check this in `is_optional_tool` in a second
112-
let is_expected = builder.config.try_run(&mut cargo).is_ok();
112+
// we check this in `is_optional_tool` in a second
113+
let is_expected = builder.run_cmd(BootstrapCommand::from(&mut cargo).allow_failure());
113114

114115
builder.save_toolstate(
115116
tool,

src/bootstrap/src/lib.rs

+88-41
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ use std::fmt::Display;
2323
use std::fs::{self, File};
2424
use std::io;
2525
use std::path::{Path, PathBuf};
26-
use std::process::{Command, Stdio};
26+
use std::process::{Command, Output, Stdio};
2727
use std::str;
2828

2929
use build_helper::ci::{gha, CiEnv};
3030
use build_helper::exit;
31+
use build_helper::util::fail;
3132
use filetime::FileTime;
3233
use once_cell::sync::OnceCell;
3334
use termcolor::{ColorChoice, StandardStream, WriteColor};
@@ -39,10 +40,8 @@ use crate::core::config::flags;
3940
use crate::core::config::{DryRun, Target};
4041
use crate::core::config::{LlvmLibunwind, TargetSelection};
4142
use crate::utils::cache::{Interned, INTERNER};
42-
use crate::utils::helpers::{
43-
self, dir_is_empty, exe, libdir, mtime, output, run, run_suppressed, symlink_dir,
44-
try_run_suppressed,
45-
};
43+
use crate::utils::exec::{BehaviorOnFailure, BootstrapCommand, OutputMode};
44+
use crate::utils::helpers::{self, dir_is_empty, exe, libdir, mtime, output, symlink_dir};
4645

4746
mod core;
4847
mod utils;
@@ -580,15 +579,15 @@ impl Build {
580579
}
581580

582581
// Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
583-
#[allow(deprecated)] // diff-index reports the modifications through the exit status
584-
let has_local_modifications = self
585-
.config
586-
.try_run(
582+
// diff-index reports the modifications through the exit status
583+
let has_local_modifications = !self.run_cmd(
584+
BootstrapCommand::from(
587585
Command::new("git")
588586
.args(&["diff-index", "--quiet", "HEAD"])
589587
.current_dir(&absolute_path),
590588
)
591-
.is_err();
589+
.allow_failure(),
590+
);
592591
if has_local_modifications {
593592
self.run(Command::new("git").args(&["stash", "push"]).current_dir(&absolute_path));
594593
}
@@ -921,55 +920,103 @@ impl Build {
921920

922921
/// Runs a command, printing out nice contextual information if it fails.
923922
fn run(&self, cmd: &mut Command) {
924-
if self.config.dry_run() {
925-
return;
926-
}
927-
self.verbose(&format!("running: {cmd:?}"));
928-
run(cmd, self.is_verbose())
923+
self.run_cmd(BootstrapCommand::from(cmd).fail_fast().output_mode(
924+
match self.is_verbose() {
925+
true => OutputMode::PrintAll,
926+
false => OutputMode::PrintOutput,
927+
},
928+
));
929+
}
930+
931+
/// Runs a command, printing out contextual info if it fails, and delaying errors until the build finishes.
932+
pub(crate) fn run_delaying_failure(&self, cmd: &mut Command) -> bool {
933+
self.run_cmd(BootstrapCommand::from(cmd).delay_failure().output_mode(
934+
match self.is_verbose() {
935+
true => OutputMode::PrintAll,
936+
false => OutputMode::PrintOutput,
937+
},
938+
))
929939
}
930940

931941
/// Runs a command, printing out nice contextual information if it fails.
932942
fn run_quiet(&self, cmd: &mut Command) {
933-
if self.config.dry_run() {
934-
return;
935-
}
936-
self.verbose(&format!("running: {cmd:?}"));
937-
run_suppressed(cmd)
943+
self.run_cmd(
944+
BootstrapCommand::from(cmd).fail_fast().output_mode(OutputMode::SuppressOnSuccess),
945+
);
938946
}
939947

940948
/// Runs a command, printing out nice contextual information if it fails.
941949
/// Exits if the command failed to execute at all, otherwise returns its
942950
/// `status.success()`.
943951
fn run_quiet_delaying_failure(&self, cmd: &mut Command) -> bool {
952+
self.run_cmd(
953+
BootstrapCommand::from(cmd).delay_failure().output_mode(OutputMode::SuppressOnSuccess),
954+
)
955+
}
956+
957+
/// A centralized function for running commands that do not return output.
958+
pub(crate) fn run_cmd<'a, C: Into<BootstrapCommand<'a>>>(&self, cmd: C) -> bool {
944959
if self.config.dry_run() {
945960
return true;
946961
}
947-
if !self.fail_fast {
948-
self.verbose(&format!("running: {cmd:?}"));
949-
if !try_run_suppressed(cmd) {
950-
let mut failures = self.delayed_failures.borrow_mut();
951-
failures.push(format!("{cmd:?}"));
952-
return false;
962+
963+
let command = cmd.into();
964+
self.verbose(&format!("running: {command:?}"));
965+
966+
let (output, print_error) = match command.output_mode {
967+
mode @ (OutputMode::PrintAll | OutputMode::PrintOutput) => (
968+
command.command.status().map(|status| Output {
969+
status,
970+
stdout: Vec::new(),
971+
stderr: Vec::new(),
972+
}),
973+
matches!(mode, OutputMode::PrintAll),
974+
),
975+
OutputMode::SuppressOnSuccess => (command.command.output(), true),
976+
};
977+
978+
let output = match output {
979+
Ok(output) => output,
980+
Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", command, e)),
981+
};
982+
let result = if !output.status.success() {
983+
if print_error {
984+
println!(
985+
"\n\ncommand did not execute successfully: {:?}\n\
986+
expected success, got: {}\n\n\
987+
stdout ----\n{}\n\
988+
stderr ----\n{}\n\n",
989+
command.command,
990+
output.status,
991+
String::from_utf8_lossy(&output.stdout),
992+
String::from_utf8_lossy(&output.stderr)
993+
);
953994
}
995+
Err(())
954996
} else {
955-
self.run_quiet(cmd);
956-
}
957-
true
958-
}
997+
Ok(())
998+
};
959999

960-
/// Runs a command, printing out contextual info if it fails, and delaying errors until the build finishes.
961-
pub(crate) fn run_delaying_failure(&self, cmd: &mut Command) -> bool {
962-
if !self.fail_fast {
963-
#[allow(deprecated)] // can't use Build::try_run, that's us
964-
if self.config.try_run(cmd).is_err() {
965-
let mut failures = self.delayed_failures.borrow_mut();
966-
failures.push(format!("{cmd:?}"));
967-
return false;
1000+
match result {
1001+
Ok(_) => true,
1002+
Err(_) => {
1003+
match command.failure_behavior {
1004+
BehaviorOnFailure::DelayFail => {
1005+
if self.fail_fast {
1006+
exit!(1);
1007+
}
1008+
1009+
let mut failures = self.delayed_failures.borrow_mut();
1010+
failures.push(format!("{command:?}"));
1011+
}
1012+
BehaviorOnFailure::Exit => {
1013+
exit!(1);
1014+
}
1015+
BehaviorOnFailure::Ignore => {}
1016+
}
1017+
false
9681018
}
969-
} else {
970-
self.run(cmd);
9711019
}
972-
true
9731020
}
9741021

9751022
pub fn is_verbose_than(&self, level: usize) -> bool {

src/bootstrap/src/utils/exec.rs

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use std::process::Command;
2+
3+
/// What should be done when the command fails.
4+
#[derive(Debug, Copy, Clone)]
5+
pub enum BehaviorOnFailure {
6+
/// Immediately stop bootstrap.
7+
Exit,
8+
/// Delay failure until the end of bootstrap invocation.
9+
DelayFail,
10+
/// Ignore the failure, the command can fail in an expected way.
11+
Ignore,
12+
}
13+
14+
/// How should the output of the command be handled.
15+
#[derive(Debug, Copy, Clone)]
16+
pub enum OutputMode {
17+
/// Print both the output (by inheriting stdout/stderr) and also the command itself, if it
18+
/// fails.
19+
PrintAll,
20+
/// Print the output (by inheriting stdout/stderr).
21+
PrintOutput,
22+
/// Suppress the output if the command succeeds, otherwise print the output.
23+
SuppressOnSuccess,
24+
}
25+
26+
/// Wrapper around `std::process::Command`.
27+
#[derive(Debug)]
28+
pub struct BootstrapCommand<'a> {
29+
pub command: &'a mut Command,
30+
pub failure_behavior: BehaviorOnFailure,
31+
pub output_mode: OutputMode,
32+
}
33+
34+
impl<'a> BootstrapCommand<'a> {
35+
pub fn delay_failure(self) -> Self {
36+
Self { failure_behavior: BehaviorOnFailure::DelayFail, ..self }
37+
}
38+
39+
pub fn fail_fast(self) -> Self {
40+
Self { failure_behavior: BehaviorOnFailure::Exit, ..self }
41+
}
42+
43+
pub fn allow_failure(self) -> Self {
44+
Self { failure_behavior: BehaviorOnFailure::Ignore, ..self }
45+
}
46+
47+
pub fn output_mode(self, output_mode: OutputMode) -> Self {
48+
Self { output_mode, ..self }
49+
}
50+
}
51+
52+
impl<'a> From<&'a mut Command> for BootstrapCommand<'a> {
53+
fn from(command: &'a mut Command) -> Self {
54+
Self {
55+
command,
56+
failure_behavior: BehaviorOnFailure::Exit,
57+
output_mode: OutputMode::SuppressOnSuccess,
58+
}
59+
}
60+
}

src/bootstrap/src/utils/helpers.rs

+1-33
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! Simple things like testing the various filesystem operations here and there,
44
//! not a lot of interesting happenings here unfortunately.
55
6-
use build_helper::util::{fail, try_run};
6+
use build_helper::util::fail;
77
use std::env;
88
use std::fs;
99
use std::io;
@@ -216,12 +216,6 @@ pub fn is_valid_test_suite_arg<'a, P: AsRef<Path>>(
216216
}
217217
}
218218

219-
pub fn run(cmd: &mut Command, print_cmd_on_fail: bool) {
220-
if try_run(cmd, print_cmd_on_fail).is_err() {
221-
crate::exit!(1);
222-
}
223-
}
224-
225219
pub fn check_run(cmd: &mut Command, print_cmd_on_fail: bool) -> bool {
226220
let status = match cmd.status() {
227221
Ok(status) => status,
@@ -239,32 +233,6 @@ pub fn check_run(cmd: &mut Command, print_cmd_on_fail: bool) -> bool {
239233
status.success()
240234
}
241235

242-
pub fn run_suppressed(cmd: &mut Command) {
243-
if !try_run_suppressed(cmd) {
244-
crate::exit!(1);
245-
}
246-
}
247-
248-
pub fn try_run_suppressed(cmd: &mut Command) -> bool {
249-
let output = match cmd.output() {
250-
Ok(status) => status,
251-
Err(e) => fail(&format!("failed to execute command: {cmd:?}\nerror: {e}")),
252-
};
253-
if !output.status.success() {
254-
println!(
255-
"\n\ncommand did not execute successfully: {:?}\n\
256-
expected success, got: {}\n\n\
257-
stdout ----\n{}\n\
258-
stderr ----\n{}\n\n",
259-
cmd,
260-
output.status,
261-
String::from_utf8_lossy(&output.stdout),
262-
String::from_utf8_lossy(&output.stderr)
263-
);
264-
}
265-
output.status.success()
266-
}
267-
268236
pub fn make(host: &str) -> PathBuf {
269237
if host.contains("dragonfly")
270238
|| host.contains("freebsd")

src/bootstrap/src/utils/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub(crate) mod cache;
66
pub(crate) mod cc_detect;
77
pub(crate) mod channel;
88
pub(crate) mod dylib;
9+
pub(crate) mod exec;
910
pub(crate) mod helpers;
1011
pub(crate) mod job;
1112
#[cfg(feature = "build-metrics")]

0 commit comments

Comments
 (0)