| 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 |  | 
|---|