| use std::{env, error, fmt, fs, io}; | |
| use rustc_session::EarlyDiagCtxt; | |
| /// Expands argfiles in command line arguments. | |
| #[derive(Default)] | |
| struct Expander { | |
| shell_argfiles: bool, | |
| next_is_unstable_option: bool, | |
| expanded: Vec<String>, | |
| } | |
| impl Expander { | |
| /// Handles the next argument. If the argument is an argfile, it is expanded | |
| /// inline. | |
| fn arg(&mut self, arg: &str) -> Result<(), Error> { | |
| if let Some(argfile) = arg.strip_prefix('@') { | |
| match argfile.split_once(':') { | |
| Some(("shell", path)) if self.shell_argfiles => { | |
| shlex::split(&Self::read_file(path)?) | |
| .ok_or_else(|| Error::ShellParseError(path.to_string()))? | |
| .into_iter() | |
| .for_each(|arg| self.push(arg)); | |
| } | |
| _ => { | |
| let contents = Self::read_file(argfile)?; | |
| contents.lines().for_each(|arg| self.push(arg.to_string())); | |
| } | |
| } | |
| } else { | |
| self.push(arg.to_string()); | |
| } | |
| Ok(()) | |
| } | |
| /// Adds a command line argument verbatim with no argfile expansion. | |
| fn push(&mut self, arg: String) { | |
| // Unfortunately, we have to do some eager argparsing to handle unstable | |
| // options which change the behavior of argfile arguments. | |
| // | |
| // Normally, all of the argfile arguments (e.g. `@args.txt`) are | |
| // expanded into our arguments list *and then* the whole list of | |
| // arguments are passed on to be parsed. However, argfile parsing | |
| // options like `-Zshell_argfiles` need to change the behavior of that | |
| // argument expansion. So we have to do a little parsing on our own here | |
| // instead of leaning on the existing logic. | |
| // | |
| // All we care about are unstable options, so we parse those out and | |
| // look for any that affect how we expand argfiles. This argument | |
| // inspection is very conservative; we only change behavior when we see | |
| // exactly the options we're looking for and everything gets passed | |
| // through. | |
| if self.next_is_unstable_option { | |
| self.inspect_unstable_option(&arg); | |
| self.next_is_unstable_option = false; | |
| } else if let Some(unstable_option) = arg.strip_prefix("-Z") { | |
| if unstable_option.is_empty() { | |
| self.next_is_unstable_option = true; | |
| } else { | |
| self.inspect_unstable_option(unstable_option); | |
| } | |
| } | |
| self.expanded.push(arg); | |
| } | |
| /// Consumes the `Expander`, returning the expanded arguments. | |
| fn finish(self) -> Vec<String> { | |
| self.expanded | |
| } | |
| /// Parses any relevant unstable flags specified on the command line. | |
| fn inspect_unstable_option(&mut self, option: &str) { | |
| match option { | |
| "shell-argfiles" => self.shell_argfiles = true, | |
| _ => (), | |
| } | |
| } | |
| /// Reads the contents of a file as UTF-8. | |
| fn read_file(path: &str) -> Result<String, Error> { | |
| fs::read_to_string(path).map_err(|e| { | |
| if e.kind() == io::ErrorKind::InvalidData { | |
| Error::Utf8Error(path.to_string()) | |
| } else { | |
| Error::IOError(path.to_string(), e) | |
| } | |
| }) | |
| } | |
| } | |
| /// Replaces any `@file` arguments with the contents of `file`, with each line of `file` as a | |
| /// separate argument. | |
| /// | |
| /// **Note:** This function doesn't interpret argument 0 in any special way. | |
| /// If this function is intended to be used with command line arguments, | |
| /// `argv[0]` must be removed prior to calling it manually. | |
| pub fn arg_expand_all(early_dcx: &EarlyDiagCtxt, at_args: &[String]) -> Vec<String> { | |
| let mut expander = Expander::default(); | |
| let mut result = Ok(()); | |
| for arg in at_args { | |
| if let Err(err) = expander.arg(arg) { | |
| result = Err(early_dcx.early_err(format!("failed to load argument file: {err}"))); | |
| } | |
| } | |
| if let Err(guar) = result { | |
| guar.raise_fatal(); | |
| } | |
| expander.finish() | |
| } | |
| /// Gets the raw unprocessed command-line arguments as Unicode strings, without doing any further | |
| /// processing (e.g., without `@file` expansion). | |
| /// | |
| /// This function is identical to [`env::args()`] except that it emits an error when it encounters | |
| /// non-Unicode arguments instead of panicking. | |
| pub fn raw_args(early_dcx: &EarlyDiagCtxt) -> Vec<String> { | |
| let mut args = Vec::new(); | |
| let mut guar = Ok(()); | |
| for (i, arg) in env::args_os().enumerate() { | |
| match arg.into_string() { | |
| Ok(arg) => args.push(arg), | |
| Err(arg) => { | |
| guar = | |
| Err(early_dcx.early_err(format!("argument {i} is not valid Unicode: {arg:?}"))) | |
| } | |
| } | |
| } | |
| if let Err(guar) = guar { | |
| guar.raise_fatal(); | |
| } | |
| args | |
| } | |
| #[derive(Debug)] | |
| enum Error { | |
| Utf8Error(String), | |
| IOError(String, io::Error), | |
| ShellParseError(String), | |
| } | |
| impl fmt::Display for Error { | |
| fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| Error::Utf8Error(path) => write!(fmt, "UTF-8 error in {path}"), | |
| Error::IOError(path, err) => write!(fmt, "IO error: {path}: {err}"), | |
| Error::ShellParseError(path) => write!(fmt, "invalid shell-style arguments in {path}"), | |
| } | |
| } | |
| } | |
| impl error::Error for Error {} |