Skip to content

Commit 14f0550

Browse files
committed
Auto merge of #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 3ff244b + 3bb226f commit 14f0550

File tree

5 files changed

+158
-91
lines changed

5 files changed

+158
-91
lines changed

src/bootstrap/exec.rs

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
}
11+
12+
/// How should the output of the command be handled.
13+
#[derive(Debug, Copy, Clone)]
14+
pub enum OutputMode {
15+
/// Print both the output (by inheriting stdout/stderr) and also the command itself, if it
16+
/// fails.
17+
PrintAll,
18+
/// Print the output (by inheriting stdout/stderr).
19+
PrintOutput,
20+
/// Suppress the output if the command succeeds, otherwise print the output.
21+
SuppressOnSuccess,
22+
}
23+
24+
/// Wrapper around `std::process::Command`.
25+
#[derive(Debug)]
26+
pub struct BootstrapCommand<'a> {
27+
pub command: &'a mut Command,
28+
pub failure_behavior: BehaviorOnFailure,
29+
pub output_mode: OutputMode,
30+
}
31+
32+
impl<'a> BootstrapCommand<'a> {
33+
pub fn delay_failure(self) -> Self {
34+
Self { failure_behavior: BehaviorOnFailure::DelayFail, ..self }
35+
}
36+
pub fn fail_fast(self) -> Self {
37+
Self { failure_behavior: BehaviorOnFailure::Exit, ..self }
38+
}
39+
pub fn output_mode(self, output_mode: OutputMode) -> Self {
40+
Self { output_mode, ..self }
41+
}
42+
}
43+
44+
impl<'a> From<&'a mut Command> for BootstrapCommand<'a> {
45+
fn from(command: &'a mut Command) -> Self {
46+
Self {
47+
command,
48+
failure_behavior: BehaviorOnFailure::Exit,
49+
output_mode: OutputMode::SuppressOnSuccess,
50+
}
51+
}
52+
}

src/bootstrap/lib.rs

+98-52
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,28 @@ 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 channel::GitInfo;
32-
use config::{DryRun, Target};
31+
use build_helper::util::fail;
3332
use filetime::FileTime;
3433
use once_cell::sync::OnceCell;
34+
use termcolor::{ColorChoice, StandardStream, WriteColor};
35+
36+
use channel::GitInfo;
37+
use config::{DryRun, Target};
3538

3639
use crate::builder::Kind;
40+
use crate::cache::{Interned, INTERNER};
3741
use crate::config::{LlvmLibunwind, TargetSelection};
38-
use crate::util::{
39-
dir_is_empty, exe, libdir, mtime, output, run, run_suppressed, symlink_dir, try_run_suppressed,
40-
};
42+
use crate::exec::{BehaviorOnFailure, BootstrapCommand, OutputMode};
43+
use crate::util::{dir_is_empty, exe, libdir, mtime, output, symlink_dir};
44+
45+
pub use crate::builder::PathSet;
46+
pub use crate::config::Config;
47+
pub use crate::flags::Subcommand;
4148

4249
mod builder;
4350
mod cache;
@@ -50,6 +57,7 @@ mod config;
5057
mod dist;
5158
mod doc;
5259
mod download;
60+
mod exec;
5361
mod flags;
5462
mod format;
5563
mod install;
@@ -87,12 +95,6 @@ mod job {
8795
pub unsafe fn setup(_build: &mut crate::Build) {}
8896
}
8997

