2018-12-31 13:26:17 +02:00
|
|
|
//! Utilities for command execution
|
2018-08-26 16:12:59 +03:00
|
|
|
use std::ffi::{OsStr, OsString};
|
|
|
|
|
use std::path::Path;
|
2022-11-08 05:54:35 -05:00
|
|
|
use std::process::{Child, Command, ExitStatus, Output};
|
|
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
use log::debug;
|
|
|
|
|
|
|
|
|
|
use crate::command::CommandExt;
|
|
|
|
|
use crate::error::DryRun;
|
2018-08-26 16:12:59 +03:00
|
|
|
|
2018-12-31 13:26:17 +02:00
|
|
|
/// An enum telling whether Topgrade should perform dry runs or actually perform the steps.
|
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
|
|
|
pub enum RunType {
|
|
|
|
|
/// Executing commands will just print the command with its argument.
|
|
|
|
|
Dry,
|
|
|
|
|
|
|
|
|
|
/// Executing commands will perform actual execution.
|
|
|
|
|
Wet,
|
2018-08-26 16:12:59 +03:00
|
|
|
}
|
|
|
|
|
|
2018-12-31 13:26:17 +02:00
|
|
|
impl RunType {
|
|
|
|
|
/// Create a new instance from a boolean telling whether to dry run.
|
2018-12-31 14:05:15 +02:00
|
|
|
pub fn new(dry_run: bool) -> Self {
|
2018-12-31 13:26:17 +02:00
|
|
|
if dry_run {
|
|
|
|
|
RunType::Dry
|
|
|
|
|
} else {
|
|
|
|
|
RunType::Wet
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create an instance of `Executor` that should run `program`.
|
|
|
|
|
pub fn execute<S: AsRef<OsStr>>(self, program: S) -> Executor {
|
|
|
|
|
match self {
|
|
|
|
|
RunType::Dry => Executor::Dry(DryCommand {
|
2018-08-26 16:12:59 +03:00
|
|
|
program: program.as_ref().into(),
|
|
|
|
|
..Default::default()
|
2018-12-31 13:26:17 +02:00
|
|
|
}),
|
|
|
|
|
RunType::Wet => Executor::Wet(Command::new(program)),
|
2018-08-26 16:12:59 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-31 13:26:17 +02:00
|
|
|
/// Tells whether we're performing a dry run.
|
|
|
|
|
pub fn dry(self) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
RunType::Dry => true,
|
|
|
|
|
RunType::Wet => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// An enum providing a similar interface to `std::process::Command`.
|
|
|
|
|
/// If the enum is set to `Wet`, execution will be performed with `std::process::Command`.
|
|
|
|
|
/// If the enum is set to `Dry`, execution will just print the command with its arguments.
|
|
|
|
|
pub enum Executor {
|
|
|
|
|
Wet(Command),
|
|
|
|
|
Dry(DryCommand),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Executor {
|
2022-11-08 05:54:35 -05:00
|
|
|
/// Get the name of the program being run.
|
|
|
|
|
///
|
|
|
|
|
/// Will give weird results for non-UTF-8 programs; see `to_string_lossy()`.
|
|
|
|
|
pub fn get_program(&self) -> String {
|
|
|
|
|
match self {
|
|
|
|
|
Executor::Wet(c) => c.get_program().to_string_lossy().into_owned(),
|
|
|
|
|
Executor::Dry(c) => c.program.to_string_lossy().into_owned(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-31 13:26:17 +02:00
|
|
|
/// See `std::process::Command::arg`
|
2018-08-26 16:12:59 +03:00
|
|
|
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Executor {
|
|
|
|
|
match self {
|
|
|
|
|
Executor::Wet(c) => {
|
|
|
|
|
c.arg(arg);
|
|
|
|
|
}
|
|
|
|
|
Executor::Dry(c) => {
|
|
|
|
|
c.args.push(arg.as_ref().into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-31 13:26:17 +02:00
|
|
|
/// See `std::process::Command::args`
|
2018-08-26 16:12:59 +03:00
|
|
|
pub fn args<I, S>(&mut self, args: I) -> &mut Executor
|
|
|
|
|
where
|
|
|
|
|
I: IntoIterator<Item = S>,
|
|
|
|
|
S: AsRef<OsStr>,
|
|
|
|
|
{
|
|
|
|
|
match self {
|
|
|
|
|
Executor::Wet(c) => {
|
|
|
|
|
c.args(args);
|
|
|
|
|
}
|
|
|
|
|
Executor::Dry(c) => {
|
|
|
|
|
c.args.extend(args.into_iter().map(|arg| arg.as_ref().into()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-22 08:55:50 +03:00
|
|
|
#[allow(dead_code)]
|
2018-12-31 13:26:17 +02:00
|
|
|
/// See `std::process::Command::current_dir`
|
2018-08-26 16:12:59 +03:00
|
|
|
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Executor {
|
|
|
|
|
match self {
|
|
|
|
|
Executor::Wet(c) => {
|
|
|
|
|
c.current_dir(dir);
|
|
|
|
|
}
|
|
|
|
|
Executor::Dry(c) => c.directory = Some(dir.as_ref().into()),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-24 11:17:03 +01:00
|
|
|
#[allow(dead_code)]
|
2020-02-29 16:30:31 +02:00
|
|
|
/// See `std::process::Command::remove_env`
|
|
|
|
|
pub fn env_remove<K>(&mut self, key: K) -> &mut Executor
|
|
|
|
|
where
|
|
|
|
|
K: AsRef<OsStr>,
|
|
|
|
|
{
|
|
|
|
|
match self {
|
|
|
|
|
Executor::Wet(c) => {
|
|
|
|
|
c.env_remove(key);
|
|
|
|
|
}
|
|
|
|
|
Executor::Dry(_) => (),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-15 09:15:35 +03:00
|
|
|
#[allow(dead_code)]
|
|
|
|
|
/// See `std::process::Command::env`
|
|
|
|
|
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Executor
|
|
|
|
|
where
|
|
|
|
|
K: AsRef<OsStr>,
|
|
|
|
|
V: AsRef<OsStr>,
|
|
|
|
|
{
|
|
|
|
|
match self {
|
|
|
|
|
Executor::Wet(c) => {
|
|
|
|
|
c.env(key, val);
|
|
|
|
|
}
|
|
|
|
|
Executor::Dry(_) => (),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-31 13:26:17 +02:00
|
|
|
/// See `std::process::Command::spawn`
|
2019-12-11 23:05:38 +02:00
|
|
|
pub fn spawn(&mut self) -> Result<ExecutorChild> {
|
2018-12-11 16:43:26 +02:00
|
|
|
let result = match self {
|
2020-02-27 15:05:34 +02:00
|
|
|
Executor::Wet(c) => {
|
|
|
|
|
debug!("Running {:?}", c);
|
2022-11-08 05:54:35 -05:00
|
|
|
c.spawn_checked().map(ExecutorChild::Wet)?
|
2020-02-27 15:05:34 +02:00
|
|
|
}
|
2018-08-26 16:12:59 +03:00
|
|
|
Executor::Dry(c) => {
|
2019-05-16 11:08:52 +03:00
|
|
|
c.dry_run();
|
2018-12-11 16:43:26 +02:00
|
|
|
ExecutorChild::Dry
|
2018-08-26 16:12:59 +03:00
|
|
|
}
|
2018-12-11 16:43:26 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
2018-08-26 16:12:59 +03:00
|
|
|
}
|
2018-12-31 22:00:34 +02:00
|
|
|
|
2019-05-16 11:08:52 +03:00
|
|
|
/// See `std::process::Command::output`
|
2019-12-11 23:05:38 +02:00
|
|
|
pub fn output(&mut self) -> Result<ExecutorOutput> {
|
2019-05-16 11:08:52 +03:00
|
|
|
match self {
|
2022-11-08 05:54:35 -05:00
|
|
|
Executor::Wet(c) => Ok(ExecutorOutput::Wet(c.output_checked()?)),
|
2019-05-16 11:08:52 +03:00
|
|
|
Executor::Dry(c) => {
|
|
|
|
|
c.dry_run();
|
|
|
|
|
Ok(ExecutorOutput::Dry)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-08 05:54:35 -05:00
|
|
|
/// An extension of `status_checked` that allows you to set a sequence of codes
|
2020-12-01 19:56:12 -08:00
|
|
|
/// that can indicate success of a script
|
2022-11-08 05:54:35 -05:00
|
|
|
#[cfg_attr(windows, allow(dead_code))]
|
|
|
|
|
pub fn status_checked_with_codes(&mut self, codes: &[i32]) -> Result<()> {
|
|
|
|
|
match self {
|
|
|
|
|
Executor::Wet(c) => c.status_checked_with(|status| match status.code() {
|
|
|
|
|
Some(code) => {
|
|
|
|
|
if codes.contains(&code) {
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => Err(()),
|
|
|
|
|
}),
|
|
|
|
|
Executor::Dry(c) => {
|
|
|
|
|
c.dry_run();
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-01 19:56:12 -08:00
|
|
|
}
|
2018-08-26 16:12:59 +03:00
|
|
|
}
|
|
|
|
|
|
2019-05-16 11:08:52 +03:00
|
|
|
pub enum ExecutorOutput {
|
2022-11-08 05:54:35 -05:00
|
|
|
Wet(Output),
|
2019-05-16 11:08:52 +03:00
|
|
|
Dry,
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-31 13:26:17 +02:00
|
|
|
/// A struct represending a command. Trying to execute it will just print its arguments.
|
2018-08-26 16:12:59 +03:00
|
|
|
#[derive(Default)]
|
|
|
|
|
pub struct DryCommand {
|
|
|
|
|
program: OsString,
|
|
|
|
|
args: Vec<OsString>,
|
|
|
|
|
directory: Option<OsString>,
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-16 11:08:52 +03:00
|
|
|
impl DryCommand {
|
|
|
|
|
fn dry_run(&self) {
|
|
|
|
|
print!(
|
|
|
|
|
"Dry running: {} {}",
|
|
|
|
|
self.program.to_string_lossy(),
|
2022-11-03 12:46:43 -04:00
|
|
|
shell_words::join(
|
|
|
|
|
self.args
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|a| String::from(a.to_string_lossy()))
|
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
)
|
2019-05-16 11:08:52 +03:00
|
|
|
);
|
|
|
|
|
match &self.directory {
|
|
|
|
|
Some(dir) => println!(" in {}", dir.to_string_lossy()),
|
|
|
|
|
None => println!(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-31 13:26:17 +02:00
|
|
|
/// The Result of spawn. Contains an actual `std::process::Child` if executed by a wet command.
|
2018-08-26 16:12:59 +03:00
|
|
|
pub enum ExecutorChild {
|
|
|
|
|
Wet(Child),
|
|
|
|
|
Dry,
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-08 05:54:35 -05:00
|
|
|
impl CommandExt for Executor {
|
|
|
|
|
type Child = ExecutorChild;
|
2018-08-26 16:12:59 +03:00
|
|
|
|
2022-11-08 05:54:35 -05:00
|
|
|
// TODO: It might be nice to make `output_checked_with` return something that has a
|
|
|
|
|
// variant for wet/dry runs.
|
2022-11-23 15:23:00 +00:00
|
|
|
|
2022-11-08 05:54:35 -05:00
|
|
|
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> anyhow::Result<Output> {
|
2020-12-01 19:56:12 -08:00
|
|
|
match self {
|
2022-11-08 05:54:35 -05:00
|
|
|
Executor::Wet(c) => c.output_checked_with(succeeded),
|
|
|
|
|
Executor::Dry(c) => {
|
|
|
|
|
c.dry_run();
|
|
|
|
|
Err(DryRun().into())
|
|
|
|
|
}
|
2022-11-23 15:23:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-08 05:54:35 -05:00
|
|
|
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> anyhow::Result<()> {
|
|
|
|
|
match self {
|
|
|
|
|
Executor::Wet(c) => c.status_checked_with(succeeded),
|
|
|
|
|
Executor::Dry(c) => {
|
|
|
|
|
c.dry_run();
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2020-12-16 13:43:38 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-08 05:54:35 -05:00
|
|
|
fn spawn_checked(&mut self) -> anyhow::Result<Self::Child> {
|
|
|
|
|
self.spawn()
|
2020-12-16 13:43:38 +02:00
|
|
|
}
|
|
|
|
|
}
|