1#[cfg(test)]
7#[path = "command_test.rs"]
8mod command_test;
9
10use crate::error::CargoMakeError;
11use crate::logger;
12use crate::toolchain;
13use crate::types::{CommandSpec, Step, UnstableFeature};
14use run_script::{IoOptions, ScriptError, ScriptOptions};
15use std::io;
16use std::io::{Error, ErrorKind, Read};
17use std::process::{Command, ExitStatus, Output, Stdio};
18use std::sync::atomic::{AtomicU32, Ordering};
19use std::sync::Once;
20
21pub(crate) fn get_exit_code(exit_status: Result<ExitStatus, Error>, force: bool) -> i32 {
23 match exit_status {
24 Ok(code) => {
25 if !code.success() {
26 match code.code() {
27 Some(value) => value,
28 None => -1,
29 }
30 } else {
31 0
32 }
33 }
34 Err(error) => {
35 if !force {
36 error!("Error while executing command, error: {:#?}", error);
37 }
38
39 -1
40 }
41 }
42}
43
44pub(crate) fn get_exit_code_from_output(output: &io::Result<Output>, force: bool) -> i32 {
45 match output {
46 &Ok(ref output_struct) => get_exit_code(Ok(output_struct.status), force),
47 &Err(ref error) => {
48 if !force {
49 error!("Error while executing command, error: {:#?}", error);
50 }
51
52 -1
53 }
54 }
55}
56
57pub(crate) fn validate_exit_code(code: i32) -> Result<(), CargoMakeError> {
59 if code == -1 {
60 Err(CargoMakeError::ExitCodeValidation)
61 } else if code != 0 {
62 Err(CargoMakeError::ExitCodeError(code))
63 } else {
64 Ok(())
65 }
66}
67
68fn is_silent() -> bool {
69 let log_level = logger::get_log_level();
70 is_silent_for_level(log_level)
71}
72
73fn is_silent_for_level(log_level: String) -> bool {
74 let level = logger::get_level(&log_level);
75
76 match level {
77 logger::LogLevel::ERROR => true,
78 logger::LogLevel::OFF => true,
79 _ => false,
80 }
81}
82
83fn should_print_commands_by_default() -> bool {
84 let log_level = logger::get_log_level();
85 let level = logger::get_level(&log_level);
86
87 match level {
88 logger::LogLevel::OFF => false,
89 _ => {
90 if should_print_commands_for_level(log_level) {
91 true
92 } else {
93 envmnt::is("CARGO_MAKE_CI")
97 }
98 }
99 }
100}
101
102fn should_print_commands_for_level(log_level: String) -> bool {
103 let level = logger::get_level(&log_level);
104
105 match level {
106 logger::LogLevel::VERBOSE => true,
107 _ => false,
108 }
109}
110
111pub(crate) fn run_script_get_output(
113 script_lines: &Vec<String>,
114 script_runner: Option<String>,
115 cli_arguments: &Vec<String>,
116 capture_output: bool,
117 print_commands: Option<bool>,
118) -> Result<(i32, String, String), ScriptError> {
119 let silent = is_silent();
120 let mut options = ScriptOptions::new();
121 options.runner = script_runner.clone();
122 options.output_redirection = if silent {
123 IoOptions::Null
124 } else if capture_output {
125 IoOptions::Pipe
126 } else {
127 IoOptions::Inherit
128 };
129 options.exit_on_error = true;
130 options.print_commands = match print_commands {
131 Some(bool_value) => bool_value,
132 None => should_print_commands_by_default(),
133 };
134
135 if is_silent() {
136 options.output_redirection = IoOptions::Pipe;
137 options.print_commands = false;
138 } else if !capture_output && envmnt::is("CARGO_MAKE_SCRIPT_FORCE_PIPE_STDIN") {
139 options.input_redirection = IoOptions::Pipe;
140 }
141
142 run_script::run(script_lines.join("\n").as_str(), cli_arguments, &options)
143}
144
145pub(crate) fn run_script_get_exit_code(
147 script_lines: &Vec<String>,
148 script_runner: Option<String>,
149 cli_arguments: &Vec<String>,
150 validate: bool,
151) -> Result<i32, CargoMakeError> {
152 let output = run_script_get_output(&script_lines, script_runner, cli_arguments, false, None);
153
154 let exit_code = match output {
155 Ok(output_struct) => output_struct.0,
156 _ => -1,
157 };
158
159 if validate {
160 validate_exit_code(exit_code)?;
161 }
162
163 Ok(exit_code)
164}
165
166pub(crate) fn run_command_get_output(
168 command_string: &str,
169 args: &Option<Vec<String>>,
170 capture_output: bool,
171) -> io::Result<Output> {
172 let ctrl_c_handling = UnstableFeature::CtrlCHandling.is_env_set();
173 let silent = is_silent();
174
175 debug!("Execute Command: {}", &command_string);
176 let mut command = Command::new(&command_string);
177
178 match *args {
179 Some(ref args_vec) => {
180 command.args(args_vec);
181 }
182 None => debug!("No command args defined."),
183 };
184
185 command.stdin(Stdio::inherit());
186
187 if silent {
188 command.stdout(Stdio::null()).stderr(Stdio::null());
189 } else if ctrl_c_handling {
190 if capture_output {
191 command.stdout(Stdio::piped()).stderr(Stdio::piped());
192 }
193 } else if !capture_output {
194 command.stdout(Stdio::inherit()).stderr(Stdio::inherit());
195 }
196
197 info!("Execute Command: {:?}", &command);
198
199 let output = if ctrl_c_handling {
200 spawn_command(command)
201 } else {
202 command.output()
203 };
204
205 debug!("Output: {:#?}", &output);
206
207 output
208}
209
210fn spawn_command(mut command: Command) -> io::Result<Output> {
211 static CTRL_C_COUNT: AtomicU32 = AtomicU32::new(0);
212 static SET_CTRL_C_HANDLER_ONCE: Once = Once::new();
213
214 SET_CTRL_C_HANDLER_ONCE.call_once(|| {
215 ctrlc::set_handler(|| {
216 if CTRL_C_COUNT.fetch_add(1, Ordering::SeqCst) == 0 {
217 info!("Shutting down...");
218 }
219 })
220 .expect("Failed to set Ctrl+C handler.");
221 });
222
223 if CTRL_C_COUNT.load(Ordering::Relaxed) != 0 {
224 Err(Error::new(
225 ErrorKind::Other,
226 "Shutting down - cannot run the command.",
227 ))?;
228 }
229
230 let mut process = command.spawn()?;
231 let process_stdout = process.stdout.take();
232 let process_stderr = process.stderr.take();
233
234 let mut killing_process = false;
235
236 Ok(loop {
237 if !killing_process && CTRL_C_COUNT.load(Ordering::Relaxed) >= 2 {
238 process.kill()?;
239 killing_process = true;
240 }
241
242 if let Some(status) = process.try_wait()? {
243 let mut stdout = Vec::new();
244 if let Some(mut process_stdout) = process_stdout {
245 process_stdout.read_to_end(&mut stdout)?;
246 }
247
248 let mut stderr = Vec::new();
249 if let Some(mut process_stderr) = process_stderr {
250 process_stderr.read_to_end(&mut stderr)?;
251 }
252
253 break Output {
254 status,
255 stdout,
256 stderr,
257 };
258 } else {
259 std::thread::sleep(std::time::Duration::from_millis(10));
260 }
261 })
262}
263
264pub(crate) fn run_command(
266 command_string: &str,
267 args: &Option<Vec<String>>,
268 validate: bool,
269) -> Result<i32, CargoMakeError> {
270 let output = run_command_get_output(&command_string, &args, false);
271
272 let exit_code = get_exit_code_from_output(&output, !validate);
273
274 if validate {
275 validate_exit_code(exit_code)?;
276 }
277
278 Ok(exit_code)
279}
280
281pub(crate) fn run_command_get_output_string(
283 command_string: &str,
284 args: &Option<Vec<String>>,
285) -> Option<String> {
286 let output = run_command_get_output(&command_string, &args, true);
287
288 let exit_code = get_exit_code_from_output(&output, true);
289
290 if exit_code == 0 {
291 match output {
292 Ok(output_struct) => {
293 let stdout = String::from_utf8_lossy(&output_struct.stdout).into_owned();
294 Some(stdout)
295 }
296 Err(_) => None,
297 }
298 } else {
299 None
300 }
301}
302
303pub(crate) fn run(step: &Step) -> Result<(), CargoMakeError> {
305 let validate = !step.config.should_ignore_errors();
306
307 match step.config.command {
308 Some(ref command_string) => {
309 let command_spec = match step.config.toolchain {
310 Some(ref toolchain) => {
311 toolchain::wrap_command(&toolchain, &command_string, &step.config.args)
312 }
313 None => CommandSpec {
314 command: command_string.to_string(),
315 args: step.config.args.clone(),
316 },
317 };
318
319 run_command(&command_spec.command, &command_spec.args, validate)?;
320 }
321 None => debug!("No command defined."),
322 };
323 Ok(())
324}