ui_test/
cmd.rs

1use crate::display;
2use std::{
3    ffi::OsString,
4    path::{Path, PathBuf},
5    process::Command,
6};
7
8#[derive(Debug, Clone)]
9/// A command, its args and its environment. Used for
10/// the main command, the dependency builder and the cfg-reader.
11pub struct CommandBuilder {
12    /// Path to the binary.
13    pub program: PathBuf,
14    /// Arguments to the binary.
15    pub args: Vec<OsString>,
16    /// A flag to prefix before the path to where output files should be written.
17    pub out_dir_flag: Option<OsString>,
18    /// A flag to set as the last flag in the command, so the `build` caller can
19    /// append the filename themselves.
20    pub input_file_flag: Option<OsString>,
21    /// Environment variables passed to the binary that is executed.
22    /// The environment variable is removed if the second tuple field is `None`
23    pub envs: Vec<(OsString, Option<OsString>)>,
24    /// A flag to add in order to make the command print the cfgs it supports
25    pub cfg_flag: Option<OsString>,
26}
27
28impl CommandBuilder {
29    /// Uses the `CARGO` env var or just a program named `cargo` and the argument `build`.
30    pub fn cargo() -> Self {
31        Self {
32            program: PathBuf::from(std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into())),
33            args: vec![
34                "build".into(),
35                "--color=never".into(),
36                "--quiet".into(),
37                "--jobs".into(),
38                "1".into(),
39            ],
40            out_dir_flag: Some("--target-dir".into()),
41            input_file_flag: Some("--manifest-path".into()),
42            envs: vec![],
43            cfg_flag: None,
44        }
45    }
46
47    /// Uses the `RUSTC` env var or just a program named `rustc` and the argument `--error-format=json`.
48    ///
49    /// Take care to only append unless you actually meant to overwrite the defaults.
50    /// Overwriting the defaults may make `//~ ERROR` style comments stop working.
51    pub fn rustc() -> Self {
52        Self {
53            program: PathBuf::from(std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into())),
54            args: vec!["--error-format=json".into()],
55            out_dir_flag: Some("--out-dir".into()),
56            input_file_flag: None,
57            envs: vec![],
58            cfg_flag: Some("--print=cfg".into()),
59        }
60    }
61
62    /// Build a `CommandBuilder` for a command without any argumemnts.
63    /// You can still add arguments later.
64    pub fn cmd(cmd: impl Into<PathBuf>) -> Self {
65        Self {
66            program: cmd.into(),
67            args: vec![],
68            out_dir_flag: None,
69            input_file_flag: None,
70            envs: vec![],
71            cfg_flag: None,
72        }
73    }
74
75    /// Render the command like you'd use it on a command line.
76    pub fn display(&self) -> impl std::fmt::Display + '_ {
77        struct Display<'a>(&'a CommandBuilder);
78        impl std::fmt::Display for Display<'_> {
79            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80                for (var, val) in &self.0.envs {
81                    if let Some(val) = val {
82                        write!(f, "{var:?}={val:?} ")?;
83                    }
84                }
85                display(&self.0.program).fmt(f)?;
86                for arg in &self.0.args {
87                    write!(f, " {arg:?}")?;
88                }
89                if let Some(flag) = &self.0.out_dir_flag {
90                    write!(f, " {flag:?} OUT_DIR")?;
91                }
92                if let Some(flag) = &self.0.input_file_flag {
93                    write!(f, " {flag:?}")?;
94                }
95                Ok(())
96            }
97        }
98        Display(self)
99    }
100
101    /// Create a command with the given settings.
102    pub fn build(&self, out_dir: &Path) -> Command {
103        let mut cmd = Command::new(&self.program);
104        cmd.args(self.args.iter());
105        if let Some(flag) = &self.out_dir_flag {
106            cmd.arg(flag).arg(out_dir);
107        }
108        if let Some(flag) = &self.input_file_flag {
109            cmd.arg(flag);
110        }
111        self.apply_env(&mut cmd);
112        cmd
113    }
114
115    pub(crate) fn apply_env(&self, cmd: &mut Command) {
116        for (var, val) in self.envs.iter() {
117            if let Some(val) = val {
118                cmd.env(var, val);
119            } else {
120                cmd.env_remove(var);
121            }
122        }
123    }
124}