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};
#[derive(Debug, Copy, Clone)]
pub enum BehaviorOnFailure {
Exit,
DelayFail,
Ignore,
}
#[derive(Debug, Copy, Clone)]
pub enum OutputMode {
Print,
Capture,
}
impl OutputMode {
pub fn captures(&self) -> bool {
match self {
OutputMode::Print => false,
OutputMode::Capture => true,
}
}
pub fn stdio(&self) -> Stdio {
match self {
OutputMode::Print => Stdio::inherit(),
OutputMode::Capture => Stdio::piped(),
}
}
}
pub struct BootstrapCommand {
command: Command,
pub failure_behavior: BehaviorOnFailure,
pub stdout: OutputMode,
pub stderr: OutputMode,
pub run_always: bool,
drop_bomb: DropBomb,
}
impl BootstrapCommand {
#[track_caller]
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
Command::new(program).into()
}
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
self.command.arg(arg.as_ref());
self
}
pub fn args<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.command.args(args);
self
}
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.command.env(key, val);
self
}
pub fn get_envs(&self) -> CommandEnvs<'_> {
self.command.get_envs()
}
pub fn get_args(&self) -> CommandArgs<'_> {
self.command.get_args()
}
pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Self {
self.command.env_remove(key);
self
}
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
self.command.current_dir(dir);
self
}
#[must_use]
pub fn delay_failure(self) -> Self {
Self { failure_behavior: BehaviorOnFailure::DelayFail, ..self }
}
#[must_use]
pub fn fail_fast(self) -> Self {
Self { failure_behavior: BehaviorOnFailure::Exit, ..self }
}
#[must_use]
pub fn allow_failure(self) -> Self {
Self { failure_behavior: BehaviorOnFailure::Ignore, ..self }
}
pub fn run_always(&mut self) -> &mut Self {
self.run_always = true;
self
}
#[must_use]
pub fn capture(self) -> Self {
Self { stdout: OutputMode::Capture, stderr: OutputMode::Capture, ..self }
}
#[must_use]
pub fn capture_stdout(self) -> Self {
Self { stdout: OutputMode::Capture, ..self }
}
#[track_caller]
pub fn run(&mut self, builder: &Build) -> CommandOutput {
builder.run(self)
}
pub fn as_command_mut(&mut self) -> &mut Command {
self.mark_as_executed();
&mut self.command
}
pub fn mark_as_executed(&mut self) {
self.drop_bomb.defuse();
}
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),
}
}
}
enum CommandStatus {
Finished(ExitStatus),
DidNotStart,
}
#[track_caller]
#[must_use]
pub fn command<S: AsRef<OsStr>>(program: S) -> BootstrapCommand {
BootstrapCommand::new(program)
}
#[allow(unused)]
pub struct CommandOutput {
status: CommandStatus,
stdout: Vec<u8>,
stderr: Vec<u8>,
}
impl CommandOutput {
#[must_use]
pub fn did_not_start() -> Self {
Self { status: CommandStatus::DidNotStart, stdout: vec![], stderr: vec![] }
}
#[must_use]
pub fn is_success(&self) -> bool {
match self.status {
CommandStatus::Finished(status) => status.success(),
CommandStatus::DidNotStart => false,
}
}
#[must_use]
pub fn is_failure(&self) -> bool {
!self.is_success()
}
#[must_use]
pub fn status(&self) -> Option<ExitStatus> {
match self.status {
CommandStatus::Finished(status) => Some(status),
CommandStatus::DidNotStart => None,
}
}
#[must_use]
pub fn stdout(&self) -> String {
String::from_utf8(self.stdout.clone()).expect("Cannot parse process stdout as UTF-8")
}
#[must_use]
pub fn stdout_if_ok(&self) -> Option<String> {
if self.is_success() { Some(self.stdout()) } else { None }
}
#[must_use]
pub fn stderr(&self) -> String {
String::from_utf8(self.stderr.clone()).expect("Cannot parse process stderr as UTF-8")
}
}
impl Default for CommandOutput {
fn default() -> Self {
Self {
status: CommandStatus::Finished(ExitStatus::default()),
stdout: vec![],
stderr: vec![],
}
}
}
impl From<Output> for CommandOutput {
fn from(output: Output) -> Self {
Self {
status: CommandStatus::Finished(output.status),
stdout: output.stdout,
stderr: output.stderr,
}
}
}