Skip to main content

rustc_driver_impl/
args.rs

1use std::{env, error, fmt, fs, io};
2
3use rustc_session::EarlyDiagCtxt;
4
5/// 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}
12
13impl Expander {
14    /// Handles the next argument. If the argument is an argfile, it is expanded
15    /// inline.
16    fn arg(&mut self, arg: &str) -> Result<(), Error> {
17        if let Some(argfile) = arg.strip_prefix('@') {
18            match argfile.split_once(':') {
19                Some(("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                _ => {
26                    let contents = Self::read_file(argfile)?;
27                    contents.lines().for_each(|arg| self.push(arg.to_string()));
28                }
29            }
30        } else {
31            self.push(arg.to_string());
32        }
33
34        Ok(())
35    }
36
37    /// Adds a command line argument verbatim with no argfile expansion.
38    fn 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.
54
55        if self.next_is_unstable_option {
56            self.inspect_unstable_option(&arg);
57            self.next_is_unstable_option = false;
58        } else if let Some(unstable_option) = arg.strip_prefix("-Z") {
59            if unstable_option.is_empty() {
60                self.next_is_unstable_option = true;
61            } else {
62                self.inspect_unstable_option(unstable_option);
63            }
64        }
65
66        self.expanded.push(arg);
67    }
68
69    /// Consumes the `Expander`, returning the expanded arguments.
70    fn finish(self) -> Vec<String> {
71        self.expanded
72    }
73
74    /// Parses any relevant unstable flags specified on the command line.
75    fn inspect_unstable_option(&mut self, option: &str) {
76        match option {
77            "shell-argfiles" => self.shell_argfiles = true,
78            _ => (),
79        }
80    }
81
82    /// Reads the contents of a file as UTF-8.
83    fn read_file(path: &str) -> Result<String, Error> {
84        fs::read_to_string(path).map_err(|e| {
85            if e.kind() == io::ErrorKind::InvalidData {
86                Error::Utf8Error(path.to_string())
87            } else {
88                Error::IOError(path.to_string(), e)
89            }
90        })
91    }
92}
93
94/// 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> {
101    let mut expander = Expander::default();
102    let mut result = Ok(());
103    for arg in at_args {
104        if 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    }
108    if let Err(guar) = result {
109        guar.raise_fatal();
110    }
111    expander.finish()
112}
113
114/// 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> {
120    let mut args = Vec::new();
121    let mut guar = Ok(());
122    for (i, arg) in env::args_os().enumerate() {
123        match arg.into_string() {
124            Ok(arg) => args.push(arg),
125            Err(arg) => {
126                guar =
127                    Err(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    }
131    if let Err(guar) = guar {
132        guar.raise_fatal();
133    }
134    args
135}
136
137#[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}
143
144impl fmt::Display for Error {
145    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
146        match 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}
153
154impl error::Error for Error {}