Skip to content

Commit 25bcc7d

Browse files
Rollup merge of rust-lang#126731 - Kobzol:bootstrap-cmd-refactor, r=onur-ozkan
Bootstrap command refactoring: refactor `BootstrapCommand` (step 1) This PR is a first step towards https://rust-lang.zulipchat.com/#narrow/stream/326414-t-infra.2Fbootstrap. It refactors `BoostrapCommand` to get it closer to a state where it is an actual command wrapper that can be routed through a central place of command execution, and also to make the distinction between printing output vs handling output programatically clearer (since now it's a mess). The existing usages of `BootstrapCommand` are complicated primarily because of different ways of handling output. There are commands that: 1) Want to eagerly print stdout/stderr of the executed command, plus print an error message if the command fails (output mode `PrintAll`). Note that this error message attempts to print stdout/stderr of the command when `-v` is enabled, but that will always be empty, since this mode uses `.status()` and not `.output()`. 2) Want to eagerly print stdout/stderr of the executed command, but do not print any additional error message if it fails (output mode `PrintOutput`) 3) Want to capture stdout/stderr of the executed command, but print an error message if it fails (output mode `PrintFailure`). This means that the user wants to either ignore the output or handle it programatically, but that's not obvious from the name. The difference between 1) and 2) (unless explicitly specified) is determined dynamically based on the bootstrap verbosity level. It is very difficult for me to wrap my head around all these modes. I think that in a future PR, we should split these axes into e.g. this: 1) Do I want to handle the output programmatically or print it to the terminal? This should be a separate axis, true/false. (Note that "hiding the output" essentially just means saying that I handle it programmatically, and then I ignore the output). 2) Do I want to print a message if the command fails? Yes/No/Based on verbosity (which would be the default). Then there is also the failure mode, but that is relatively simple to handle, the command execution will just shutdown bootstrap (either eagerly or late) when the command fails. Note that this is just a first refactoring steps, there are a lot of other things to be done, so some things might not look "final" yet. The next steps are (not necessarily in this order): - Remove `run` and `run_cmd` and implement everything in terms of `run_tracked` and rename `run_tracked` to `run` - Implement the refactoring specified above (change how output modes work) - Modify `BootstrapCmd` so that it stores `Command` instead of `&mut Command` and remove all the annoying `BootstrapCmd::from` by changing `Command::new` to `BootstrapCmd::new` - Refactor the rest of command executions not currently using `BootstrapCmd` that can access Builder to use the correct output and failure modes. This will include passing Builder to additional places. - Handle the most complex cases, such as output streaming. That will probably need to be handled separately. - Refactor the rest of commands that cannot access builder (e.g. `Config::parse`) by introducing a new command context that will be passed to these places, and then stored in `Builder`. Move certain fields (such as `fail_fast`) from `Builder` to the command context. - Handle the co-operation of `Builder`, `Build`, `Config` and command context. There are some fields and logic used during command execution that are distributed amongst `Builder/Build/Config`, so it will require some refactoring to make it work if the execution will happen on a separate place (in the command context). - Refactor logging of commands, so that it is either logged to a file or printed in a nice hierarchical way that cooperates with the `Step` debug hierarchical output. - Implement profiling of commands (add command durations to the command log, print a log of slowest commands and their execution counts at the end of bootstrap execution, perhaps store command executions to `metrics.json`). - Implement caching of commands. - Implement testing of commands through snapshot tests/mocking. Best reviewed commit by commit. r? ``@onur-ozkan``
2 parents 399c5ca + 250586c commit 25bcc7d

File tree

3 files changed

+131
-106
lines changed

3 files changed

+131
-106
lines changed

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

+26-17
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::core::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
2626
use crate::core::config::flags::get_completion;
2727
use crate::core::config::flags::Subcommand;
2828
use crate::core::config::TargetSelection;
29-
use crate::utils::exec::BootstrapCommand;
29+
use crate::utils::exec::{BootstrapCommand, OutputMode};
3030
use crate::utils::helpers::{
3131
self, add_link_lib_path, add_rustdoc_cargo_linker_args, dylib_path, dylib_path_var,
3232
linker_args, linker_flags, output, t, target_supports_cranelift_backend, up_to_date,
@@ -156,7 +156,10 @@ You can skip linkcheck with --skip src/tools/linkchecker"
156156
let _guard =
157157
builder.msg(Kind::Test, compiler.stage, "Linkcheck", bootstrap_host, bootstrap_host);
158158
let _time = helpers::timeit(builder);
159-
builder.run_delaying_failure(linkchecker.arg(builder.out.join(host.triple).join("doc")));
159+
builder.run_tracked(
160+
BootstrapCommand::from(linkchecker.arg(builder.out.join(host.triple).join("doc")))
161+
.delay_failure(),
162+
);
160163
}
161164

