blob: 53fa3e0f43d9e000107c8d9beed8d54d40da0815 [file] [log] [blame]
use std::{
ffi::OsString,
path::{Path, PathBuf},
process::Command,
};
#[derive(Debug, Clone)]
/// A command, its args and its environment. Used for
/// the main command, the dependency builder and the cfg-reader.
pub struct CommandBuilder {
/// Path to the binary.
pub program: PathBuf,
/// Arguments to the binary.
pub args: Vec<OsString>,
/// A flag to prefix before the path to where output files should be written.
pub out_dir_flag: Option<OsString>,
/// A flag to set as the last flag in the command, so the `build` caller can
/// append the filename themselves.
pub input_file_flag: Option<OsString>,
/// Environment variables passed to the binary that is executed.
/// The environment variable is removed if the second tuple field is `None`
pub envs: Vec<(OsString, Option<OsString>)>,
}
impl CommandBuilder {
/// Uses the `CARGO` env var or just a program named `cargo` and the argument `build`.
pub fn cargo() -> Self {
Self {
program: PathBuf::from(std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into())),
args: vec!["build".into()],
out_dir_flag: Some("--target-dir".into()),
input_file_flag: Some("--manifest-path".into()),
envs: vec![],
}
}
/// Uses the `RUSTC` env var or just a program named `rustc` and the argument `--error-format=json`.
///
/// Take care to only append unless you actually meant to overwrite the defaults.
/// Overwriting the defaults may make `//~ ERROR` style comments stop working.
pub fn rustc() -> Self {
Self {
program: PathBuf::from(std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into())),
args: vec!["--error-format=json".into()],
out_dir_flag: Some("--out-dir".into()),
input_file_flag: None,
envs: vec![],
}
}
/// Same as [`CommandBuilder::rustc`], but with arguments for obtaining the cfgs.
pub fn cfgs() -> Self {
Self {
args: vec!["--print".into(), "cfg".into()],
..Self::rustc()
}
}
/// Build a `CommandBuilder` for a command without any argumemnts.
/// You can still add arguments later.
pub fn cmd(cmd: impl Into<PathBuf>) -> Self {
Self {
program: cmd.into(),
args: vec![],
out_dir_flag: None,
input_file_flag: None,
envs: vec![],
}
}
/// Render the command like you'd use it on a command line.
pub fn display(&self) -> impl std::fmt::Display + '_ {
struct Display<'a>(&'a CommandBuilder);
impl std::fmt::Display for Display<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (var, val) in &self.0.envs {
if let Some(val) = val {
write!(f, "{var:?}={val:?} ")?;
}
}
self.0.program.display().fmt(f)?;
for arg in &self.0.args {
write!(f, " {arg:?}")?;
}
if let Some(flag) = &self.0.out_dir_flag {
write!(f, " {flag:?} OUT_DIR")?;
}
if let Some(flag) = &self.0.input_file_flag {
write!(f, " {flag:?}")?;
}
Ok(())
}
}
Display(self)
}
/// Create a command with the given settings.
pub fn build(&self, out_dir: &Path) -> Command {
let mut cmd = Command::new(&self.program);
cmd.args(self.args.iter());
if let Some(flag) = &self.out_dir_flag {
cmd.arg(flag).arg(out_dir);
}
if let Some(flag) = &self.input_file_flag {
cmd.arg(flag);
}
self.apply_env(&mut cmd);
cmd
}
pub(crate) fn apply_env(&self, cmd: &mut Command) {
for (var, val) in self.envs.iter() {
if let Some(val) = val {
cmd.env(var, val);
} else {
cmd.env_remove(var);
}
}
}
}