Remove temporary BootstrapCommand trait impls

This commit is contained in:
Jakub Beránek 2024-06-22 12:32:55 +02:00 committed by Jakub Beránek
parent a34d0a8d5f
commit b14ff77c04
9 changed files with 114 additions and 143 deletions

View File

@ -2080,7 +2080,7 @@ pub fn stream_cargo(
tail_args: Vec<String>, tail_args: Vec<String>,
cb: &mut dyn FnMut(CargoMessage<'_>), cb: &mut dyn FnMut(CargoMessage<'_>),
) -> bool { ) -> bool {
let mut cargo = BootstrapCommand::from(cargo).command; let mut cargo = cargo.into_cmd().command;
// Instruct Cargo to give us json messages on stdout, critically leaving // Instruct Cargo to give us json messages on stdout, critically leaving
// stderr as piped so we can get those pretty colors. // stderr as piped so we can get those pretty colors.
let mut message_format = if builder.config.json_output { let mut message_format = if builder.config.json_output {

View File

@ -738,7 +738,7 @@ fn doc_std(
format!("library{} in {} format", crate_description(requested_crates), format.as_str()); format!("library{} in {} format", crate_description(requested_crates), format.as_str());
let _guard = builder.msg_doc(compiler, description, target); let _guard = builder.msg_doc(compiler, description, target);
builder.run(cargo); builder.run(cargo.into_cmd());
builder.cp_link_r(&out_dir, out); builder.cp_link_r(&out_dir, out);
} }
@ -863,7 +863,7 @@ impl Step for Rustc {
let proc_macro_out_dir = builder.stage_out(compiler, Mode::Rustc).join("doc"); let proc_macro_out_dir = builder.stage_out(compiler, Mode::Rustc).join("doc");
symlink_dir_force(&builder.config, &out, &proc_macro_out_dir); symlink_dir_force(&builder.config, &out, &proc_macro_out_dir);
builder.run(cargo); builder.run(cargo.into_cmd());
if !builder.config.dry_run() { if !builder.config.dry_run() {
// Sanity check on linked compiler crates // Sanity check on linked compiler crates
@ -996,7 +996,7 @@ macro_rules! tool_doc {
symlink_dir_force(&builder.config, &out, &proc_macro_out_dir); symlink_dir_force(&builder.config, &out, &proc_macro_out_dir);
let _guard = builder.msg_doc(compiler, stringify!($tool).to_lowercase(), target); let _guard = builder.msg_doc(compiler, stringify!($tool).to_lowercase(), target);
builder.run(cargo); builder.run(cargo.into_cmd());
if !builder.config.dry_run() { if !builder.config.dry_run() {
// Sanity check on linked doc directories // Sanity check on linked doc directories

View File

@ -160,16 +160,15 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) {
override_builder.add(&format!("!{ignore}")).expect(&ignore); override_builder.add(&format!("!{ignore}")).expect(&ignore);
} }
} }
let git_available = build let git_available =
.run(helpers::git(None).print_on_failure().allow_failure().arg("--version")) build.run(helpers::git(None).capture().allow_failure().arg("--version")).is_success();
.is_success();
let mut adjective = None; let mut adjective = None;
if git_available { if git_available {
let in_working_tree = build let in_working_tree = build
.run( .run(
helpers::git(Some(&build.src)) helpers::git(Some(&build.src))
.print_on_failure() .capture()
.allow_failure() .allow_failure()
.arg("rev-parse") .arg("rev-parse")
.arg("--is-inside-work-tree"), .arg("--is-inside-work-tree"),

View File

@ -158,7 +158,7 @@ impl Step for Miri {
// after another --, so this must be at the end. // after another --, so this must be at the end.
miri.args(builder.config.args()); miri.args(builder.config.args());
builder.run(miri); builder.run(miri.into_cmd());
} }
} }

View File

@ -26,7 +26,7 @@ use crate::core::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
use crate::core::config::flags::get_completion; use crate::core::config::flags::get_completion;
use crate::core::config::flags::Subcommand; use crate::core::config::flags::Subcommand;
use crate::core::config::TargetSelection; use crate::core::config::TargetSelection;
use crate::utils::exec::{BootstrapCommand, OutputMode}; use crate::utils::exec::BootstrapCommand;
use crate::utils::helpers::{ use crate::utils::helpers::{
self, add_link_lib_path, add_rustdoc_cargo_linker_args, dylib_path, dylib_path_var, self, add_link_lib_path, add_rustdoc_cargo_linker_args, dylib_path, dylib_path_var,
linker_args, linker_flags, output, t, target_supports_cranelift_backend, up_to_date, linker_args, linker_flags, output, t, target_supports_cranelift_backend, up_to_date,
@ -150,16 +150,13 @@ You can skip linkcheck with --skip src/tools/linkchecker"
builder.default_doc(&[]); builder.default_doc(&[]);
// Build the linkchecker before calling `msg`, since GHA doesn't support nested groups. // Build the linkchecker before calling `msg`, since GHA doesn't support nested groups.
let mut linkchecker = builder.tool_cmd(Tool::Linkchecker); let linkchecker = builder.tool_cmd(Tool::Linkchecker);
// Run the linkchecker. // Run the linkchecker.
let _guard = let _guard =
builder.msg(Kind::Test, compiler.stage, "Linkcheck", bootstrap_host, bootstrap_host); builder.msg(Kind::Test, compiler.stage, "Linkcheck", bootstrap_host, bootstrap_host);
let _time = helpers::timeit(builder); let _time = helpers::timeit(builder);
builder.run( builder.run(linkchecker.delay_failure().arg(builder.out.join(host.triple).join("doc")));
BootstrapCommand::from(linkchecker.arg(builder.out.join(host.triple).join("doc")))
.delay_failure(),
);
} }
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
@ -217,10 +214,7 @@ impl Step for HtmlCheck {
)); ));
builder.run( builder.run(
BootstrapCommand::from( builder.tool_cmd(Tool::HtmlChecker).delay_failure().arg(builder.doc_out(self.target)),
builder.tool_cmd(Tool::HtmlChecker).arg(builder.doc_out(self.target)),
)
.delay_failure(),
); );
} }
} }
@ -260,14 +254,13 @@ impl Step for Cargotest {
let _time = helpers::timeit(builder); let _time = helpers::timeit(builder);
let mut cmd = builder.tool_cmd(Tool::CargoTest); let mut cmd = builder.tool_cmd(Tool::CargoTest);
let cmd = cmd cmd.arg(&cargo)
.arg(&cargo)
.arg(&out_dir) .arg(&out_dir)
.args(builder.config.test_args()) .args(builder.config.test_args())
.env("RUSTC", builder.rustc(compiler)) .env("RUSTC", builder.rustc(compiler))
.env("RUSTDOC", builder.rustdoc(compiler)); .env("RUSTDOC", builder.rustdoc(compiler));
add_rustdoc_cargo_linker_args(cmd, builder, compiler.host, LldThreads::No); add_rustdoc_cargo_linker_args(&mut cmd, builder, compiler.host, LldThreads::No);
builder.run(BootstrapCommand::from(cmd).delay_failure()); builder.run(cmd.delay_failure());
} }
} }
@ -763,12 +756,12 @@ impl Step for Clippy {
cargo.env("HOST_LIBS", host_libs); cargo.env("HOST_LIBS", host_libs);
cargo.add_rustc_lib_path(builder); cargo.add_rustc_lib_path(builder);
let mut cargo = prepare_cargo_test(cargo, &[], &[], "clippy", compiler, host, builder); let cargo = prepare_cargo_test(cargo, &[], &[], "clippy", compiler, host, builder);
let _guard = builder.msg_sysroot_tool(Kind::Test, compiler.stage, "clippy", host, host); let _guard = builder.msg_sysroot_tool(Kind::Test, compiler.stage, "clippy", host, host);
// Clippy reports errors if it blessed the outputs // Clippy reports errors if it blessed the outputs
if builder.run(BootstrapCommand::from(&mut cargo).allow_failure()).is_success() { if builder.run(cargo.allow_failure()).is_success() {
// The tests succeeded; nothing to do. // The tests succeeded; nothing to do.
return; return;
} }
@ -821,7 +814,7 @@ impl Step for RustdocTheme {
.env("RUSTC_BOOTSTRAP", "1"); .env("RUSTC_BOOTSTRAP", "1");
cmd.args(linker_args(builder, self.compiler.host, LldThreads::No)); cmd.args(linker_args(builder, self.compiler.host, LldThreads::No));
builder.run(BootstrapCommand::from(&mut cmd).delay_failure()); builder.run(cmd.delay_failure());
} }
} }
@ -1099,7 +1092,7 @@ HELP: to skip test's attempt to check tidiness, pass `--skip src/tools/tidy` to
} }
builder.info("tidy check"); builder.info("tidy check");
builder.run(BootstrapCommand::from(&mut cmd).delay_failure()); builder.run(cmd.delay_failure());
builder.info("x.py completions check"); builder.info("x.py completions check");
let [bash, zsh, fish, powershell] = ["x.py.sh", "x.py.zsh", "x.py.fish", "x.py.ps1"] let [bash, zsh, fish, powershell] = ["x.py.sh", "x.py.zsh", "x.py.fish", "x.py.ps1"]
@ -1306,7 +1299,7 @@ impl Step for RunMakeSupport {
&[], &[],
); );
builder.run(cargo); builder.run(cargo.into_cmd());
let lib_name = "librun_make_support.rlib"; let lib_name = "librun_make_support.rlib";
let lib = builder.tools_dir(self.compiler).join(lib_name); let lib = builder.tools_dir(self.compiler).join(lib_name);
@ -2187,7 +2180,7 @@ impl BookTest {
compiler.host, compiler.host,
); );
let _time = helpers::timeit(builder); let _time = helpers::timeit(builder);
let cmd = BootstrapCommand::from(&mut rustbook_cmd).delay_failure(); let cmd = rustbook_cmd.delay_failure();
let toolstate = let toolstate =
if builder.run(cmd).is_success() { ToolState::TestPass } else { ToolState::TestFail }; if builder.run(cmd).is_success() { ToolState::TestPass } else { ToolState::TestFail };
builder.save_toolstate(self.name, toolstate); builder.save_toolstate(self.name, toolstate);
@ -2318,7 +2311,7 @@ impl Step for ErrorIndex {
let guard = let guard =
builder.msg(Kind::Test, compiler.stage, "error-index", compiler.host, compiler.host); builder.msg(Kind::Test, compiler.stage, "error-index", compiler.host, compiler.host);
let _time = helpers::timeit(builder); let _time = helpers::timeit(builder);
builder.run(BootstrapCommand::from(&mut tool).output_mode(OutputMode::OnlyOnFailure)); builder.run(tool.capture());
drop(guard); drop(guard);
// The tests themselves need to link to std, so make sure it is // The tests themselves need to link to std, so make sure it is
// available. // available.
@ -2349,7 +2342,7 @@ fn markdown_test(builder: &Builder<'_>, compiler: Compiler, markdown: &Path) ->
cmd = cmd.delay_failure(); cmd = cmd.delay_failure();
if !builder.config.verbose_tests { if !builder.config.verbose_tests {
cmd = cmd.print_on_failure(); cmd = cmd.capture();
} }
builder.run(cmd).is_success() builder.run(cmd).is_success()
} }
@ -2375,10 +2368,13 @@ impl Step for RustcGuide {
builder.update_submodule(&relative_path); builder.update_submodule(&relative_path);
let src = builder.src.join(relative_path); let src = builder.src.join(relative_path);
let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook); let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook).delay_failure();
let cmd = BootstrapCommand::from(rustbook_cmd.arg("linkcheck").arg(&src)).delay_failure(); rustbook_cmd.arg("linkcheck").arg(&src);
let toolstate = let toolstate = if builder.run(rustbook_cmd).is_success() {
if builder.run(cmd).is_success() { ToolState::TestPass } else { ToolState::TestFail }; ToolState::TestPass
} else {
ToolState::TestFail
};
builder.save_toolstate("rustc-dev-guide", toolstate); builder.save_toolstate("rustc-dev-guide", toolstate);
} }
} }
@ -3347,7 +3343,7 @@ impl Step for CodegenCranelift {
.arg("testsuite.extended_sysroot"); .arg("testsuite.extended_sysroot");
cargo.args(builder.config.test_args()); cargo.args(builder.config.test_args());
builder.run(cargo); builder.run(cargo.into_cmd());
} }
} }
@ -3472,6 +3468,6 @@ impl Step for CodegenGCC {
.arg("--std-tests"); .arg("--std-tests");
cargo.args(builder.config.test_args()); cargo.args(builder.config.test_args());
builder.run(cargo); builder.run(cargo.into_cmd());
} }
} }

