use std::env; use std::fmt; use std::io; use std::process::{Command, ExitStatus}; use crate::makefile::target::Target; use crate::makefile::token::{Token, TokenString}; use crate::makefile::Makefile; // inspired by python's subprocess module fn execute_command_line(command_line: &str, ignore_errors: bool) -> Result { 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) }; Command::new(program).args(args).status() } #[derive(PartialEq, Eq, Clone, Debug)] pub struct CommandLine { /// If the command prefix contains a , or the -i option is present, or /// the special target .IGNORE has either the current target as a prerequisite or has /// no prerequisites, any error found while executing the command shall be ignored. ignore_errors: bool, /// If the command prefix contains an at-sign and the make utility command line -n /// option is not specified, or the -s option is present, or the special target /// .SILENT has either the current target as a prerequisite or has no prerequisites, /// the command shall not be written to standard output before it is executed. silent: bool, /// If the command prefix contains a , this indicates a makefile command /// line that shall be executed even if -n, -q, or -t is specified. always_execute: bool, execution_line: TokenString, } impl CommandLine { pub fn from(mut line: TokenString) -> Self { let mut ignore_errors = false; let mut silent = false; let mut always_execute = false; if let Token::Text(text) = line.first_token_mut() { let mut text_chars = text.chars().peekable(); while let Some(x) = text_chars.next_if(|x| matches!(x, '-' | '@' | '+')) { match x { '-' => ignore_errors = true, '@' => silent = true, '+' => always_execute = true, _ => unreachable!(), } } *text = text_chars.collect(); } Self { ignore_errors, silent, always_execute, execution_line: line, } } pub fn execute(&self, file: &Makefile, target: &Target) -> anyhow::Result<()> { let ignore_error = self.ignore_errors || file.args.ignore_errors || file.special_target_has_prereq(".IGNORE", &target.name); let silent = (self.silent && !file.args.dry_run) || file.args.silent || file.special_target_has_prereq(".SILENT", &target.name); let execution_line = file.expand_macros(&self.execution_line, Some(target))?; if !silent { println!("{}", execution_line); } let should_execute = self.always_execute || !(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); let errored = return_value.map_or(true, |status| !status.success()); if errored { // apparently there was an error. do we care? if !ignore_error { anyhow::bail!("error from command execution!"); } } Ok(()) } } impl fmt::Display for CommandLine { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.ignore_errors { write!(f, "-")?; } if self.silent { write!(f, "@")?; } if self.always_execute { write!(f, "+")?; } let execution_line = format!("{}", &self.execution_line); let execution_line = execution_line.replace("\n", "↵\n"); write!(f, "{}", execution_line)?; Ok(()) } }