use std::collections::HashMap;
use std::env;
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::path::Path;
use std::process::{Command, Output, Stdio};
use failure::Fail;
use jobserver::Client;
use shell_escape::escape;
use crate::util::{process_error, read2, CargoResult, CargoResultExt};
#[derive(Clone, Debug)]
pub struct ProcessBuilder {
program: OsString,
args: Vec<OsString>,
env: HashMap<String, Option<OsString>>,
cwd: Option<OsString>,
jobserver: Option<Client>,
display_env_vars: bool,
}
impl fmt::Display for ProcessBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "`")?;
if self.display_env_vars {
for (key, val) in self.env.iter() {
if let Some(val) = val {
let val = escape(val.to_string_lossy());
if cfg!(windows) {
write!(f, "set {}={}&& ", key, val)?;
} else {
write!(f, "{}={} ", key, val)?;
}
}
}
}
write!(f, "{}", self.program.to_string_lossy())?;
for arg in &self.args {
write!(f, " {}", escape(arg.to_string_lossy()))?;
}
write!(f, "`")
}
}
impl ProcessBuilder {
pub fn program<T: AsRef<OsStr>>(&mut self, program: T) -> &mut ProcessBuilder {
self.program = program.as_ref().to_os_string();
self
}
pub fn arg<T: AsRef<OsStr>>(&mut self, arg: T) -> &mut ProcessBuilder {
self.args.push(arg.as_ref().to_os_string());
self
}
pub fn args<T: AsRef<OsStr>>(&mut self, args: &[T]) -> &mut ProcessBuilder {
self.args
.extend(args.iter().map(|t| t.as_ref().to_os_string()));
self
}
pub fn args_replace<T: AsRef<OsStr>>(&mut self, args: &[T]) -> &mut ProcessBuilder {
self.args = args.iter().map(|t| t.as_ref().to_os_string()).collect();
self
}
pub fn cwd<T: AsRef<OsStr>>(&mut self, path: T) -> &mut ProcessBuilder {
self.cwd = Some(path.as_ref().to_os_string());
self
}
pub fn env<T: AsRef<OsStr>>(&mut self, key: &str, val: T) -> &mut ProcessBuilder {
self.env
.insert(key.to_string(), Some(val.as_ref().to_os_string()));
self
}
pub fn env_remove(&mut self, key: &str) -> &mut ProcessBuilder {
self.env.insert(key.to_string(), None);
self
}
pub fn get_program(&self) -> &OsString {
&self.program
}
pub fn get_args(&self) -> &[OsString] {
&self.args
}
pub fn get_cwd(&self) -> Option<&Path> {
self.cwd.as_ref().map(Path::new)
}
pub fn get_env(&self, var: &str) -> Option<OsString> {
self.env
.get(var)
.cloned()
.or_else(|| Some(env::var_os(var)))
.and_then(|s| s)
}
pub fn get_envs(&self) -> &HashMap<String, Option<OsString>> {
&self.env
}
pub fn inherit_jobserver(&mut self, jobserver: &Client) -> &mut Self {
self.jobserver = Some(jobserver.clone());
self
}
pub fn display_env_vars(&mut self) -> &mut Self {
self.display_env_vars = true;
self
}
pub fn exec(&self) -> CargoResult<()> {
let mut command = self.build_command();
let exit = command.status().chain_err(|| {
process_error(&format!("could not execute process {}", self), None, None)
})?;
if exit.success() {
Ok(())
} else {
Err(process_error(
&format!("process didn't exit successfully: {}", self),
Some(exit),
None,
)
.into())
}
}
pub fn exec_replace(&self) -> CargoResult<()> {
imp::exec_replace(self)
}
pub fn exec_with_output(&self) -> CargoResult<Output> {
let mut command = self.build_command();
let output = command.output().chain_err(|| {
process_error(&format!("could not execute process {}", self), None, None)
})?;
if output.status.success() {
Ok(output)
} else {
Err(process_error(
&format!("process didn't exit successfully: {}", self),
Some(output.status),
Some(&output),
)
.into())
}
}
pub fn exec_with_streaming(
&self,
on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>,
on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>,
capture_output: bool,
) -> CargoResult<Output> {
let mut stdout = Vec::new();
let mut stderr = Vec::new();
let mut cmd = self.build_command();
cmd.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null());
let mut callback_error = None;
let status = (|| {
let mut child = cmd.spawn()?;
let out = child.stdout.take().unwrap();
let err = child.stderr.take().unwrap();
read2(out, err, &mut |is_out, data, eof| {
let idx = if eof {
data.len()
} else {
match data.iter().rposition(|b| *b == b'\n') {
Some(i) => i + 1,
None => return,
}
};
{
let new_lines = if capture_output {
let dst = if is_out { &mut stdout } else { &mut stderr };
let start = dst.len();
let data = data.drain(..idx);
dst.extend(data);
&dst[start..]
} else {
&data[..idx]
};
for line in String::from_utf8_lossy(new_lines).lines() {
if callback_error.is_some() {
break;
}
let callback_result = if is_out {
on_stdout_line(line)
} else {
on_stderr_line(line)
};
if let Err(e) = callback_result {
callback_error = Some(e);
}
}
}
if !capture_output {
data.drain(..idx);
}
})?;
child.wait()
})()
.chain_err(|| process_error(&format!("could not execute process {}", self), None, None))?;
let output = Output {
stdout,
stderr,
status,
};
{
let to_print = if capture_output { Some(&output) } else { None };
if let Some(e) = callback_error {
let cx = process_error(
&format!("failed to parse process output: {}", self),
Some(output.status),
to_print,
);
return Err(cx.context(e).into());
} else if !output.status.success() {
return Err(process_error(
&format!("process didn't exit successfully: {}", self),
Some(output.status),
to_print,
)
.into());
}
}
Ok(output)
}
pub fn build_command(&self) -> Command {
let mut command = Command::new(&self.program);
if let Some(cwd) = self.get_cwd() {
command.current_dir(cwd);
}
for arg in &self.args {
command.arg(arg);
}
for (k, v) in &self.env {
match *v {
Some(ref v) => {
command.env(k, v);
}
None => {
command.env_remove(k);
}
}
}
if let Some(ref c) = self.jobserver {
c.configure(&mut command);
}
command
}
}
pub fn process<T: AsRef<OsStr>>(cmd: T) -> ProcessBuilder {
ProcessBuilder {
program: cmd.as_ref().to_os_string(),
args: Vec::new(),
cwd: None,
env: HashMap::new(),
jobserver: None,
display_env_vars: false,
}
}
#[cfg(unix)]
mod imp {
use crate::util::{process_error, ProcessBuilder};
use crate::CargoResult;
use std::os::unix::process::CommandExt;
pub fn exec_replace(process_builder: &ProcessBuilder) -> CargoResult<()> {
let mut command = process_builder.build_command();
let error = command.exec();
Err(failure::Error::from(error)
.context(process_error(
&format!("could not execute process {}", process_builder),
None,
None,
))
.into())
}
}
#[cfg(windows)]
mod imp {
use crate::util::{process_error, ProcessBuilder};
use crate::CargoResult;
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
use winapi::um::consoleapi::SetConsoleCtrlHandler;
unsafe extern "system" fn ctrlc_handler(_: DWORD) -> BOOL {
TRUE
}
pub fn exec_replace(process_builder: &ProcessBuilder) -> CargoResult<()> {
unsafe {
if SetConsoleCtrlHandler(Some(ctrlc_handler), TRUE) == FALSE {
return Err(process_error("Could not set Ctrl-C handler.", None, None).into());
}
}
process_builder.exec()
}
}