xref: /aosp_15_r20/external/bazelbuild-rules_rust/util/process_wrapper/options.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1 use std::collections::HashMap;
2 use std::env;
3 use std::fmt;
4 use std::fs::File;
5 use std::io::{self, Write};
6 use std::process::exit;
7 
8 use crate::flags::{FlagParseError, Flags, ParseOutcome};
9 use crate::rustc;
10 use crate::util::*;
11 
12 #[derive(Debug)]
13 pub(crate) enum OptionError {
14     FlagError(FlagParseError),
15     Generic(String),
16 }
17 
18 impl fmt::Display for OptionError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result19     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20         match self {
21             Self::FlagError(e) => write!(f, "error parsing flags: {e}"),
22             Self::Generic(s) => write!(f, "{s}"),
23         }
24     }
25 }
26 
27 #[derive(Debug)]
28 pub(crate) struct Options {
29     // Contains the path to the child executable
30     pub(crate) executable: String,
31     // Contains arguments for the child process fetched from files.
32     pub(crate) child_arguments: Vec<String>,
33     // Contains environment variables for the child process fetched from files.
34     pub(crate) child_environment: HashMap<String, String>,
35     // If set, create the specified file after the child process successfully
36     // terminated its execution.
37     pub(crate) touch_file: Option<String>,
38     // If set to (source, dest) copies the source file to dest.
39     pub(crate) copy_output: Option<(String, String)>,
40     // If set, redirects the child process stdout to this file.
41     pub(crate) stdout_file: Option<String>,
42     // If set, redirects the child process stderr to this file.
43     pub(crate) stderr_file: Option<String>,
44     // If set, also logs all unprocessed output from the rustc output to this file.
45     // Meant to be used to get json output out of rustc for tooling usage.
46     pub(crate) output_file: Option<String>,
47     // If set, it configures rustc to emit an rmeta file and then
48     // quit.
49     pub(crate) rustc_quit_on_rmeta: bool,
50     // This controls the output format of rustc messages.
51     pub(crate) rustc_output_format: Option<rustc::ErrorFormat>,
52 }
53 
options() -> Result<Options, OptionError>54 pub(crate) fn options() -> Result<Options, OptionError> {
55     // Process argument list until -- is encountered.
56     // Everything after is sent to the child process.
57     let mut subst_mapping_raw = None;
58     let mut stable_status_file_raw = None;
59     let mut volatile_status_file_raw = None;
60     let mut env_file_raw = None;
61     let mut arg_file_raw = None;
62     let mut touch_file = None;
63     let mut copy_output_raw = None;
64     let mut stdout_file = None;
65     let mut stderr_file = None;
66     let mut output_file = None;
67     let mut rustc_quit_on_rmeta_raw = None;
68     let mut rustc_output_format_raw = None;
69     let mut flags = Flags::new();
70     flags.define_repeated_flag("--subst", "", &mut subst_mapping_raw);
71     flags.define_flag("--stable-status-file", "", &mut stable_status_file_raw);
72     flags.define_flag("--volatile-status-file", "", &mut volatile_status_file_raw);
73     flags.define_repeated_flag(
74         "--env-file",
75         "File(s) containing environment variables to pass to the child process.",
76         &mut env_file_raw,
77     );
78     flags.define_repeated_flag(
79         "--arg-file",
80         "File(s) containing command line arguments to pass to the child process.",
81         &mut arg_file_raw,
82     );
83     flags.define_flag(
84         "--touch-file",
85         "Create this file after the child process runs successfully.",
86         &mut touch_file,
87     );
88     flags.define_repeated_flag("--copy-output", "", &mut copy_output_raw);
89     flags.define_flag(
90         "--stdout-file",
91         "Redirect subprocess stdout in this file.",
92         &mut stdout_file,
93     );
94     flags.define_flag(
95         "--stderr-file",
96         "Redirect subprocess stderr in this file.",
97         &mut stderr_file,
98     );
99     flags.define_flag(
100         "--output-file",
101         "Log all unprocessed subprocess stderr in this file.",
102         &mut output_file,
103     );
104     flags.define_flag(
105         "--rustc-quit-on-rmeta",
106         "If enabled, this wrapper will terminate rustc after rmeta has been emitted.",
107         &mut rustc_quit_on_rmeta_raw,
108     );
109     flags.define_flag(
110         "--rustc-output-format",
111         "Controls the rustc output format if --rustc-quit-on-rmeta is set.\n\
112         'json' will cause the json output to be output, \
113         'rendered' will extract the rendered message and print that.\n\
114         Default: `rendered`",
115         &mut rustc_output_format_raw,
116     );
117 
118     let mut child_args = match flags
119         .parse(env::args().collect())
120         .map_err(OptionError::FlagError)?
121     {
122         ParseOutcome::Help(help) => {
123             eprintln!("{help}");
124             exit(0);
125         }
126         ParseOutcome::Parsed(p) => p,
127     };
128     let current_dir = std::env::current_dir()
129         .map_err(|e| OptionError::Generic(format!("failed to get current directory: {e}")))?
130         .to_str()
131         .ok_or_else(|| OptionError::Generic("current directory not utf-8".to_owned()))?
132         .to_owned();
133     let subst_mappings = subst_mapping_raw
134         .unwrap_or_default()
135         .into_iter()
136         .map(|arg| {
137             let (key, val) = arg.split_once('=').ok_or_else(|| {
138                 OptionError::Generic(format!("empty key for substitution '{arg}'"))
139             })?;
140             let v = if val == "${pwd}" {
141                 current_dir.as_str()
142             } else {
143                 val
144             }
145             .to_owned();
146             Ok((key.to_owned(), v))
147         })
148         .collect::<Result<Vec<(String, String)>, OptionError>>()?;
149     let stable_stamp_mappings =
150         stable_status_file_raw.map_or_else(Vec::new, |s| read_stamp_status_to_array(s).unwrap());
151     let volatile_stamp_mappings =
152         volatile_status_file_raw.map_or_else(Vec::new, |s| read_stamp_status_to_array(s).unwrap());
153     let environment_file_block = env_from_files(env_file_raw.unwrap_or_default())?;
154     let mut file_arguments = args_from_file(arg_file_raw.unwrap_or_default())?;
155     // Process --copy-output
156     let copy_output = copy_output_raw
157         .map(|co| {
158             if co.len() != 2 {
159                 return Err(OptionError::Generic(format!(
160                     "\"--copy-output\" needs exactly 2 parameters, {} provided",
161                     co.len()
162                 )));
163             }
164             let copy_source = &co[0];
165             let copy_dest = &co[1];
166             if copy_source == copy_dest {
167                 return Err(OptionError::Generic(format!(
168                     "\"--copy-output\" source ({copy_source}) and dest ({copy_dest}) need to be different.",
169                 )));
170             }
171             Ok((copy_source.to_owned(), copy_dest.to_owned()))
172         })
173         .transpose()?;
174 
175     let rustc_quit_on_rmeta = rustc_quit_on_rmeta_raw.map_or(false, |s| s == "true");
176     let rustc_output_format = rustc_output_format_raw
177         .map(|v| match v.as_str() {
178             "json" => Ok(rustc::ErrorFormat::Json),
179             "rendered" => Ok(rustc::ErrorFormat::Rendered),
180             _ => Err(OptionError::Generic(format!(
181                 "invalid --rustc-output-format '{v}'",
182             ))),
183         })
184         .transpose()?;
185 
186     // Prepare the environment variables, unifying those read from files with the ones
187     // of the current process.
188     let vars = environment_block(
189         environment_file_block,
190         &stable_stamp_mappings,
191         &volatile_stamp_mappings,
192         &subst_mappings,
193     );
194     // Append all the arguments fetched from files to those provided via command line.
195     child_args.append(&mut file_arguments);
196     let child_args = prepare_args(child_args, &subst_mappings)?;
197     // Split the executable path from the rest of the arguments.
198     let (exec_path, args) = child_args.split_first().ok_or_else(|| {
199         OptionError::Generic(
200             "at least one argument after -- is required (the child process path)".to_owned(),
201         )
202     })?;
203 
204     Ok(Options {
205         executable: exec_path.to_owned(),
206         child_arguments: args.to_vec(),
207         child_environment: vars,
208         touch_file,
209         copy_output,
210         stdout_file,
211         stderr_file,
212         output_file,
213         rustc_quit_on_rmeta,
214         rustc_output_format,
215     })
216 }
217 
args_from_file(paths: Vec<String>) -> Result<Vec<String>, OptionError>218 fn args_from_file(paths: Vec<String>) -> Result<Vec<String>, OptionError> {
219     let mut args = vec![];
220     for path in paths.iter() {
221         let mut lines = read_file_to_array(path).map_err(|err| {
222             OptionError::Generic(format!(
223                 "{} while processing args from file paths: {:?}",
224                 err, &paths
225             ))
226         })?;
227         args.append(&mut lines);
228     }
229     Ok(args)
230 }
231 
env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionError>232 fn env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionError> {
233     let mut env_vars = HashMap::new();
234     for path in paths.into_iter() {
235         let lines = read_file_to_array(&path).map_err(OptionError::Generic)?;
236         for line in lines.into_iter() {
237             let (k, v) = line
238                 .split_once('=')
239                 .ok_or_else(|| OptionError::Generic("environment file invalid".to_owned()))?;
240             env_vars.insert(k.to_owned(), v.to_owned());
241         }
242     }
243     Ok(env_vars)
244 }
245 
prepare_arg(mut arg: String, subst_mappings: &[(String, String)]) -> String246 fn prepare_arg(mut arg: String, subst_mappings: &[(String, String)]) -> String {
247     for (f, replace_with) in subst_mappings {
248         let from = format!("${{{f}}}");
249         arg = arg.replace(&from, replace_with);
250     }
251     arg
252 }
253 
254 /// Apply substitutions to the given param file. Returns the new filename.
prepare_param_file( filename: &str, subst_mappings: &[(String, String)], ) -> Result<String, OptionError>255 fn prepare_param_file(
256     filename: &str,
257     subst_mappings: &[(String, String)],
258 ) -> Result<String, OptionError> {
259     let expanded_file = format!("{filename}.expanded");
260     let format_err = |err: io::Error| {
261         OptionError::Generic(format!(
262             "{} writing path: {:?}, current directory: {:?}",
263             err,
264             expanded_file,
265             std::env::current_dir()
266         ))
267     };
268     let mut out = io::BufWriter::new(File::create(&expanded_file).map_err(format_err)?);
269     fn process_file(
270         filename: &str,
271         out: &mut io::BufWriter<File>,
272         subst_mappings: &[(String, String)],
273         format_err: &impl Fn(io::Error) -> OptionError,
274     ) -> Result<(), OptionError> {
275         for arg in read_file_to_array(filename).map_err(OptionError::Generic)? {
276             let arg = prepare_arg(arg, subst_mappings);
277             if let Some(arg_file) = arg.strip_prefix('@') {
278                 process_file(arg_file, out, subst_mappings, format_err)?;
279             } else {
280                 writeln!(out, "{arg}").map_err(format_err)?;
281             }
282         }
283         Ok(())
284     }
285     process_file(filename, &mut out, subst_mappings, &format_err)?;
286     Ok(expanded_file)
287 }
288 
289 /// Apply substitutions to the provided arguments, recursing into param files.
prepare_args( args: Vec<String>, subst_mappings: &[(String, String)], ) -> Result<Vec<String>, OptionError>290 fn prepare_args(
291     args: Vec<String>,
292     subst_mappings: &[(String, String)],
293 ) -> Result<Vec<String>, OptionError> {
294     args.into_iter()
295         .map(|arg| {
296             let arg = prepare_arg(arg, subst_mappings);
297             if let Some(param_file) = arg.strip_prefix('@') {
298                 // Note that substitutions may also apply to the param file path!
299                 prepare_param_file(param_file, subst_mappings)
300                     .map(|filename| format!("@{filename}"))
301             } else {
302                 Ok(arg)
303             }
304         })
305         .collect()
306 }
307 
environment_block( environment_file_block: HashMap<String, String>, stable_stamp_mappings: &[(String, String)], volatile_stamp_mappings: &[(String, String)], subst_mappings: &[(String, String)], ) -> HashMap<String, String>308 fn environment_block(
309     environment_file_block: HashMap<String, String>,
310     stable_stamp_mappings: &[(String, String)],
311     volatile_stamp_mappings: &[(String, String)],
312     subst_mappings: &[(String, String)],
313 ) -> HashMap<String, String> {
314     // Taking all environment variables from the current process
315     // and sending them down to the child process
316     let mut environment_variables: HashMap<String, String> = std::env::vars().collect();
317     // Have the last values added take precedence over the first.
318     // This is simpler than needing to track duplicates and explicitly override
319     // them.
320     environment_variables.extend(environment_file_block);
321     for (f, replace_with) in &[stable_stamp_mappings, volatile_stamp_mappings].concat() {
322         for value in environment_variables.values_mut() {
323             let from = format!("{{{f}}}");
324             let new = value.replace(from.as_str(), replace_with);
325             *value = new;
326         }
327     }
328     for (f, replace_with) in subst_mappings {
329         for value in environment_variables.values_mut() {
330             let from = format!("${{{f}}}");
331             let new = value.replace(from.as_str(), replace_with);
332             *value = new;
333         }
334     }
335     environment_variables
336 }
337