View File

@ -603,7 +603,7 @@ impl Step for Rustdoc {
&self.compiler.host, &self.compiler.host,
&target, &target,
); );
builder.run(cargo); builder.run(cargo.into_cmd());
// Cargo adds a number of paths to the dylib search path on windows, which results in // Cargo adds a number of paths to the dylib search path on windows, which results in
// the wrong rustdoc being executed. To avoid the conflicting rustdocs, we name the "tool" // the wrong rustdoc being executed. To avoid the conflicting rustdocs, we name the "tool"
@ -858,7 +858,7 @@ impl Step for LlvmBitcodeLinker {
&self.extra_features, &self.extra_features,
); );
builder.run(cargo); builder.run(cargo.into_cmd());
let tool_out = builder let tool_out = builder
.cargo_out(self.compiler, Mode::ToolRustc, self.target) .cargo_out(self.compiler, Mode::ToolRustc, self.target)

View File

@ -2398,6 +2398,10 @@ impl Cargo {
cargo cargo
} }
pub fn into_cmd(self) -> BootstrapCommand {
self.into()
}
/// Same as `Cargo::new` except this one doesn't configure the linker with `Cargo::configure_linker` /// Same as `Cargo::new` except this one doesn't configure the linker with `Cargo::configure_linker`
pub fn new_for_mir_opt_tests( pub fn new_for_mir_opt_tests(
builder: &Builder<'_>, builder: &Builder<'_>,
@ -2622,9 +2626,3 @@ impl From<Cargo> for BootstrapCommand {
cargo.command cargo.command
} }
} }
impl From<Cargo> for Command {
fn from(cargo: Cargo) -> Command {
BootstrapCommand::from(cargo).command
}
}

