1use std::{env, error, fmt, fs, io};
23use rustc_session::EarlyDiagCtxt;
45/// Expands argfiles in command line arguments.
6#[derive(#[automatically_derived]
impl ::core::default::Default for Expander {
#[inline]
fn default() -> Expander {
Expander {
shell_argfiles: ::core::default::Default::default(),
next_is_unstable_option: ::core::default::Default::default(),
expanded: ::core::default::Default::default(),
}
}
}Default)]
7struct Expander {
8 shell_argfiles: bool,
9 next_is_unstable_option: bool,
10 expanded: Vec<String>,
11}
1213impl Expander {
14/// Handles the next argument. If the argument is an argfile, it is expanded
15 /// inline.
16fn arg(&mut self, arg: &str) -> Result<(), Error> {
17if let Some(argfile) = arg.strip_prefix('@') {
18match argfile.split_once(':') {
19Some(("shell", path)) if self.shell_argfiles => {
20 shlex::split(&Self::read_file(path)?)
21 .ok_or_else(|| Error::ShellParseError(path.to_string()))?
22.into_iter()
23 .for_each(|arg| self.push(arg));
24 }
25_ => {
26let contents = Self::read_file(argfile)?;
27contents.lines().for_each(|arg| self.push(arg.to_string()));
28 }
29 }
30 } else {
31self.push(arg.to_string());
32 }
3334Ok(())
35 }
3637/// Adds a command line argument verbatim with no argfile expansion.
38fn push(&mut self, arg: String) {
39// Unfortunately, we have to do some eager argparsing to handle unstable
40 // options which change the behavior of argfile arguments.
41 //
42 // Normally, all of the argfile arguments (e.g. `@args.txt`) are
43 // expanded into our arguments list *and then* the whole list of
44 // arguments are passed on to be parsed. However, argfile parsing
45 // options like `-Zshell_argfiles` need to change the behavior of that
46 // argument expansion. So we have to do a little parsing on our own here
47 // instead of leaning on the existing logic.
48 //
49 // All we care about are unstable options, so we parse those out and
50 // look for any that affect how we expand argfiles. This argument
51 // inspection is very conservative; we only change behavior when we see
52 // exactly the options we're looking for and everything gets passed
53 // through.
5455if self.next_is_unstable_option {
56self.inspect_unstable_option(&arg);
57self.next_is_unstable_option = false;
58 } else if let Some(unstable_option) = arg.strip_prefix("-Z") {
59if unstable_option.is_empty() {
60self.next_is_unstable_option = true;
61 } else {
62self.inspect_unstable_option(unstable_option);
63 }
64 }
6566self.expanded.push(arg);
67 }
6869/// Consumes the `Expander`, returning the expanded arguments.
70fn finish(self) -> Vec<String> {
71self.expanded
72 }
7374/// Parses any relevant unstable flags specified on the command line.
75fn inspect_unstable_option(&mut self, option: &str) {
76match option {
77"shell-argfiles" => self.shell_argfiles = true,
78_ => (),
79 }
80 }
8182/// Reads the contents of a file as UTF-8.
83fn read_file(path: &str) -> Result<String, Error> {
84 fs::read_to_string(path).map_err(|e| {
85if e.kind() == io::ErrorKind::InvalidData {
86 Error::Utf8Error(path.to_string())
87 } else {
88 Error::IOError(path.to_string(), e)
89 }
90 })
91 }
92}
9394/// Replaces any `@file` arguments with the contents of `file`, with each line of `file` as a
95/// separate argument.
96///
97/// **Note:** This function doesn't interpret argument 0 in any special way.
98/// If this function is intended to be used with command line arguments,
99/// `argv[0]` must be removed prior to calling it manually.
100pub fn arg_expand_all(early_dcx: &EarlyDiagCtxt, at_args: &[String]) -> Vec<String> {
101let mut expander = Expander::default();
102let mut result = Ok(());
103for arg in at_args {
104if let Err(err) = expander.arg(arg) {
105 result = Err(early_dcx.early_err(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("failed to load argument file: {0}",
err))
})format!("failed to load argument file: {err}")));
106 }
107 }
108if let Err(guar) = result {
109guar.raise_fatal();
110 }
111expander.finish()
112}
113114/// Gets the raw unprocessed command-line arguments as Unicode strings, without doing any further
115/// processing (e.g., without `@file` expansion).
116///
117/// This function is identical to [`env::args()`] except that it emits an error when it encounters
118/// non-Unicode arguments instead of panicking.
119pub fn raw_args(early_dcx: &EarlyDiagCtxt) -> Vec<String> {
120let mut args = Vec::new();
121let mut guar = Ok(());
122for (i, arg) in env::args_os().enumerate() {
123match arg.into_string() {
124Ok(arg) => args.push(arg),
125Err(arg) => {
126 guar =
127Err(early_dcx.early_err(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("argument {0} is not valid Unicode: {1:?}",
i, arg))
})format!("argument {i} is not valid Unicode: {arg:?}")))
128 }
129 }
130 }
131if let Err(guar) = guar {
132guar.raise_fatal();
133 }
134args135}
136137#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Error {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
Error::Utf8Error(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"Utf8Error", &__self_0),
Error::IOError(__self_0, __self_1) =>
::core::fmt::Formatter::debug_tuple_field2_finish(f,
"IOError", __self_0, &__self_1),
Error::ShellParseError(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"ShellParseError", &__self_0),
}
}
}Debug)]
138enum Error {
139 Utf8Error(String),
140 IOError(String, io::Error),
141 ShellParseError(String),
142}
143144impl fmt::Displayfor Error {
145fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
146match self {
147 Error::Utf8Error(path) => fmt.write_fmt(format_args!("UTF-8 error in {0}", path))write!(fmt, "UTF-8 error in {path}"),
148 Error::IOError(path, err) => fmt.write_fmt(format_args!("IO error: {0}: {1}", path, err))write!(fmt, "IO error: {path}: {err}"),
149 Error::ShellParseError(path) => fmt.write_fmt(format_args!("invalid shell-style arguments in {0}", path))write!(fmt, "invalid shell-style arguments in {path}"),
150 }
151 }
152}
153154impl error::Errorfor Error {}