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