1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
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<ExitStatus, io::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)
};
Command::new(program).args(args).status()
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct CommandLine {
/// If the command prefix contains a <hyphen-minus>, 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 <plus-sign>, 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(())
}
}
|