Skip to content

Commit d952de3

Browse files
committed
Add support for shell argfiles
1 parent e24e5af commit d952de3

16 files changed

+139
-16
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3750,6 +3750,7 @@ dependencies = [
37503750
"rustc_trait_selection",
37513751
"rustc_ty_utils",
37523752
"serde_json",
3753+
"shlex",
37533754
"time",
37543755
"tracing",
37553756
"windows",

compiler/rustc_driver_impl/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ rustc_target = { path = "../rustc_target" }
5050
rustc_trait_selection = { path = "../rustc_trait_selection" }
5151
rustc_ty_utils = { path = "../rustc_ty_utils" }
5252
serde_json = "1.0.59"
53+
shlex = "1.0"
5354
time = { version = "0.3", default-features = false, features = ["alloc", "formatting"] }
5455
tracing = { version = "0.1.35" }
5556
# tidy-alphabetical-end

compiler/rustc_driver_impl/src/args.rs

+75-16
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,97 @@ use std::io;
55

66
use rustc_session::EarlyErrorHandler;
77

8-
fn arg_expand(arg: String) -> Result<Vec<String>, Error> {
9-
if let Some(path) = arg.strip_prefix('@') {
10-
let file = match fs::read_to_string(path) {
11-
Ok(file) => file,
12-
Err(ref err) if err.kind() == io::ErrorKind::InvalidData => {
13-
return Err(Error::Utf8Error(Some(path.to_string())));
8+
/// Expands argfiles in command line arguments.
9+
#[derive(Default)]
10+
struct Expander {
11+
shell_argfiles: bool,
12+
next_is_unstable_option: bool,
13+
expanded: Vec<String>,
14+
}
15+
16+
impl Expander {
17+
/// Handles the next argument. If the argument is an argfile, it is expanded
18+
/// inline.
19+
fn arg(&mut self, arg: &str) -> Result<(), Error> {
20+
if let Some(argfile) = arg.strip_prefix('@') {
21+
match argfile.split_once(':') {
22+
Some(("shell", path)) if self.shell_argfiles => {
23+
shlex::split(&Self::read_file(path)?)
24+
.ok_or_else(|| Error::ShellParseError(path.to_string()))?
25+
.into_iter()
26+
.for_each(|arg| self.push(arg));
27+
}
28+
_ => {
29+
let contents = Self::read_file(argfile)?;
30+
contents.lines().for_each(|arg| self.push(arg.to_string()));
31+
}
32+
}
33+
} else {
34+
self.push(arg.to_string());
35+
}
36+
37+
Ok(())
38+
}
39+
40+
/// Adds a command line argument verbatim with no argfile expansion.
41+
fn push(&mut self, arg: String) {
42+
if self.next_is_unstable_option {
43+
self.inspect_unstable_option(&arg);
44+
self.next_is_unstable_option = false;
45+
} else if let Some(unstable_option) = arg.strip_prefix("-Z") {
46+
if unstable_option.is_empty() {
47+
self.next_is_unstable_option = true;
48+
} else {
49+
self.inspect_unstable_option(unstable_option);
50+
}
51+
}
52+
53+
self.expanded.push(arg);
54+
}
55+
56+
/// Consumes the `Expander`, returning the expanded arguments.
57+
fn finish(self) -> Vec<String> {
58+
self.expanded
59+
}
60+
61+
/// Parses any relevant unstable flags specified on the command line.
62+
fn inspect_unstable_option(&mut self, option: &str) {
63+
match option {
64+
"shell-argfiles" => self.shell_argfiles = true,
65+
_ => (),
66+
}
67+
}
68+
69+
/// Reads the contents of a file as UTF-8.
70+
fn read_file(path: &str) -> Result<String, Error> {
71+
fs::read_to_string(path).map_err(|e| {
72+
if e.kind() == io::ErrorKind::InvalidData {
73+
Error::Utf8Error(Some(path.to_string()))
74+
} else {
75+
Error::IOError(path.to_string(), e)
1476
}
15-
Err(err) => return Err(Error::IOError(path.to_string(), err)),
16-
};
17-
Ok(file.lines().map(ToString::to_string).collect())
18-
} else {
19-
Ok(vec![arg])
77+
})
2078
}
2179
}
2280

2381
/// **Note:** This function doesn't interpret argument 0 in any special way.
2482
/// If this function is intended to be used with command line arguments,
2583
/// `argv[0]` must be removed prior to calling it manually.
2684
pub fn arg_expand_all(handler: &EarlyErrorHandler, at_args: &[String]) -> Vec<String> {
27-
let mut args = Vec::new();
85+
let mut expander = Expander::default();
2886
for arg in at_args {
29-
match arg_expand(arg.clone()) {
30-
Ok(arg) => args.extend(arg),
31-
Err(err) => handler.early_error(format!("Failed to load argument file: {err}")),
87+
if let Err(err) = expander.arg(arg) {
88+
handler.early_error(format!("Failed to load argument file: {err}"));
3289
}
3390
}
34-
args
91+
expander.finish()
3592
}
3693

3794
#[derive(Debug)]
3895
pub enum Error {
3996
Utf8Error(Option<String>),
4097
IOError(String, io::Error),
98+
ShellParseError(String),
4199
}
42100

43101
impl fmt::Display for Error {
@@ -46,6 +104,7 @@ impl fmt::Display for Error {
46104
Error::Utf8Error(None) => write!(fmt, "Utf8 error"),
47105
Error::Utf8Error(Some(path)) => write!(fmt, "Utf8 error in {path}"),
48106
Error::IOError(path, err) => write!(fmt, "IO Error: {path}: {err}"),
107+
Error::ShellParseError(path) => write!(fmt, "Invalid shell-style arguments in {path}"),
49108
}
50109
}
51110
}

compiler/rustc_interface/src/tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,7 @@ fn test_unstable_options_tracking_hash() {
702702
untracked!(query_dep_graph, true);
703703
untracked!(self_profile, SwitchWithOptPath::Enabled(None));
704704
untracked!(self_profile_events, Some(vec![String::new()]));
705+
untracked!(shell_argfiles, true);
705706
untracked!(span_debug, true);
706707
untracked!(span_free_formats, true);
707708
untracked!(temps_dir, Some(String::from("abc")));

compiler/rustc_session/src/options.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1832,6 +1832,8 @@ written to standard error output)"),
18321832
query-blocked, incr-cache-load, incr-result-hashing, query-keys, function-args, args, llvm, artifact-sizes"),
18331833
share_generics: Option<bool> = (None, parse_opt_bool, [TRACKED],
18341834
"make the current crate share its generic instantiations"),
1835+
shell_argfiles: bool = (false, parse_bool, [UNTRACKED],
1836+
"allow argument files to be specified with POSIX \"shell-style\" argument quoting"),
18351837
show_span: Option<String> = (None, parse_opt_string, [TRACKED],
18361838
"show spans for compiler debugging (expr|pat|ty)"),
18371839
simulate_remapped_rust_src_base: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# `shell-argfiles`
2+
3+
--------------------
4+
5+
The `-Zshell-argfiles` compiler flag allows argfiles to be parsed using POSIX
6+
"shell-style" quoting. When enabled, the compiler will use `shlex` to parse the
7+
arguments from argfiles specified with `@shell:<path>`.
8+
9+
Because this feature controls the parsing of input arguments, the
10+
`-Zshell-argfiles` flag must be present before the argument specifying the
11+
shell-style arguemnt file.

src/tools/tidy/src/deps.rs

+1
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
327327
"sha1",
328328
"sha2",
329329
"sharded-slab",
330+
"shlex",
330331
"smallvec",
331332
"snap",
332333
"stable_deref_trait",

src/tools/tidy/src/ui_tests.rs

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ const EXTENSION_EXCEPTION_PATHS: &[&str] = &[
3636
"tests/ui/unused-crate-deps/test.mk", // why would you use make
3737
"tests/ui/proc-macro/auxiliary/included-file.txt", // more include
3838
"tests/ui/invalid/foo.natvis.xml", // sample debugger visualizer
39+
"tests/ui/shell-argfiles/shell-argfiles.args", // passing args via a file
40+
"tests/ui/shell-argfiles/shell-argfiles-badquotes.args", // passing args via a file
41+
"tests/ui/shell-argfiles/shell-argfiles-via-argfile-shell.args", // passing args via a file
42+
"tests/ui/shell-argfiles/shell-argfiles-via-argfile.args", // passing args via a file
3943
];
4044

4145
fn check_entries(tests_path: &Path, bad: &mut bool) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"--cfg" "unquoted_set
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Check to see if we can get parameters from an @argsfile file
2+
//
3+
// compile-flags: --cfg cmdline_set -Z shell-argfiles @shell:{{src-base}}/shell-argfiles-badquotes.args
4+
5+
fn main() {
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: Failed to load argument file: Invalid shell-style arguments in $DIR/commandline-argfile-shell-badquotes.args
2+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"--cfg" "shell_args_set"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Zshell-argfiles
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Check to see if we can get parameters from an @argsfile file
2+
//
3+
// build-pass
4+
// compile-flags: @{{src-base}}/shell-argfiles-via-argfile.args @shell:{{src-base}}/shell-argfiles-via-argfile-shell.args
5+
6+
#[cfg(not(shell_args_set))]
7+
compile_error!("shell_args_set not set");
8+
9+
fn main() {
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--cfg unquoted_set
2+
'--cfg' 'single_quoted_set'
3+
"--cfg" "double_quoted_set"
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Check to see if we can get parameters from an @argsfile file
2+
//
3+
// build-pass
4+
// compile-flags: --cfg cmdline_set -Z shell-argfiles @shell:{{src-base}}/shell-argfiles.args
5+
6+
#[cfg(not(cmdline_set))]
7+
compile_error!("cmdline_set not set");
8+
9+
#[cfg(not(unquoted_set))]
10+
compile_error!("unquoted_set not set");
11+
12+
#[cfg(not(single_quoted_set))]
13+
compile_error!("single_quoted_set not set");
14+
15+
#[cfg(not(double_quoted_set))]
16+
compile_error!("double_quoted_set not set");
17+
18+
fn main() {
19+
}

0 commit comments

Comments
 (0)