use std::env;
use std::fmt;
use std::process::{Command, ExitStatus};
use eyre::{bail, Error};
#[cfg(feature = "full")]
use lazy_static::lazy_static;
#[cfg(feature = "full")]
use regex::Regex;
#[cfg(feature = "full")]
use super::r#macro::Set as MacroSet;
use super::target::Target;
use super::token::{Token, TokenString};
use super::Makefile;
fn execute_command_line(
command_line: &str,
ignore_errors: bool,
#[cfg(feature = "full")] macros: &MacroSet,
) -> Result<ExitStatus, Error> {
let (program, args) = if cfg!(windows) {
let cmd = env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".into());
let args = vec!["/c", command_line];
(cmd, args)
} else {
let sh = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".into());
let args = if ignore_errors {
vec!["-c", command_line]
} else {
vec!["-e", "-c", command_line]
};
(sh, args)
};
let mut command = Command::new(program);
command.args(args);
#[cfg(feature = "full")]
command.envs(macros.resolve_exports::<&[u8]>(None)?);
Ok(command.status()?)
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct CommandLine {
execution_line: TokenString,
}
impl CommandLine {
pub const fn from(line: TokenString) -> Self {
Self {
execution_line: line,
}
}
pub fn execute(&self, file: &Makefile, target: &Target) -> eyre::Result<()> {
let is_recursive = self.execution_line.tokens().any(|x| match x {
Token::MacroExpansion { name, .. } => name == "MAKE",
_ => false,
});
log::trace!("executing {}", &self.execution_line);
let execution_line = file.expand_macros(&self.execution_line, Some(target))?;
#[cfg(feature = "full")]
{
let is_just_one_macro_expansion = self.execution_line.tokens().count() == 1
&& self.execution_line.tokens().all(|x| {
matches!(x, Token::MacroExpansion { .. } | Token::FunctionCall { .. })
});
lazy_static! {
static ref UNESCAPED_NEWLINE: Regex = #[allow(clippy::unwrap_used)]
Regex::new(r"([^\\])\n").unwrap();
}
if is_just_one_macro_expansion && UNESCAPED_NEWLINE.is_match(&execution_line) {
let lines = UNESCAPED_NEWLINE
.split(&execution_line)
.map(|x| Self::from(TokenString::text(x.trim_start())));
for line in lines {
line.execute(file, target)?;
}
return Ok(());
}
}
log::trace!("executing {}", &execution_line);
let mut ignore_errors = false;
let mut silent = false;
let mut always_execute = false;
let execution_line: String = {
let mut line_chars = execution_line
.chars()
.skip_while(char::is_ascii_whitespace)
.peekable();
while let Some(x) = line_chars.next_if(|x| matches!(x, '-' | '@' | '+')) {
match x {
'-' => ignore_errors = true,
'@' => silent = true,
'+' => always_execute = true,
_ => unreachable!(),
}
}
line_chars.collect()
};
let ignore_error = ignore_errors
|| file.args.ignore_errors
|| file.special_target_has_prereq(".IGNORE", &target.name);
let silent = (silent && !file.args.dry_run)
|| file.args.silent
|| file.special_target_has_prereq(".SILENT", &target.name);
if !silent {
println!("{}", execution_line);
}
let should_execute = always_execute
|| is_recursive
|| !(file.args.dry_run || file.args.question || file.args.touch);
if !should_execute {
return Ok(());
}
let return_value = execute_command_line(
&execution_line,
ignore_error,
#[cfg(feature = "full")]
&file.macros,
);
let errored = return_value.map_or(true, |status| !status.success());
if errored {
if !ignore_error {
bail!("error from command execution!");
}
}
Ok(())
}
}
impl fmt::Display for CommandLine {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let execution_line = format!("{}", &self.execution_line);
let execution_line = execution_line.replace("\n", "↵\n");
write!(f, "{}", execution_line)?;
Ok(())
}
}