Auto merge of #127450 - Kobzol:bootstrap-cmd-refactor-5, r=onur-ozkan
Bootstrap command refactoring: improve debuggability (step 5) Continuation of https://github.com/rust-lang/rust/pull/127321. This PR improves the debuggability of command execution, by improving the output logged when a command fails (it now includes the exact location where the command was created and where it was executed), and also by adding a "drop bomb", which will panic if a command was created, but not executed (which is probably a bug). Here is how the output of a failed command looks like: ``` Command "git" "foo" "[bar]" (failure_mode=Exit, stdout_mode=Capture, stderr_mode=Capture) did not execute successfully. Expected success, got exit status: 1 Created at: src/core/build_steps/compile.rs:1699:9 Executed at: src/core/build_steps/compile.rs:1699:58 STDOUT ---- STDERR ---- git: 'foo' is not a git command. See 'git --help'. ``` And this is what is printed if you forget to execute a command: ``` thread 'main' panicked at /projects/personal/rust/rust/src/tools/build_helper/src/drop_bomb/mod.rs:42:13: command constructed at `src/core/build_steps/compile.rs:1699:9` was dropped without being executed: `git` ``` Best reviewed commit by commit. Tracking issue: https://github.com/rust-lang/rust/issues/126819 r? `@onur-ozkan`
This commit is contained in:
commit
c1e3f03e60
24 changed files with 193 additions and 84 deletions
|
@ -3420,6 +3420,7 @@ version = "0.2.0"
|
|||
dependencies = [
|
||||
"ar",
|
||||
"bstr",
|
||||
"build_helper",
|
||||
"gimli 0.28.1",
|
||||
"object 0.34.0",
|
||||
"regex",
|
||||
|
|
|
@ -2080,7 +2080,8 @@ pub fn stream_cargo(
|
|||
tail_args: Vec<String>,
|
||||
cb: &mut dyn FnMut(CargoMessage<'_>),
|
||||
) -> bool {
|
||||
let mut cargo = cargo.into_cmd().command;
|
||||
let mut cmd = cargo.into_cmd();
|
||||
let cargo = cmd.as_command_mut();
|
||||
// Instruct Cargo to give us json messages on stdout, critically leaving
|
||||
// stderr as piped so we can get those pretty colors.
|
||||
let mut message_format = if builder.config.json_output {
|
||||
|
|
|
@ -1060,11 +1060,7 @@ impl Step for PlainSourceTarball {
|
|||
cmd.arg("--sync").arg(manifest_path);
|
||||
}
|
||||
|
||||
let config = if !builder.config.dry_run() {
|
||||
cmd.capture().run(builder).stdout()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let config = cmd.capture().run(builder).stdout();
|
||||
|
||||
let cargo_config_dir = plain_dst_src.join(".cargo");
|
||||
builder.create_dir(&cargo_config_dir);
|
||||
|
@ -2072,11 +2068,7 @@ fn maybe_install_llvm(
|
|||
let mut cmd = command(llvm_config);
|
||||
cmd.arg("--libfiles");
|
||||
builder.verbose(|| println!("running {cmd:?}"));
|
||||
let files = if builder.config.dry_run() {
|
||||
"".into()
|
||||
} else {
|
||||
cmd.capture_stdout().run(builder).stdout()
|
||||
};
|
||||
let files = cmd.capture_stdout().run(builder).stdout();
|
||||
let build_llvm_out = &builder.llvm_out(builder.config.build);
|
||||
let target_llvm_out = &builder.llvm_out(target);
|
||||
for file in files.trim_end().split(' ') {
|
||||
|
|
|
@ -146,7 +146,6 @@ impl<P: Step> Step for RustbookSrc<P> {
|
|||
let out = out.join(&name);
|
||||
let index = out.join("index.html");
|
||||
let rustbook = builder.tool_exe(Tool::Rustbook);
|
||||
let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
|
||||
|
||||
if !builder.config.dry_run()
|
||||
&& (!up_to_date(&src, &index) || !up_to_date(&rustbook, &index))
|
||||
|
@ -154,7 +153,13 @@ impl<P: Step> Step for RustbookSrc<P> {
|
|||
builder.info(&format!("Rustbook ({target}) - {name}"));
|
||||
let _ = fs::remove_dir_all(&out);
|
||||
|
||||
rustbook_cmd.arg("build").arg(&src).arg("-d").arg(&out).run(builder);
|
||||
builder
|
||||
.tool_cmd(Tool::Rustbook)
|
||||
.arg("build")
|
||||
.arg(&src)
|
||||
.arg("-d")
|
||||
.arg(&out)
|
||||
.run(builder);
|
||||
|
||||
for lang in &self.languages {
|
||||
let out = out.join(lang);
|
||||
|
@ -253,10 +258,6 @@ impl Step for TheBook {
|
|||
// build the version info page and CSS
|
||||
let shared_assets = builder.ensure(SharedAssets { target });
|
||||
|
||||
// build the command first so we don't nest GHA groups
|
||||
// FIXME: this doesn't do anything!
|
||||
builder.rustdoc_cmd(compiler);
|
||||
|
||||
// build the redirect pages
|
||||
let _guard = builder.msg_doc(compiler, "book redirect pages", target);
|
||||
for file in t!(fs::read_dir(redirect_path)) {
|
||||
|
|
|
@ -172,7 +172,7 @@ pub(crate) fn detect_llvm_sha(config: &Config, is_git: bool) -> String {
|
|||
// the LLVM shared object file is named `LLVM-12-rust-{version}-nightly`
|
||||
config.src.join("src/version"),
|
||||
]);
|
||||
output(&mut rev_list.command).trim().to_owned()
|
||||
output(rev_list.as_command_mut()).trim().to_owned()
|
||||
} else if let Some(info) = channel::read_commit_info_file(&config.src) {
|
||||
info.sha.trim().to_owned()
|
||||
} else {
|
||||
|
@ -254,7 +254,7 @@ pub(crate) fn is_ci_llvm_modified(config: &Config) -> bool {
|
|||
// `true` here.
|
||||
let llvm_sha = detect_llvm_sha(config, true);
|
||||
let head_sha =
|
||||
output(&mut helpers::git(Some(&config.src)).arg("rev-parse").arg("HEAD").command);
|
||||
output(helpers::git(Some(&config.src)).arg("rev-parse").arg("HEAD").as_command_mut());
|
||||
let head_sha = head_sha.trim();
|
||||
llvm_sha == head_sha
|
||||
}
|
||||
|
|
|
@ -484,7 +484,7 @@ impl Step for Hook {
|
|||
fn install_git_hook_maybe(config: &Config) -> io::Result<()> {
|
||||
let git = helpers::git(Some(&config.src))
|
||||
.args(["rev-parse", "--git-common-dir"])
|
||||
.command
|
||||
.as_command_mut()
|
||||
.output()
|
||||
.map(|output| {
|
||||
assert!(output.status.success(), "failed to run `git`");
|
||||
|
|
|
@ -471,9 +471,6 @@ impl Miri {
|
|||
// We re-use the `cargo` from above.
|
||||
cargo.arg("--print-sysroot");
|
||||
|
||||
if builder.config.dry_run() {
|
||||
String::new()
|
||||
} else {
|
||||
builder.verbose(|| println!("running: {cargo:?}"));
|
||||
let stdout = cargo.capture_stdout().run(builder).stdout();
|
||||
// Output is "<sysroot>\n".
|
||||
|
@ -481,7 +478,6 @@ impl Miri {
|
|||
builder.verbose(|| println!("`cargo miri setup --print-sysroot` said: {sysroot:?}"));
|
||||
sysroot.to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Step for Miri {
|
||||
|
@ -1352,6 +1348,52 @@ impl Step for CrateRunMakeSupport {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct CrateBuildHelper {
|
||||
host: TargetSelection,
|
||||
}
|
||||
|
||||
impl Step for CrateBuildHelper {
|
||||
type Output = ();
|
||||
const ONLY_HOSTS: bool = true;
|
||||
|
||||
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
|
||||
run.path("src/tools/build_helper")
|
||||
}
|
||||
|
||||
fn make_run(run: RunConfig<'_>) {
|
||||
run.builder.ensure(CrateBuildHelper { host: run.target });
|
||||
}
|
||||
|
||||
/// Runs `cargo test` for build_helper.
|
||||
fn run(self, builder: &Builder<'_>) {
|
||||
let host = self.host;
|
||||
let compiler = builder.compiler(0, host);
|
||||
|
||||
let mut cargo = tool::prepare_tool_cargo(
|
||||
builder,
|
||||
compiler,
|
||||
Mode::ToolBootstrap,
|
||||
host,
|
||||
"test",
|
||||
"src/tools/build_helper",
|
||||
SourceType::InTree,
|
||||
&[],
|
||||
);
|
||||
cargo.allow_features("test");
|
||||
run_cargo_test(
|
||||
cargo,
|
||||
&[],
|
||||
&[],
|
||||
"build_helper",
|
||||
"build_helper self test",
|
||||
compiler,
|
||||
host,
|
||||
builder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
default_test!(Ui { path: "tests/ui", mode: "ui", suite: "ui" });
|
||||
|
||||
default_test!(Crashes { path: "tests/crashes", mode: "crashes", suite: "crashes" });
|
||||
|
@ -2058,7 +2100,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
|
|||
cmd.arg("--nightly-branch").arg(git_config.nightly_branch);
|
||||
|
||||
// FIXME: Move CiEnv back to bootstrap, it is only used here anyway
|
||||
builder.ci_env.force_coloring_in_ci(&mut cmd.command);
|
||||
builder.ci_env.force_coloring_in_ci(cmd.as_command_mut());
|
||||
|
||||
#[cfg(feature = "build-metrics")]
|
||||
builder.metrics.begin_test_suite(
|
||||
|
|
|
@ -106,7 +106,7 @@ fn check_changed_files(toolstates: &HashMap<Box<str>, ToolState>) {
|
|||
.arg("--name-status")
|
||||
.arg("HEAD")
|
||||
.arg("HEAD^")
|
||||
.command
|
||||
.as_command_mut()
|
||||
.output();
|
||||
let output = match output {
|
||||
Ok(o) => o,
|
||||
|
@ -329,7 +329,7 @@ fn checkout_toolstate_repo() {
|
|||
.arg("--depth=1")
|
||||
.arg(toolstate_repo())
|
||||
.arg(TOOLSTATE_DIR)
|
||||
.command
|
||||
.as_command_mut()
|
||||
.status();
|
||||
let success = match status {
|
||||
Ok(s) => s.success(),
|
||||
|
@ -343,8 +343,13 @@ fn checkout_toolstate_repo() {
|
|||
/// Sets up config and authentication for modifying the toolstate repo.
|
||||
fn prepare_toolstate_config(token: &str) {
|
||||
fn git_config(key: &str, value: &str) {
|
||||
let status =
|
||||
helpers::git(None).arg("config").arg("--global").arg(key).arg(value).command.status();
|
||||
let status = helpers::git(None)
|
||||
.arg("config")
|
||||
.arg("--global")
|
||||
.arg(key)
|
||||
.arg(value)
|
||||
.as_command_mut()
|
||||
.status();
|
||||
let success = match status {
|
||||
Ok(s) => s.success(),
|
||||
Err(_) => false,
|
||||
|
@ -413,7 +418,7 @@ fn commit_toolstate_change(current_toolstate: &ToolstateData) {
|
|||
.arg("-a")
|
||||
.arg("-m")
|
||||
.arg(&message)
|
||||
.command
|
||||
.as_command_mut()
|
||||
.status());
|
||||
if !status.success() {
|
||||
success = true;
|
||||
|
@ -424,7 +429,7 @@ fn commit_toolstate_change(current_toolstate: &ToolstateData) {
|
|||
.arg("push")
|
||||
.arg("origin")
|
||||
.arg("master")
|
||||
.command
|
||||
.as_command_mut()
|
||||
.status());
|
||||
// If we successfully push, exit.
|
||||
if status.success() {
|
||||
|
@ -437,14 +442,14 @@ fn commit_toolstate_change(current_toolstate: &ToolstateData) {
|
|||
.arg("fetch")
|
||||
.arg("origin")
|
||||
.arg("master")
|
||||
.command
|
||||
.as_command_mut()
|
||||
.status());
|
||||
assert!(status.success());
|
||||
let status = t!(helpers::git(Some(Path::new(TOOLSTATE_DIR)))
|
||||
.arg("reset")
|
||||
.arg("--hard")
|
||||
.arg("origin/master")
|
||||
.command
|
||||
.as_command_mut()
|
||||
.status());
|
||||
assert!(status.success());
|
||||
}
|
||||
|
@ -460,7 +465,7 @@ fn commit_toolstate_change(current_toolstate: &ToolstateData) {
|
|||
/// `publish_toolstate.py` script if the PR passes all tests and is merged to
|
||||
/// master.
|
||||
fn publish_test_results(current_toolstate: &ToolstateData) {
|
||||
let commit = t!(helpers::git(None).arg("rev-parse").arg("HEAD").command.output());
|
||||
let commit = t!(helpers::git(None).arg("rev-parse").arg("HEAD").as_command_mut().output());
|
||||
let commit = t!(String::from_utf8(commit.stdout));
|
||||
|
||||
let toolstate_serialized = t!(serde_json::to_string(¤t_toolstate));
|
||||
|
|
|
@ -860,6 +860,7 @@ impl<'a> Builder<'a> {
|
|||
test::Clippy,
|
||||
test::CompiletestTest,
|
||||
test::CrateRunMakeSupport,
|
||||
test::CrateBuildHelper,
|
||||
test::RustdocJSStd,
|
||||
test::RustdocJSNotStd,
|
||||
test::RustdocGUI,
|
||||
|
@ -2104,7 +2105,7 @@ impl<'a> Builder<'a> {
|
|||
// Try to use a sysroot-relative bindir, in case it was configured absolutely.
|
||||
cargo.env("RUSTC_INSTALL_BINDIR", self.config.bindir_relative());
|
||||
|
||||
self.ci_env.force_coloring_in_ci(&mut cargo.command);
|
||||
self.ci_env.force_coloring_in_ci(cargo.as_command_mut());
|
||||
|
||||
// When we build Rust dylibs they're all intended for intermediate
|
||||
// usage, so make sure we pass the -Cprefer-dynamic flag instead of
|
||||
|
|
|
@ -1259,7 +1259,7 @@ impl Config {
|
|||
cmd.arg("rev-parse").arg("--show-cdup");
|
||||
// Discard stderr because we expect this to fail when building from a tarball.
|
||||
let output = cmd
|
||||
.command
|
||||
.as_command_mut()
|
||||
.stderr(std::process::Stdio::null())
|
||||
.output()
|
||||
.ok()
|
||||
|
@ -2163,7 +2163,7 @@ impl Config {
|
|||
|
||||
let mut git = helpers::git(Some(&self.src));
|
||||
git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap()));
|
||||
output(&mut git.command)
|
||||
output(git.as_command_mut())
|
||||
}
|
||||
|
||||
/// Bootstrap embeds a version number into the name of shared libraries it uploads in CI.
|
||||
|
@ -2469,11 +2469,11 @@ impl Config {
|
|||
// Look for a version to compare to based on the current commit.
|
||||
// Only commits merged by bors will have CI artifacts.
|
||||
let merge_base = output(
|
||||
&mut helpers::git(Some(&self.src))
|
||||
helpers::git(Some(&self.src))
|
||||
.arg("rev-list")
|
||||
.arg(format!("--author={}", self.stage0_metadata.config.git_merge_commit_email))
|
||||
.args(["-n1", "--first-parent", "HEAD"])
|
||||
.command,
|
||||
.as_command_mut(),
|
||||
);
|
||||
let commit = merge_base.trim_end();
|
||||
if commit.is_empty() {
|
||||
|
@ -2489,7 +2489,7 @@ impl Config {
|
|||
.args(["diff-index", "--quiet", commit])
|
||||
.arg("--")
|
||||
.args([self.src.join("compiler"), self.src.join("library")])
|
||||
.command
|
||||
.as_command_mut()
|
||||
.status())
|
||||
.success();
|
||||
if has_changes {
|
||||
|
@ -2563,11 +2563,11 @@ impl Config {
|
|||
// Look for a version to compare to based on the current commit.
|
||||
// Only commits merged by bors will have CI artifacts.
|
||||
let merge_base = output(
|
||||
&mut helpers::git(Some(&self.src))
|
||||
helpers::git(Some(&self.src))
|
||||
.arg("rev-list")
|
||||
.arg(format!("--author={}", self.stage0_metadata.config.git_merge_commit_email))
|
||||
.args(["-n1", "--first-parent", "HEAD"])
|
||||
.command,
|
||||
.as_command_mut(),
|
||||
);
|
||||
let commit = merge_base.trim_end();
|
||||
if commit.is_empty() {
|
||||
|
@ -2589,7 +2589,7 @@ impl Config {
|
|||
git.arg(top_level.join(path));
|
||||
}
|
||||
|
||||
let has_changes = !t!(git.command.status()).success();
|
||||
let has_changes = !t!(git.as_command_mut().status()).success();
|
||||
if has_changes {
|
||||
if if_unchanged {
|
||||
if self.verbose > 0 {
|
||||
|
|
|
@ -209,7 +209,7 @@ than building it.
|
|||
|
||||
#[cfg(not(feature = "bootstrap-self-test"))]
|
||||
let stage0_supported_target_list: HashSet<String> = crate::utils::helpers::output(
|
||||
&mut command(&build.config.initial_rustc).args(["--print", "target-list"]).command,
|
||||
command(&build.config.initial_rustc).args(["--print", "target-list"]).as_command_mut(),
|
||||
)
|
||||
.lines()
|
||||
.map(|s| s.to_string())
|
||||
|
|
|
@ -493,11 +493,14 @@ impl Build {
|
|||
let submodule_git = || helpers::git(Some(&absolute_path));
|
||||
|
||||
// Determine commit checked out in submodule.
|
||||
let checked_out_hash = output(&mut submodule_git().args(["rev-parse", "HEAD"]).command);
|
||||
let checked_out_hash = output(submodule_git().args(["rev-parse", "HEAD"]).as_command_mut());
|
||||
let checked_out_hash = checked_out_hash.trim_end();
|
||||
// Determine commit that the submodule *should* have.
|
||||
let recorded = output(
|
||||
&mut helpers::git(Some(&self.src)).args(["ls-tree", "HEAD"]).arg(relative_path).command,
|
||||
helpers::git(Some(&self.src))
|
||||
.args(["ls-tree", "HEAD"])
|
||||
.arg(relative_path)
|
||||
.as_command_mut(),
|
||||
);
|
||||
let actual_hash = recorded
|
||||
.split_whitespace()
|
||||
|
@ -522,7 +525,7 @@ impl Build {
|
|||
let current_branch = {
|
||||
let output = helpers::git(Some(&self.src))
|
||||
.args(["symbolic-ref", "--short", "HEAD"])
|
||||
.command
|
||||
.as_command_mut()
|
||||
.stderr(Stdio::inherit())
|
||||
.output();
|
||||
let output = t!(output);
|
||||
|
@ -548,7 +551,7 @@ impl Build {
|
|||
git
|
||||
};
|
||||
// NOTE: doesn't use `try_run` because this shouldn't print an error if it fails.
|
||||
if !update(true).command.status().map_or(false, |status| status.success()) {
|
||||
if !update(true).as_command_mut().status().map_or(false, |status| status.success()) {
|
||||
update(false).run(self);
|
||||
}
|
||||
|
||||
|
@ -934,17 +937,26 @@ impl Build {
|
|||
|
||||
/// Execute a command and return its output.
|
||||
/// This method should be used for all command executions in bootstrap.
|
||||
#[track_caller]
|
||||
fn run(&self, command: &mut BootstrapCommand) -> CommandOutput {
|
||||
command.mark_as_executed();
|
||||
if self.config.dry_run() && !command.run_always {
|
||||
return CommandOutput::default();
|
||||
}
|
||||
|
||||
self.verbose(|| println!("running: {command:?}"));
|
||||
let created_at = command.get_created_location();
|
||||
let executed_at = std::panic::Location::caller();
|
||||
|
||||
command.command.stdout(command.stdout.stdio());
|
||||
command.command.stderr(command.stderr.stdio());
|
||||
self.verbose(|| {
|
||||
println!("running: {command:?} (created at {created_at}, executed at {executed_at})")
|
||||
});
|
||||
|
||||
let output = command.command.output();
|
||||
let stdout = command.stdout.stdio();
|
||||
command.as_command_mut().stdout(stdout);
|
||||
let stderr = command.stderr.stdio();
|
||||
command.as_command_mut().stderr(stderr);
|
||||
|
||||
let output = command.as_command_mut().output();
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
|
@ -956,8 +968,11 @@ impl Build {
|
|||
Ok(output) => {
|
||||
writeln!(
|
||||
message,
|
||||
"\n\nCommand {command:?} did not execute successfully.\
|
||||
\nExpected success, got: {}",
|
||||
r#"
|
||||
Command {command:?} did not execute successfully.
|
||||
Expected success, got {}
|
||||
Created at: {created_at}
|
||||
Executed at: {executed_at}"#,
|
||||
output.status,
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -1931,7 +1946,7 @@ fn envify(s: &str) -> String {
|
|||
pub fn generate_smart_stamp_hash(dir: &Path, additional_input: &str) -> String {
|
||||
let diff = helpers::git(Some(dir))
|
||||
.arg("diff")
|
||||
.command
|
||||
.as_command_mut()
|
||||
.output()
|
||||
.map(|o| String::from_utf8(o.stdout).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
|
@ -1941,7 +1956,7 @@ pub fn generate_smart_stamp_hash(dir: &Path, additional_input: &str) -> String {
|
|||
.arg("--porcelain")
|
||||
.arg("-z")
|
||||
.arg("--untracked-files=normal")
|
||||
.command
|
||||
.as_command_mut()
|
||||
.output()
|
||||
.map(|o| String::from_utf8(o.stdout).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
|
|
|
@ -45,7 +45,7 @@ impl GitInfo {
|
|||
}
|
||||
|
||||
// Make sure git commands work
|
||||
match helpers::git(Some(dir)).arg("rev-parse").command.output() {
|
||||
match helpers::git(Some(dir)).arg("rev-parse").as_command_mut().output() {
|
||||
Ok(ref out) if out.status.success() => {}
|
||||
_ => return GitInfo::Absent,
|
||||
}
|
||||
|
@ -58,16 +58,17 @@ impl GitInfo {
|
|||
|
||||
// Ok, let's scrape some info
|
||||
let ver_date = output(
|
||||
&mut helpers::git(Some(dir))
|
||||
helpers::git(Some(dir))
|
||||
.arg("log")
|
||||
.arg("-1")
|
||||
.arg("--date=short")
|
||||
.arg("--pretty=format:%cd")
|
||||
.command,
|
||||
.as_command_mut(),
|
||||
);
|
||||
let ver_hash = output(&mut helpers::git(Some(dir)).arg("rev-parse").arg("HEAD").command);
|
||||
let ver_hash =
|
||||
output(helpers::git(Some(dir)).arg("rev-parse").arg("HEAD").as_command_mut());
|
||||
let short_ver_hash = output(
|
||||
&mut helpers::git(Some(dir)).arg("rev-parse").arg("--short=9").arg("HEAD").command,
|
||||
helpers::git(Some(dir)).arg("rev-parse").arg("--short=9").arg("HEAD").as_command_mut(),
|
||||
);
|
||||
GitInfo::Present(Some(Info {
|
||||
commit_date: ver_date.trim().to_string(),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use crate::Build;
|
||||
use build_helper::drop_bomb::DropBomb;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::path::Path;
|
||||
use std::process::{Command, CommandArgs, CommandEnvs, ExitStatus, Output, Stdio};
|
||||
|
||||
|
@ -53,17 +55,20 @@ impl OutputMode {
|
|||
///
|
||||
/// [allow_failure]: BootstrapCommand::allow_failure
|
||||
/// [delay_failure]: BootstrapCommand::delay_failure
|
||||
#[derive(Debug)]
|
||||
pub struct BootstrapCommand {
|
||||
pub command: Command,
|
||||
command: Command,
|
||||
pub failure_behavior: BehaviorOnFailure,
|
||||
pub stdout: OutputMode,
|
||||
pub stderr: OutputMode,
|
||||
// Run the command even during dry run
|
||||
pub run_always: bool,
|
||||
// This field makes sure that each command is executed (or disarmed) before it is dropped,
|
||||
// to avoid forgetting to execute a command.
|
||||
drop_bomb: DropBomb,
|
||||
}
|
||||
|
||||
impl BootstrapCommand {
|
||||
#[track_caller]
|
||||
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
|
||||
Command::new(program).into()
|
||||
}
|
||||
|
@ -142,19 +147,55 @@ impl BootstrapCommand {
|
|||
}
|
||||
|
||||
/// Run the command, returning its output.
|
||||
#[track_caller]
|
||||
pub fn run(&mut self, builder: &Build) -> CommandOutput {
|
||||
builder.run(self)
|
||||
}
|
||||
|
||||
/// Provides access to the stdlib Command inside.
|
||||
/// FIXME: This function should be eventually removed from bootstrap.
|
||||
pub fn as_command_mut(&mut self) -> &mut Command {
|
||||
// We don't know what will happen with the returned command, so we need to mark this
|
||||
// command as executed proactively.
|
||||
self.mark_as_executed();
|
||||
&mut self.command
|
||||
}
|
||||
|
||||
/// Mark the command as being executed, disarming the drop bomb.
|
||||
/// If this method is not called before the command is dropped, its drop will panic.
|
||||
pub fn mark_as_executed(&mut self) {
|
||||
self.drop_bomb.defuse();
|
||||
}
|
||||
|
||||
/// Returns the source code location where this command was created.
|
||||
pub fn get_created_location(&self) -> std::panic::Location<'static> {
|
||||
self.drop_bomb.get_created_location()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BootstrapCommand {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.command)?;
|
||||
write!(
|
||||
f,
|
||||
" (failure_mode={:?}, stdout_mode={:?}, stderr_mode={:?})",
|
||||
self.failure_behavior, self.stdout, self.stderr
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Command> for BootstrapCommand {
|
||||
#[track_caller]
|
||||
fn from(command: Command) -> Self {
|
||||
let program = command.get_program().to_owned();
|
||||
|
||||
Self {
|
||||
command,
|
||||
failure_behavior: BehaviorOnFailure::Exit,
|
||||
stdout: OutputMode::Print,
|
||||
stderr: OutputMode::Print,
|
||||
run_always: false,
|
||||
drop_bomb: DropBomb::arm(program),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +210,7 @@ enum CommandStatus {
|
|||
|
||||
/// Create a new BootstrapCommand. This is a helper function to make command creation
|
||||
/// shorter than `BootstrapCommand::new`.
|
||||
#[track_caller]
|
||||
#[must_use]
|
||||
pub fn command<S: AsRef<OsStr>>(program: S) -> BootstrapCommand {
|
||||
BootstrapCommand::new(program)
|
||||
|
|
|
@ -244,7 +244,7 @@ pub fn is_valid_test_suite_arg<'a, P: AsRef<Path>>(
|
|||
|
||||
// FIXME: get rid of this function
|
||||
pub fn check_run(cmd: &mut BootstrapCommand, print_cmd_on_fail: bool) -> bool {
|
||||
let status = match cmd.command.status() {
|
||||
let status = match cmd.as_command_mut().status() {
|
||||
Ok(status) => status,
|
||||
Err(e) => {
|
||||
println!("failed to execute command: {cmd:?}\nERROR: {e}");
|
||||
|
@ -501,6 +501,7 @@ pub fn check_cfg_arg(name: &str, values: Option<&[&str]>) -> String {
|
|||
/// bootstrap-specific needs/hacks from a single source, rather than applying them on next to every
|
||||
/// git command creation, which is painful to ensure that the required change is applied
|
||||
/// on each one of them correctly.
|
||||
#[track_caller]
|
||||
pub fn git(source_dir: Option<&Path>) -> BootstrapCommand {
|
||||
let mut git = command("git");
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ pub(crate) fn try_run_tests(
|
|||
stream: bool,
|
||||
) -> bool {
|
||||
if builder.config.dry_run() {
|
||||
cmd.mark_as_executed();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -50,7 +51,7 @@ pub(crate) fn try_run_tests(
|
|||
}
|
||||
|
||||
fn run_tests(builder: &Builder<'_>, cmd: &mut BootstrapCommand, stream: bool) -> bool {
|
||||
let cmd = &mut cmd.command;
|
||||
let cmd = cmd.as_command_mut();
|
||||
cmd.stdout(Stdio::piped());
|
||||
|
||||
builder.verbose(|| println!("running: {cmd:?}"));
|
||||
|
|
|
@ -370,11 +370,11 @@ impl<'a> Tarball<'a> {
|
|||
if self.builder.rust_info().is_managed_git_subrepository() {
|
||||
// %ct means committer date
|
||||
let timestamp = helpers::output(
|
||||
&mut helpers::git(Some(&self.builder.src))
|
||||
helpers::git(Some(&self.builder.src))
|
||||
.arg("log")
|
||||
.arg("-1")
|
||||
.arg("--format=%ct")
|
||||
.command,
|
||||
.as_command_mut(),
|
||||
);
|
||||
cmd.args(["--override-file-mtime", timestamp.trim()]);
|
||||
}
|
||||
|
|
|
@ -12,27 +12,31 @@ use std::panic;
|
|||
mod tests;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DropBomb {
|
||||
pub struct DropBomb {
|
||||
command: OsString,
|
||||
defused: bool,
|
||||
armed_line: u32,
|
||||
armed_location: panic::Location<'static>,
|
||||
}
|
||||
|
||||
impl DropBomb {
|
||||
/// Arm a [`DropBomb`]. If the value is dropped without being [`defused`][Self::defused], then
|
||||
/// it will panic. It is expected that the command wrapper uses `#[track_caller]` to help
|
||||
/// propagate the caller info from rmake.rs.
|
||||
/// propagate the caller location.
|
||||
#[track_caller]
|
||||
pub(crate) fn arm<S: AsRef<OsStr>>(command: S) -> DropBomb {
|
||||
pub fn arm<S: AsRef<OsStr>>(command: S) -> DropBomb {
|
||||
DropBomb {
|
||||
command: command.as_ref().into(),
|
||||
defused: false,
|
||||
armed_line: panic::Location::caller().line(),
|
||||
armed_location: *panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_created_location(&self) -> panic::Location<'static> {
|
||||
self.armed_location
|
||||
}
|
||||
|
||||
/// Defuse the [`DropBomb`]. This will prevent the drop bomb from panicking when dropped.
|
||||
pub(crate) fn defuse(&mut self) {
|
||||
pub fn defuse(&mut self) {
|
||||
self.defused = true;
|
||||
}
|
||||
}
|
||||
|
@ -41,8 +45,8 @@ impl Drop for DropBomb {
|
|||
fn drop(&mut self) {
|
||||
if !self.defused && !std::thread::panicking() {
|
||||
panic!(
|
||||
"command constructed but not executed at line {}: `{}`",
|
||||
self.armed_line,
|
||||
"command constructed at `{}` was dropped without being executed: `{}`",
|
||||
self.armed_location,
|
||||
self.command.to_string_lossy()
|
||||
)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
//! Types and functions shared across tools in this workspace.
|
||||
|
||||
pub mod ci;
|
||||
pub mod drop_bomb;
|
||||
pub mod git;
|
||||
pub mod metrics;
|
||||
pub mod stage0_parser;
|
||||
|
|
|
@ -11,3 +11,5 @@ wasmparser = "0.118.2"
|
|||
regex = "1.8" # 1.8 to avoid memchr 2.6.0, as 2.5.0 is pinned in the workspace
|
||||
gimli = "0.28.1"
|
||||
ar = "0.9.0"
|
||||
|
||||
build_helper = { path = "../build_helper" }
|
||||
|
|
|
@ -5,8 +5,8 @@ use std::panic;
|
|||
use std::path::Path;
|
||||
use std::process::{Command as StdCommand, ExitStatus, Output, Stdio};
|
||||
|
||||
use crate::drop_bomb::DropBomb;
|
||||
use crate::{assert_contains, assert_equals, assert_not_contains, handle_failed_output};
|
||||
use build_helper::drop_bomb::DropBomb;
|
||||
|
||||
/// This is a custom command wrapper that simplifies working with commands and makes it easier to
|
||||
/// ensure that we check the exit status of executed processes.
|
||||
|
|
|
@ -2,8 +2,8 @@ use regex::Regex;
|
|||
use similar::TextDiff;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::drop_bomb::DropBomb;
|
||||
use crate::fs_wrapper;
|
||||
use build_helper::drop_bomb::DropBomb;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
@ -7,7 +7,6 @@ pub mod cc;
|
|||
pub mod clang;
|
||||
mod command;
|
||||
pub mod diff;
|
||||
mod drop_bomb;
|
||||
pub mod fs_wrapper;
|
||||
pub mod llvm;
|
||||
pub mod run;
|
||||
|
|
Loading…
Add table
Reference in a new issue