View File

@ -555,10 +555,7 @@ impl Build {
// Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error). // Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error).
// diff-index reports the modifications through the exit status // diff-index reports the modifications through the exit status
let has_local_modifications = self let has_local_modifications = self
.run( .run(submodule_git().allow_failure().args(["diff-index", "--quiet", "HEAD"]))
BootstrapCommand::from(submodule_git().args(["diff-index", "--quiet", "HEAD"]))
.allow_failure(),
)
.is_failure(); .is_failure();
if has_local_modifications { if has_local_modifications {
self.run(submodule_git().args(["stash", "push"])); self.run(submodule_git().args(["stash", "push"]));
@ -582,7 +579,7 @@ impl Build {
let output = self let output = self
.run( .run(
helpers::git(Some(&self.src)) helpers::git(Some(&self.src))
.quiet() .capture()
.args(["config", "--file"]) .args(["config", "--file"])
.arg(self.config.src.join(".gitmodules")) .arg(self.config.src.join(".gitmodules"))
.args(["--get-regexp", "path"]), .args(["--get-regexp", "path"]),
@ -937,69 +934,71 @@ impl Build {
/// Execute a command and return its output. /// Execute a command and return its output.
/// This method should be used for all command executions in bootstrap. /// This method should be used for all command executions in bootstrap.
fn run<C: Into<BootstrapCommand>>(&self, command: C) -> CommandOutput { fn run<C: AsMut<BootstrapCommand>>(&self, mut command: C) -> CommandOutput {
if self.config.dry_run() { if self.config.dry_run() {
return CommandOutput::default(); return CommandOutput::default();
} }
let mut command = command.into(); let command = command.as_mut();
self.verbose(|| println!("running: {command:?}")); self.verbose(|| println!("running: {command:?}"));
let output_mode = command.output_mode.unwrap_or_else(|| match self.is_verbose() { let output: io::Result<CommandOutput> = match command.output_mode {
true => OutputMode::All, OutputMode::Print => command.command.status().map(|status| status.into()),
false => OutputMode::OnlyOutput, OutputMode::CaptureAll => command.command.output().map(|o| o.into()),
}); OutputMode::CaptureStdout => {
let (output, print_error): (io::Result<CommandOutput>, bool) = match output_mode { command.command.stderr(Stdio::inherit());
mode @ (OutputMode::All | OutputMode::OnlyOutput) => ( command.command.output().map(|o| o.into())
command.command.status().map(|status| status.into()), }
matches!(mode, OutputMode::All),
),
mode @ (OutputMode::OnlyOnFailure | OutputMode::Quiet) => (
command.command.output().map(|o| o.into()),
matches!(mode, OutputMode::OnlyOnFailure),
),
}; };
let output = match output { let output = match output {
Ok(output) => output, Ok(output) => output,
Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", command, e)), Err(e) => fail(&format!("failed to execute command: {command:?}\nerror: {e}")),
}; };
if !output.is_success() { if !output.is_success() {
if print_error { use std::fmt::Write;
println!(
"\n\nCommand did not execute successfully.\
\nExpected success, got: {}",
output.status(),
);
if !self.is_verbose() { // Here we build an error message, and below we decide if it should be printed or not.
println!("Add `-v` to see more details.\n"); let mut message = String::new();
writeln!(
message,
"\n\nCommand {command:?} did not execute successfully.\
\nExpected success, got: {}",
output.status(),
)
.unwrap();
// If the output mode is OutputMode::Print, the output has already been printed to
// stdout/stderr, and we thus don't have anything captured to print anyway.
if matches!(command.output_mode, OutputMode::CaptureAll | OutputMode::CaptureStdout) {
writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap();
// Stderr is added to the message only if it was captured
if matches!(command.output_mode, OutputMode::CaptureAll) {
writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap();
} }
self.verbose(|| {
println!(
"\nSTDOUT ----\n{}\n\
STDERR ----\n{}\n",
output.stdout(),
output.stderr(),
)
});
} }
match command.failure_behavior { match command.failure_behavior {
BehaviorOnFailure::DelayFail => { BehaviorOnFailure::DelayFail => {
if self.fail_fast { if self.fail_fast {
println!("{message}");
exit!(1); exit!(1);
} }
let mut failures = self.delayed_failures.borrow_mut(); let mut failures = self.delayed_failures.borrow_mut();
failures.push(format!("{command:?}")); failures.push(message);
} }
BehaviorOnFailure::Exit => { BehaviorOnFailure::Exit => {
println!("{message}");
exit!(1); exit!(1);
} }
BehaviorOnFailure::Ignore => {} BehaviorOnFailure::Ignore => {
// If failures are allowed, either the error has been printed already
// (OutputMode::Print) or the user used a capture output mode and wants to
// handle the error output on their own.
}
} }
} }
output output
@ -1490,7 +1489,7 @@ impl Build {
// (Note that we use a `..` range, not the `...` symmetric difference.) // (Note that we use a `..` range, not the `...` symmetric difference.)
self.run( self.run(
helpers::git(Some(&self.src)) helpers::git(Some(&self.src))
.quiet() .capture()
.arg("rev-list") .arg("rev-list")
.arg("--count") .arg("--count")
.arg("--merges") .arg("--merges")

View File

@ -13,18 +13,19 @@ pub enum BehaviorOnFailure {
Ignore, Ignore,
} }
/// How should the output of the command be handled. /// How should the output of the command be handled (whether it should be captured or printed).
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum OutputMode { pub enum OutputMode {
/// Print both the output (by inheriting stdout/stderr) and also the command itself, if it /// Prints the stdout/stderr of the command to stdout/stderr of bootstrap (by inheriting these
/// fails. /// streams).
All, /// Corresponds to calling `cmd.status()`.
/// Print the output (by inheriting stdout/stderr). Print,
OnlyOutput, /// Captures the stdout and stderr of the command into memory.
/// Suppress the output if the command succeeds, otherwise print the output. /// Corresponds to calling `cmd.output()`.
OnlyOnFailure, CaptureAll,
/// Suppress the output of the command. /// Captures the stdout of the command into memory, inherits stderr.
Quiet, /// Corresponds to calling `cmd.output()`.
CaptureStdout,
} }
/// Wrapper around `std::process::Command`. /// Wrapper around `std::process::Command`.
@ -34,10 +35,10 @@ pub enum OutputMode {
/// If you want to delay failures until the end of bootstrap, use [delay_failure]. /// If you want to delay failures until the end of bootstrap, use [delay_failure].
/// ///
/// By default, the command will print its stdout/stderr to stdout/stderr of bootstrap /// By default, the command will print its stdout/stderr to stdout/stderr of bootstrap
/// ([OutputMode::OnlyOutput]). If bootstrap uses verbose mode, then it will also print the /// ([OutputMode::Print]).
/// command itself in case of failure ([OutputMode::All]). /// If you want to handle the output programmatically, use [BootstrapCommand::capture].
/// If you want to handle the output programmatically, use `output_mode(OutputMode::OnlyOnFailure)`, ///
/// which will print the output only if the command fails. /// Bootstrap will print a debug log to stdout if the command fails and failure is not allowed.
/// ///
/// [allow_failure]: BootstrapCommand::allow_failure /// [allow_failure]: BootstrapCommand::allow_failure
/// [delay_failure]: BootstrapCommand::delay_failure /// [delay_failure]: BootstrapCommand::delay_failure
@ -45,12 +46,16 @@ pub enum OutputMode {
pub struct BootstrapCommand { pub struct BootstrapCommand {
pub command: Command, pub command: Command,
pub failure_behavior: BehaviorOnFailure, pub failure_behavior: BehaviorOnFailure,
pub output_mode: Option<OutputMode>, pub output_mode: OutputMode,
} }
impl BootstrapCommand { impl BootstrapCommand {
pub fn new<S: AsRef<OsStr>>(program: S) -> Self { pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
Command::new(program).into() Self {
command: Command::new(program),
failure_behavior: BehaviorOnFailure::Exit,
output_mode: OutputMode::Print,
}
} }
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self { pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
@ -106,52 +111,22 @@ impl BootstrapCommand {
Self { failure_behavior: BehaviorOnFailure::Ignore, ..self } Self { failure_behavior: BehaviorOnFailure::Ignore, ..self }
} }
/// Do not print the output of the command, unless it fails. /// Capture the output of the command, do not print it.
pub fn print_on_failure(self) -> Self { pub fn capture(self) -> Self {
self.output_mode(OutputMode::OnlyOnFailure) Self { output_mode: OutputMode::CaptureAll, ..self }
} }
/// Do not print the output of the command. /// Capture stdout of the command, do not print it.
pub fn quiet(self) -> Self { pub fn capture_stdout(self) -> Self {
self.output_mode(OutputMode::Quiet) Self { output_mode: OutputMode::CaptureStdout, ..self }
}
pub fn output_mode(self, output_mode: OutputMode) -> Self {
Self { output_mode: Some(output_mode), ..self }
} }
} }
/// FIXME: This implementation is temporary, until all `Command` invocations are migrated to /// This implementation exists to make it possible to pass both [BootstrapCommand] and
/// `BootstrapCommand`. /// `&mut BootstrapCommand` to `Build.run()`.
impl<'a> From<&'a mut BootstrapCommand> for BootstrapCommand { impl AsMut<BootstrapCommand> for BootstrapCommand {
fn from(command: &'a mut BootstrapCommand) -> Self { fn as_mut(&mut self) -> &mut BootstrapCommand {
// This is essentially a manual `Command::clone` self
let mut cmd = Command::new(command.command.get_program());
if let Some(dir) = command.command.get_current_dir() {
cmd.current_dir(dir);
}
cmd.args(command.command.get_args());
for (key, value) in command.command.get_envs() {
match value {
Some(value) => {
cmd.env(key, value);
}
None => {
cmd.env_remove(key);
}
}
}
Self {
command: cmd,
output_mode: command.output_mode,
failure_behavior: command.failure_behavior,
}
}
}
impl From<Command> for BootstrapCommand {
fn from(command: Command) -> Self {
Self { command, failure_behavior: BehaviorOnFailure::Exit, output_mode: None }
} }
} }
@ -176,6 +151,10 @@ impl CommandOutput {
String::from_utf8(self.0.stdout.clone()).expect("Cannot parse process stdout as UTF-8") String::from_utf8(self.0.stdout.clone()).expect("Cannot parse process stdout as UTF-8")
} }
pub fn stdout_if_ok(&self) -> Option<String> {
if self.is_success() { Some(self.stdout()) } else { None }
}
pub fn stderr(&self) -> String { pub fn stderr(&self) -> String {
String::from_utf8(self.0.stderr.clone()).expect("Cannot parse process stderr as UTF-8") String::from_utf8(self.0.stderr.clone()).expect("Cannot parse process stderr as UTF-8")
} }