summaryrefslogtreecommitdiffstats
path: root/vendor/ui_test/src/cmd.rs
blob: 63d055e2886d637e0d2220e2af45cf771f669f51 (plain)
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
use std::{
    ffi::OsString,
    path::{Path, PathBuf},
    process::Command,
};

#[derive(Debug, Clone)]
/// A command, its args and its environment. Used for
/// the main command, the dependency builder and the cfg-reader.
pub struct CommandBuilder {
    /// Path to the binary.
    pub program: PathBuf,
    /// Arguments to the binary.
    pub args: Vec<OsString>,
    /// A flag to prefix before the path to where output files should be written.
    pub out_dir_flag: Option<OsString>,
    /// A flag to set as the last flag in the command, so the `build` caller can
    /// append the filename themselves.
    pub input_file_flag: Option<OsString>,
    /// Environment variables passed to the binary that is executed.
    /// The environment variable is removed if the second tuple field is `None`
    pub envs: Vec<(OsString, Option<OsString>)>,
}

impl CommandBuilder {
    /// Uses the `CARGO` env var or just a program named `cargo` and the argument `build`.
    pub fn cargo() -> Self {
        Self {
            program: PathBuf::from(std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into())),
            args: vec!["build".into()],
            out_dir_flag: Some("--target-dir".into()),
            input_file_flag: Some("--manifest-path".into()),
            envs: vec![],
        }
    }

    /// Uses the `RUSTC` env var or just a program named `rustc` and the argument `--error-format=json`.
    ///
    /// Take care to only append unless you actually meant to overwrite the defaults.
    /// Overwriting the defaults may make `//~ ERROR` style comments stop working.
    pub fn rustc() -> Self {
        Self {
            program: PathBuf::from(std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into())),
            args: vec!["--error-format=json".into()],
            out_dir_flag: Some("--out-dir".into()),
            input_file_flag: None,
            envs: vec![],
        }
    }

    /// Same as [`rustc`], but with arguments for obtaining the cfgs.
    pub fn cfgs() -> Self {
        Self {
            args: vec!["--print".into(), "cfg".into()],
            ..Self::rustc()
        }
    }

    /// Build a `CommandBuilder` for a command without any argumemnts.
    /// You can still add arguments later.
    pub fn cmd(cmd: impl Into<PathBuf>) -> Self {
        Self {
            program: cmd.into(),
            args: vec![],
            out_dir_flag: None,
            input_file_flag: None,
            envs: vec![],
        }
    }

    /// Render the command like you'd use it on a command line.
    pub fn display(&self) -> impl std::fmt::Display + '_ {
        struct Display<'a>(&'a CommandBuilder);
        impl std::fmt::Display for Display<'_> {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                for (var, val) in &self.0.envs {
                    if let Some(val) = val {
                        write!(f, "{var:?}={val:?} ")?;
                    }
                }
                self.0.program.display().fmt(f)?;
                for arg in &self.0.args {
                    write!(f, " {arg:?}")?;
                }
                if let Some(flag) = &self.0.out_dir_flag {
                    write!(f, " {flag:?} OUT_DIR")?;
                }
                if let Some(flag) = &self.0.input_file_flag {
                    write!(f, " {flag:?}")?;
                }
                Ok(())
            }
        }
        Display(self)
    }

    /// Create a command with the given settings.
    pub fn build(&self, out_dir: &Path) -> Command {
        let mut cmd = Command::new(&self.program);
        cmd.args(self.args.iter());
        if let Some(flag) = &self.out_dir_flag {
            cmd.arg(flag).arg(out_dir);
        }
        if let Some(flag) = &self.input_file_flag {
            cmd.arg(flag);
        }
        self.apply_env(&mut cmd);
        cmd
    }

    pub(crate) fn apply_env(&self, cmd: &mut Command) {
        for (var, val) in self.envs.iter() {
            if let Some(val) = val {
                cmd.env(var, val);
            } else {
                cmd.env_remove(var);
            }
        }
    }
}