162165
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
@@ -213,8 +216,11 @@ impl Step for HtmlCheck {
213216
builder,
214217
));
215218

216-
builder.run_delaying_failure(
217-
builder.tool_cmd(Tool::HtmlChecker).arg(builder.doc_out(self.target)),
219+
builder.run_tracked(
220+
BootstrapCommand::from(
221+
builder.tool_cmd(Tool::HtmlChecker).arg(builder.doc_out(self.target)),
222+
)
223+
.delay_failure(),
218224
);
219225
}
220226
}
@@ -261,7 +267,7 @@ impl Step for Cargotest {
261267
.env("RUSTC", builder.rustc(compiler))
262268
.env("RUSTDOC", builder.rustdoc(compiler));
263269
add_rustdoc_cargo_linker_args(cmd, builder, compiler.host, LldThreads::No);
264-
builder.run_delaying_failure(cmd);
270+
builder.run_tracked(BootstrapCommand::from(cmd).delay_failure());
265271
}
266272
}
267273

@@ -813,7 +819,7 @@ impl Step for RustdocTheme {
813819
.env("RUSTC_BOOTSTRAP", "1");
814820
cmd.args(linker_args(builder, self.compiler.host, LldThreads::No));
815821

816-
builder.run_delaying_failure(&mut cmd);
822+
builder.run_tracked(BootstrapCommand::from(&mut cmd).delay_failure());
817823
}
818824
}
819825

@@ -1093,7 +1099,7 @@ HELP: to skip test's attempt to check tidiness, pass `--skip src/tools/tidy` to
10931099
}
10941100

10951101
builder.info("tidy check");
1096-
builder.run_delaying_failure(&mut cmd);
1102+
builder.run_tracked(BootstrapCommand::from(&mut cmd).delay_failure());
10971103