90-
pub use crate::builder::PathSet;
91-
use crate::cache::{Interned, INTERNER};
92-
pub use crate::config::Config;
93-
pub use crate::flags::Subcommand;
94-
use termcolor::{ColorChoice, StandardStream, WriteColor};
95-
9698
const LLVM_TOOLS: &[&str] = &[
9799
"llvm-cov", // used to generate coverage report
98100
"llvm-nm", // used to inspect binaries; it shows symbol names, their sizes and visibility
@@ -628,15 +630,12 @@ impl Build {
628630
}
629631

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

966965
/// Runs a command, printing out nice contextual information if it fails.
967966
fn run(&self, cmd: &mut Command) {
968-
if self.config.dry_run() {
969-
return;
970-
}
971-
self.verbose(&format!("running: {cmd:?}"));
972-
run(cmd, self.is_verbose())
967+
self.run_cmd(BootstrapCommand::from(cmd).fail_fast().output_mode(
968+
match self.is_verbose() {
969+
true => OutputMode::PrintAll,
970+
false => OutputMode::PrintOutput,
971+
},
972+
));
973+
}
974+
975+
/// Runs a command, printing out contextual info if it fails, and delaying errors until the build finishes.
976+
pub(crate) fn run_delaying_failure(&self, cmd: &mut Command) -> bool {
977+
self.run_cmd(BootstrapCommand::from(cmd).delay_failure().output_mode(
978+
match self.is_verbose() {
979+
true => OutputMode::PrintAll,
980+
false => OutputMode::PrintOutput,
981+
},
982+
))
973983
}
974984

975985
/// Runs a command, printing out nice contextual information if it fails.
976986
fn run_quiet(&self, cmd: &mut Command) {
977-
if self.config.dry_run() {
978-
return;
979-
}
980-
self.verbose(&format!("running: {cmd:?}"));
981-
run_suppressed(cmd)
987+
self.run_cmd(
988+
BootstrapCommand::from(cmd).fail_fast().output_mode(OutputMode::SuppressOnSuccess),
989+
);
982990
}
983991

984992
/// Runs a command, printing out nice contextual information if it fails.
985993
/// Exits if the command failed to execute at all, otherwise returns its
986994
/// `status.success()`.
987995
fn run_quiet_delaying_failure(&self, cmd: &mut Command) -> bool {
996+
self.run_cmd(
997+
BootstrapCommand::from(cmd).delay_failure().output_mode(OutputMode::SuppressOnSuccess),
998+
)
999+
}
1000+
1001+
/// A centralized function for running commands that do not return output.
1002+
pub(crate) fn run_cmd<'a, C: Into<BootstrapCommand<'a>>>(&self, cmd: C) -> bool {
9881003
if self.config.dry_run() {
9891004
return true;
9901005
}
991-
if !self.fail_fast {
992-
self.verbose(&format!("running: {cmd:?}"));
993-
if !try_run_suppressed(cmd) {
994-
let mut failures = self.delayed_failures.borrow_mut();
995-
failures.push(format!("{cmd:?}"));
996-
return false;
1006+
1007+
let command = cmd.into();
1008+
self.verbose(&format!("running: {command:?}"));
1009+
1010+
let (output, print_error) = match command.output_mode {
1011+
mode @ (OutputMode::PrintAll | OutputMode::PrintOutput) => (
1012+
command.command.status().map(|status| Output {
1013+
status,
1014+
stdout: Vec::new(),
1015+
stderr: Vec::new(),
1016+
}),
1017+
matches!(mode, OutputMode::PrintAll),
1018+
),
1019+
OutputMode::SuppressOnSuccess => (command.command.output(), true),
1020+
};
1021+
1022+
let output = match output {
1023+
Ok(output) => output,
1024+
Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", command, e)),
1025+
};
1026+
let result = if !output.status.success() {
1027+
if print_error {
1028+
println!(
1029+
"\n\ncommand did not execute successfully: {:?}\n\
1030+
expected success, got: {}\n\n\
1031+
stdout ----\n{}\n\
1032+
stderr ----\n{}\n\n",
1033+
command.command,
1034+
output.status,
1035+
String::from_utf8_lossy(&output.stdout),
1036+
String::from_utf8_lossy(&output.stderr)
1037+
);
9971038
}
1039+
Err(())
9981040
} else {
999-
self.run_quiet(cmd);
1000-
}
1001-
true
1002-
}
1041+
Ok(())
1042+
};
10031043

1004-
/// Runs a command, printing out contextual info if it fails, and delaying errors until the build finishes.
1005-
pub(crate) fn run_delaying_failure(&self, cmd: &mut Command) -> bool {
1006-
if !self.fail_fast {
1007-
#[allow(deprecated)] // can't use Build::try_run, that's us
1008-
if self.config.try_run(cmd).is_err() {
1009-
let mut failures = self.delayed_failures.borrow_mut();
1010-
failures.push(format!("{cmd:?}"));
1011-
return false;
1044+
match result {
1045+
Ok(_) => true,
1046+
Err(_) => {
1047+
match command.failure_behavior {
1048+
BehaviorOnFailure::DelayFail => {
1049+
if self.fail_fast {
1050+
exit!(1);
1051+
}
1052+
1053+
let mut failures = self.delayed_failures.borrow_mut();
1054+
failures.push(format!("{command:?}"));
1055+
}
1056+
BehaviorOnFailure::Exit => {
1057+
exit!(1);
1058+
}
1059+
}
1060+
false
10121061
}
1013-
} else {
1014-
self.run(cmd);
10151062
}
1016-
true
10171063
}
10181064

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

src/bootstrap/test.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::compile;
2121
use crate::config::TargetSelection;
2222
use crate::dist;
2323
use crate::doc::DocumentationFormat;
24+
use crate::exec::BootstrapCommand;
2425
use crate::flags::Subcommand;
2526
use crate::llvm;
2627
use crate::render_tests::add_flags_and_try_run_tests;
@@ -801,8 +802,8 @@ impl Step for Clippy {
801802

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

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

3090-
#[allow(deprecated)]
3091-
builder.config.try_run(&mut cargo.into()).unwrap();
3091+
let mut cmd: Command = cargo.into();
3092+
builder.run_cmd(BootstrapCommand::from(&mut cmd).fail_fast());
30923093
}
30933094
}

src/bootstrap/tool.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ impl Step for ToolBuild {
108108
);
109109

110110
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();
111+
// we check this in `is_optional_tool` in a second
112+
let is_expected = builder.run_cmd(&mut cargo);
113113

114114
builder.save_toolstate(
115115
tool,

src/bootstrap/util.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")

0 commit comments

Comments
 (0)