1 | use crate::display; |
2 | use 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. |
11 | pub 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 | |
28 | impl 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 | } |
125 | |