Skip to content

Commit 48a7b0b

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

14 files changed

+130
-18
lines changed

Cargo.lock

+3-2
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",
@@ -4955,9 +4956,9 @@ checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
49554956

49564957
[[package]]
49574958
name = "shlex"
4958-
version = "1.1.0"
4959+
version = "1.2.0"
49594960
source = "registry+https://github.com/rust-lang/crates.io-index"
4960-
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
4961+
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
49614962

49624963
[[package]]
49634964
name = "siphasher"

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.2.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

+69-16
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,91 @@ 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+
#[derive(Default)]
9+
struct Expander {
10+
shell_argfiles: bool,
11+
next_is_unstable_option: bool,
12+
expanded: Vec<String>,
13+
}
14+
15+
impl Expander {
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+
fn push(&mut self, arg: String) {
38+
if self.next_is_unstable_option {
39+
self.inspect_unstable_option(&arg);
40+
self.next_is_unstable_option = false;
41+
} else if let Some(unstable_option) = arg.strip_prefix("-Z") {
42+
if unstable_option.is_empty() {
43+
self.next_is_unstable_option = true;
44+
} else {
45+
self.inspect_unstable_option(unstable_option);
46+
}
47+
}
48+
49+
self.expanded.push(arg);
50+
}
51+
52+
fn finish(self) -> Vec<String> {
53+
self.expanded
54+
}
55+
56+
/// Parses any unstable flags specified on the command line.
57+
fn inspect_unstable_option(&mut self, option: &str) {
58+
match option {
59+
"shell-argfiles" => self.shell_argfiles = true,
60+
_ => (),
61+
}
62+
}
63+
64+
fn read_file(path: &str) -> Result<String, Error> {
65+
fs::read_to_string(path).map_err(|e| {
66+
if e.kind() == io::ErrorKind::InvalidData {
67+
Error::Utf8Error(Some(path.to_string()))
68+
} else {
69+
Error::IOError(path.to_string(), e)
1470
}
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])
71+
})
2072
}
2173
}
2274

2375
/// **Note:** This function doesn't interpret argument 0 in any special way.
2476
/// If this function is intended to be used with command line arguments,
2577
/// `argv[0]` must be removed prior to calling it manually.
2678
pub fn arg_expand_all(handler: &EarlyErrorHandler, at_args: &[String]) -> Vec<String> {
27-
let mut args = Vec::new();
79+
let mut expander = Expander::default();
2880
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}")),
81+
if let Err(err) = expander.arg(arg) {
82+
handler.early_error(format!("Failed to load argument file: {err}"));
3283
}
3384
}
34-
args
85+
expander.finish()
3586
}
3687

3788
#[derive(Debug)]
3889
pub enum Error {
3990
Utf8Error(Option<String>),
4091
IOError(String, io::Error),
92+
ShellParseError(String),
4193
}
4294

4395
impl fmt::Display for Error {
@@ -46,6 +98,7 @@ impl fmt::Display for Error {
4698
Error::Utf8Error(None) => write!(fmt, "Utf8 error"),
4799
Error::Utf8Error(Some(path)) => write!(fmt, "Utf8 error in {path}"),
48100
Error::IOError(path, err) => write!(fmt, "IO Error: {path}: {err}"),
101+
Error::ShellParseError(path) => write!(fmt, "Invalid shell-style arguments in {path}"),
49102
}
50103
}
51104
}

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.
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}}/commandline-argfile-shell-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}}/commandline-argfile-shell-via-argfile.args @shell:{{src-base}}/commandline-argfile-shell-via-argfile-shell.args
5+
6+
#[cfg(not(shell_args_set))]
7+
compile_error!("shell_args_set not set");
8+
9+
fn main() {
10+
}
+3
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"

tests/ui/commandline-argfile-shell.rs

+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}}/commandline-argfile-shell.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)