Skip to main content

xtask/
step.rs

1//! Running one step of a task as a child command.
2
3use std::ffi::OsStr;
4use std::process::Command;
5
6/// Run `command`, reporting which step failed.
7///
8/// The step's name and the command line are printed first. A task's steps are
9/// the commands it wraps, so seeing which one is running — and being able to
10/// paste it back into a shell — is most of what makes a slow task legible.
11///
12/// A step inherits the terminal, so the tool's own output — clippy's
13/// diagnostics, nextest's progress — reaches the contributor unchanged.
14pub fn run(command: &mut Command, step: &str) -> Result<(), String> {
15    println!("==> {step}");
16    println!("    {}", describe(command));
17    let status = command
18        .status()
19        .map_err(|error| format!("running {step}: {error}"))?;
20    if status.success() {
21        Ok(())
22    } else {
23        Err(format!(
24            "{step} failed; fix the diagnostics above and run again"
25        ))
26    }
27}
28
29/// The command line `command` would run, quoted the way a shell would need it,
30/// so it can be pasted back into a terminal and run as-is.
31pub fn describe(command: &Command) -> String {
32    let mut line = quote(command.get_program());
33    for argument in command.get_args() {
34        line.push(' ');
35        line.push_str(&quote(argument));
36    }
37    line
38}
39
40/// Quote one word for a shell, leaving it bare where it needs no quoting.
41fn quote(word: &OsStr) -> String {
42    let word = word.to_string_lossy();
43    let bare = !word.is_empty()
44        && word
45            .chars()
46            .all(|c| c.is_ascii_alphanumeric() || "_-./=:,+@".contains(c));
47    if bare {
48        word.into_owned()
49    } else {
50        format!("'{}'", word.replace('\'', r"'\''"))
51    }
52}