cli/
command.rs

1//! # command
2//!
3//! Runs task commands/scripts.
4//!
5
6#[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
21/// Returns the exit code (-1 if no exit code found)
22pub(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
57/// Validates the exit code and if not 0 or unable to validate it, err
58pub(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                // if log level defaults to not printing the script commands
94                // we also check if we are running in a CI env.
95                // Users will not see the commands while CI builds will have the commands printed out.
96                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
111/// Runs the requested script text and returns its output.
112pub(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
145/// Runs the requested script text and panics in case of any script error.
146pub(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
166/// Runs the requested command and return its output.
167pub(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
264/// Runs the requested command and panics in case of any error.
265pub(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
281/// Runs the requested command and returns the stdout if exit code is valid.
282pub(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
303/// Runs the given task command.
304pub(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}