10981104
builder.info("x.py completions check");
10991105
let [bash, zsh, fish, powershell] = ["x.py.sh", "x.py.zsh", "x.py.fish", "x.py.ps1"]
@@ -2179,7 +2185,8 @@ impl BookTest {
21792185
compiler.host,
21802186
);
21812187
let _time = helpers::timeit(builder);
2182-
let toolstate = if builder.run_delaying_failure(&mut rustbook_cmd) {
2188+
let cmd = BootstrapCommand::from(&mut rustbook_cmd).delay_failure();
2189+
let toolstate = if builder.run_tracked(cmd).is_success() {
21832190
ToolState::TestPass
21842191
} else {
21852192
ToolState::TestFail
@@ -2312,7 +2319,8 @@ impl Step for ErrorIndex {
23122319
let guard =
23132320
builder.msg(Kind::Test, compiler.stage, "error-index", compiler.host, compiler.host);
23142321
let _time = helpers::timeit(builder);
2315-
builder.run_quiet(&mut tool);
2322+
builder
2323+
.run_tracked(BootstrapCommand::from(&mut tool).output_mode(OutputMode::OnlyOnFailure));
23162324
drop(guard);
23172325
// The tests themselves need to link to std, so make sure it is
23182326
// available.
@@ -2341,11 +2349,11 @@ fn markdown_test(builder: &Builder<'_>, compiler: Compiler, markdown: &Path) ->
23412349
let test_args = builder.config.test_args().join(" ");
23422350
cmd.arg("--test-args").arg(test_args);
23432351

2344-
if builder.config.verbose_tests {
2345-
builder.run_delaying_failure(&mut cmd)
2346-
} else {
2347-
builder.run_quiet_delaying_failure(&mut cmd)
2352+
let mut cmd = BootstrapCommand::from(&mut cmd).delay_failure();
2353+
if !builder.config.verbose_tests {
2354+
cmd = cmd.quiet();
23482355
}
2356+
builder.run_tracked(cmd).is_success()
23492357
}
23502358

23512359
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -2370,7 +2378,8 @@ impl Step for RustcGuide {
23702378

23712379
let src = builder.src.join(relative_path);
23722380
let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
2373-
let toolstate = if builder.run_delaying_failure(rustbook_cmd.arg("linkcheck").arg(&src)) {
2381+
let cmd = BootstrapCommand::from(rustbook_cmd.arg("linkcheck").arg(&src)).delay_failure();
2382+
let toolstate = if builder.run_tracked(cmd).is_success() {
23742383
ToolState::TestPass
23752384
} else {
23762385
ToolState::TestFail
@@ -2984,7 +2993,7 @@ impl Step for Bootstrap {
29842993
.current_dir(builder.src.join("src/bootstrap/"));
29852994
// NOTE: we intentionally don't pass test_args here because the args for unittest and cargo test are mutually incompatible.
29862995
// Use `python -m unittest` manually if you want to pass arguments.
2987-
builder.run_delaying_failure(&mut check_bootstrap);
2996+
builder.run_tracked(BootstrapCommand::from(&mut check_bootstrap).delay_failure());
29882997

29892998
let mut cmd = Command::new(&builder.initial_cargo);
29902999
cmd.arg("test")
@@ -3061,7 +3070,7 @@ impl Step for TierCheck {
30613070
self.compiler.host,
30623071
self.compiler.host,
30633072
);
3064-
builder.run_delaying_failure(&mut cargo.into());
3073+
builder.run_tracked(BootstrapCommand::from(&mut cargo.into()).delay_failure());
30653074
}
30663075
}
30673076

@@ -3147,7 +3156,7 @@ impl Step for RustInstaller {
31473156
cmd.env("CARGO", &builder.initial_cargo);
31483157
cmd.env("RUSTC", &builder.initial_rustc);
31493158
cmd.env("TMP_DIR", &tmpdir);
3150-
builder.run_delaying_failure(&mut cmd);
3159+
builder.run_tracked(BootstrapCommand::from(&mut cmd).delay_failure());
31513160
}
31523161

31533162
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {

src/bootstrap/src/lib.rs

+49-78
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ 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, Output, Stdio};
26+
use std::process::{Command, Stdio};
2727
use std::str;
2828
use std::sync::OnceLock;
2929

@@ -41,7 +41,7 @@ use crate::core::builder::Kind;
4141
use crate::core::config::{flags, LldMode};
4242
use crate::core::config::{DryRun, Target};
4343
use crate::core::config::{LlvmLibunwind, TargetSelection};
44-
use crate::utils::exec::{BehaviorOnFailure, BootstrapCommand, OutputMode};
44+
use crate::utils::exec::{BehaviorOnFailure, BootstrapCommand, CommandOutput, OutputMode};
4545
use crate::utils::helpers::{self, dir_is_empty, exe, libdir, mtime, output, symlink_dir};
4646

4747
mod core;
@@ -585,8 +585,8 @@ impl Build {
585585
BootstrapCommand::from(submodule_git().args(["diff-index", "--quiet", "HEAD"]))
586586
.allow_failure()
587587
.output_mode(match self.is_verbose() {
588-
true => OutputMode::PrintAll,
589-
false => OutputMode::PrintOutput,
588+
true => OutputMode::All,
589+
false => OutputMode::OnlyOutput,
590590
}),
591591
);
592592
if has_local_modifications {
@@ -958,73 +958,36 @@ impl Build {
958958
})
959959
}
960960

961-
/// Runs a command, printing out nice contextual information if it fails.
962-
fn run(&self, cmd: &mut Command) {
963-
self.run_cmd(BootstrapCommand::from(cmd).fail_fast().output_mode(
964-
match self.is_verbose() {
965-
true => OutputMode::PrintAll,
966-
false => OutputMode::PrintOutput,
967-
},
968-
));
969-
}
970-
971-
/// Runs a command, printing out contextual info if it fails, and delaying errors until the build finishes.
972-
pub(crate) fn run_delaying_failure(&self, cmd: &mut Command) -> bool {
973-
self.run_cmd(BootstrapCommand::from(cmd).delay_failure().output_mode(
974-
match self.is_verbose() {
975-
true => OutputMode::PrintAll,
976-
false => OutputMode::PrintOutput,
977-
},
978-
))
979-
}
980-
981-
/// Runs a command, printing out nice contextual information if it fails.
982-
fn run_quiet(&self, cmd: &mut Command) {
983-
self.run_cmd(
984-
BootstrapCommand::from(cmd).fail_fast().output_mode(OutputMode::SuppressOnSuccess),
985-
);
986-
}
987-
988-
/// Runs a command, printing out nice contextual information if it fails.
989-
/// Exits if the command failed to execute at all, otherwise returns its
990-
/// `status.success()`.
991-
fn run_quiet_delaying_failure(&self, cmd: &mut Command) -> bool {
992-
self.run_cmd(
993-
BootstrapCommand::from(cmd).delay_failure().output_mode(OutputMode::SuppressOnSuccess),
994-
)
995-
}
996-
997-
/// A centralized function for running commands that do not return output.
998-
pub(crate) fn run_cmd<'a, C: Into<BootstrapCommand<'a>>>(&self, cmd: C) -> bool {
961+
/// Execute a command and return its output.
962+
fn run_tracked(&self, command: BootstrapCommand<'_>) -> CommandOutput {
999963
if self.config.dry_run() {
1000-
return true;
964+
return CommandOutput::default();
1001965
}
1002966

1003-
let command = cmd.into();
1004967
self.verbose(|| println!("running: {command:?}"));
1005968

1006-
let (output, print_error) = match command.output_mode {
1007-
mode @ (OutputMode::PrintAll | OutputMode::PrintOutput) => (
1008-
command.command.status().map(|status| Output {
1009-
status,
1010-
stdout: Vec::new(),
1011-
stderr: Vec::new(),
1012-
}),
1013-
matches!(mode, OutputMode::PrintAll),
969+
let output_mode = command.output_mode.unwrap_or_else(|| match self.is_verbose() {
970+
true => OutputMode::All,
971+
false => OutputMode::OnlyOutput,
972+
});
973+
let (output, print_error): (io::Result<CommandOutput>, bool) = match output_mode {
974+
mode @ (OutputMode::All | OutputMode::OnlyOutput) => (
975+
command.command.status().map(|status| status.into()),
976+
matches!(mode, OutputMode::All),
1014977
),
1015-
OutputMode::SuppressOnSuccess => (command.command.output(), true),
978+
OutputMode::OnlyOnFailure => (command.command.output().map(|o| o.into()), true),
1016979
};
1017980

1018981
let output = match output {
1019982
Ok(output) => output,
1020983
Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", command, e)),
1021984
};
1022-
let result = if !output.status.success() {
985+
if !output.is_success() {
1023986
if print_error {
1024987
println!(
1025988
"\n\nCommand did not execute successfully.\
1026-
\nExpected success, got: {}",
1027-
output.status,
989+
\nExpected success, got: {}",
990+
output.status(),
1028991
);
1029992

1030993
if !self.is_verbose() {
@@ -1034,37 +997,45 @@ impl Build {
1034997
self.verbose(|| {
1035998
println!(
1036999
"\nSTDOUT ----\n{}\n\
1037-
STDERR ----\n{}\n",
1038-
String::from_utf8_lossy(&output.stdout),
1039-
String::from_utf8_lossy(&output.stderr)
1000+
STDERR ----\n{}\n",
1001+
output.stdout(),
1002+
output.stderr(),
10401003
)
10411004
});
10421005
}
1043-
Err(())
1044-
} else {
1045-
Ok(())
1046-
};
10471006

1048-
match result {
1049-
Ok(_) => true,
1050-
Err(_) => {
1051-
match command.failure_behavior {
1052-
BehaviorOnFailure::DelayFail => {
1053-
if self.fail_fast {
1054-
exit!(1);
1055-
}
1056-
1057-
let mut failures = self.delayed_failures.borrow_mut();
1058-
failures.push(format!("{command:?}"));
1059-
}
1060-
BehaviorOnFailure::Exit => {
1007+
match command.failure_behavior {
1008+
BehaviorOnFailure::DelayFail => {
1009+
if self.fail_fast {
10611010
exit!(1);
10621011
}
1063-
BehaviorOnFailure::Ignore => {}
1012+
1013+
let mut failures = self.delayed_failures.borrow_mut();
1014+
failures.push(format!("{command:?}"));
1015+
}
1016+
BehaviorOnFailure::Exit => {
1017+
exit!(1);
10641018
}
1065-
false
1019+
BehaviorOnFailure::Ignore => {}
10661020
}
10671021
}
1022+
output
1023+
}
1024+
1025+
/// Runs a command, printing out nice contextual information if it fails.
1026+
fn run(&self, cmd: &mut Command) {
1027+
self.run_cmd(BootstrapCommand::from(cmd).fail_fast().output_mode(
1028+
match self.is_verbose() {
1029+
true => OutputMode::All,
1030+
false => OutputMode::OnlyOutput,
1031+
},
1032+
));
1033+
}
1034+
1035+
/// A centralized function for running commands that do not return output.
1036+
pub(crate) fn run_cmd<'a, C: Into<BootstrapCommand<'a>>>(&self, cmd: C) -> bool {
1037+
let command = cmd.into();
1038+
self.run_tracked(command).is_success()
10681039
}
10691040

10701041
/// Check if verbosity is greater than the `level`

0 commit comments

Comments
 (0)