Skip to content

Commit 85ba0ff

Browse files
authored
Rollup merge of rust-lang#63175 - jsgf:argsfile, r=alexcrichton
rustc: implement argsfiles for command line Many tools, such as gcc and gnu-ld, support "args files" - that is, being able to specify @file on the command line. This causes `file` to be opened and parsed for command line options. They're separated with whitespace; whitespace can be quoted with double or single quotes, and everything can be \\-escaped. Args files may recursively include other args files via `@file2`. See https://sourceware.org/binutils/docs/ld/Options.html#Options for the documentation of gnu-ld's @file parameters. This is useful for very large command lines, or when command lines are being generated into files by other tooling.
2 parents 804d973 + 6589dce commit 85ba0ff

11 files changed

+307
-8
lines changed

src/doc/rustc/src/command-line-arguments.md

+7
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,10 @@ to customize the output:
304304

305305
Note that it is invalid to combine the `--json` argument with the `--color`
306306
argument, and it is required to combine `--json` with `--error-format=json`.
307+
308+
## `@path`: load command-line flags from a path
309+
310+
If you specify `@path` on the command-line, then it will open `path` and read
311+
command line options from it. These options are one per line; a blank line indicates
312+
an empty option. The file can use Unix or Windows style line endings, and must be
313+
encoded as UTF-8.

src/librustc_driver/args/mod.rs

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use std::env;
2+
use std::error;
3+
use std::fmt;
4+
use std::fs;
5+
use std::io;
6+
use std::str;
7+
use std::sync::atomic::{AtomicBool, Ordering};
8+
9+
#[cfg(test)]
10+
mod tests;
11+
12+
static USED_ARGSFILE_FEATURE: AtomicBool = AtomicBool::new(false);
13+
14+
pub fn used_unstable_argsfile() -> bool {
15+
USED_ARGSFILE_FEATURE.load(Ordering::Relaxed)
16+
}
17+
18+
pub struct ArgsIter {
19+
base: env::ArgsOs,
20+
file: std::vec::IntoIter<String>,
21+
}
22+
23+
impl ArgsIter {
24+
pub fn new() -> Self {
25+
ArgsIter { base: env::args_os(), file: vec![].into_iter() }
26+
}
27+
}
28+
29+
impl Iterator for ArgsIter {
30+
type Item = Result<String, Error>;
31+
32+
fn next(&mut self) -> Option<Self::Item> {
33+
loop {
34+
if let Some(line) = self.file.next() {
35+
return Some(Ok(line));
36+
}
37+
38+
let arg =
39+
self.base.next().map(|arg| arg.into_string().map_err(|_| Error::Utf8Error(None)));
40+
match arg {
41+
Some(Err(err)) => return Some(Err(err)),
42+
Some(Ok(ref arg)) if arg.starts_with("@") => {
43+
let path = &arg[1..];
44+
let file = match fs::read_to_string(path) {
45+
Ok(file) => {
46+
USED_ARGSFILE_FEATURE.store(true, Ordering::Relaxed);
47+
file
48+
}
49+
Err(ref err) if err.kind() == io::ErrorKind::InvalidData => {
50+
return Some(Err(Error::Utf8Error(Some(path.to_string()))));
51+
}
52+
Err(err) => return Some(Err(Error::IOError(path.to_string(), err))),
53+
};
54+
self.file =
55+
file.lines().map(ToString::to_string).collect::<Vec<_>>().into_iter();
56+
}
57+
Some(Ok(arg)) => return Some(Ok(arg)),
58+
None => return None,
59+
}
60+
}
61+
}
62+
}
63+
64+
#[derive(Debug)]
65+
pub enum Error {
66+
Utf8Error(Option<String>),
67+
IOError(String, io::Error),
68+
}
69+
70+
impl fmt::Display for Error {
71+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
72+
match self {
73+
Error::Utf8Error(None) => write!(fmt, "Utf8 error"),
74+
Error::Utf8Error(Some(path)) => write!(fmt, "Utf8 error in {}", path),
75+
Error::IOError(path, err) => write!(fmt, "IO Error: {}: {}", path, err),
76+
}
77+
}
78+
}
79+
80+
impl error::Error for Error {
81+
fn description(&self) -> &'static str {
82+
"argument error"
83+
}
84+
}

src/librustc_driver/args/tests.rs

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use super::*;
2+
3+
use std::str;
4+
5+
fn want_args(v: impl IntoIterator<Item = &'static str>) -> Vec<String> {
6+
v.into_iter().map(String::from).collect()
7+
}
8+
9+
fn got_args(file: &[u8]) -> Result<Vec<String>, Error> {
10+
let ret = str::from_utf8(file)
11+
.map_err(|_| Error::Utf8Error(None))?
12+
.lines()
13+
.map(ToString::to_string)
14+
.collect::<Vec<_>>();
15+
Ok(ret)
16+
}
17+
18+
#[test]
19+
fn nothing() {
20+
let file = b"";
21+
22+
assert_eq!(got_args(file).unwrap(), want_args(vec![]));
23+
}
24+
25+
#[test]
26+
fn empty() {
27+
let file = b"\n";
28+
29+
assert_eq!(got_args(file).unwrap(), want_args(vec![""]));
30+
}
31+
32+
#[test]
33+
fn simple() {
34+
let file = b"foo";
35+
36+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo"]));
37+
}
38+
39+
#[test]
40+
fn simple_eol() {
41+
let file = b"foo\n";
42+
43+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo"]));
44+
}
45+
46+
#[test]
47+
fn multi() {
48+
let file = b"foo\nbar";
49+
50+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo", "bar"]));
51+
}
52+
53+
#[test]
54+
fn multi_eol() {
55+
let file = b"foo\nbar\n";
56+
57+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo", "bar"]));
58+
}
59+
60+
#[test]
61+
fn multi_empty() {
62+
let file = b"foo\n\nbar";
63+
64+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo", "", "bar"]));
65+
}
66+
67+
#[test]
68+
fn multi_empty_eol() {
69+
let file = b"foo\n\nbar\n";
70+
71+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo", "", "bar"]));
72+
}
73+
74+
#[test]
75+
fn multi_empty_start() {
76+
let file = b"\nfoo\nbar";
77+
78+
assert_eq!(got_args(file).unwrap(), want_args(vec!["", "foo", "bar"]));
79+
}
80+
81+
#[test]
82+
fn multi_empty_end() {
83+
let file = b"foo\nbar\n\n";
84+
85+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo", "bar", ""]));
86+
}
87+
88+
#[test]
89+
fn simple_eol_crlf() {
90+
let file = b"foo\r\n";
91+
92+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo"]));
93+
}
94+
95+
#[test]
96+
fn multi_crlf() {
97+
let file = b"foo\r\nbar";
98+
99+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo", "bar"]));
100+
}
101+
102+
#[test]
103+
fn multi_eol_crlf() {
104+
let file = b"foo\r\nbar\r\n";
105+
106+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo", "bar"]));
107+
}
108+
109+
#[test]
110+
fn multi_empty_crlf() {
111+
let file = b"foo\r\n\r\nbar";
112+
113+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo", "", "bar"]));
114+
}
115+
116+
#[test]
117+
fn multi_empty_eol_crlf() {
118+
let file = b"foo\r\n\r\nbar\r\n";
119+
120+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo", "", "bar"]));
121+
}
122+
123+
#[test]
124+
fn multi_empty_start_crlf() {
125+
let file = b"\r\nfoo\r\nbar";
126+
127+
assert_eq!(got_args(file).unwrap(), want_args(vec!["", "foo", "bar"]));
128+
}
129+
130+
#[test]
131+
fn multi_empty_end_crlf() {
132+
let file = b"foo\r\nbar\r\n\r\n";
133+
134+
assert_eq!(got_args(file).unwrap(), want_args(vec!["foo", "bar", ""]));
135+
}
136+
137+
#[test]
138+
fn bad_utf8() {
139+
let file = b"foo\x80foo";
140+
141+
match got_args(file).unwrap_err() {
142+
Error::Utf8Error(_) => (),
143+
bad => panic!("bad err: {:?}", bad),
144+
}
145+
}

src/librustc_driver/lib.rs

+21-8
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ use syntax::symbol::sym;
6666
use syntax_pos::{DUMMY_SP, MultiSpan, FileName};
6767

6868
pub mod pretty;
69+
mod args;
6970

7071
/// Exit status code used for successful compilation and help output.
7172
pub const EXIT_SUCCESS: i32 = 0;
@@ -777,13 +778,19 @@ fn usage(verbose: bool, include_unstable_options: bool) {
777778
} else {
778779
"\n --help -v Print the full set of options rustc accepts"
779780
};
780-
println!("{}\nAdditional help:
781+
let at_path = if verbose && nightly_options::is_nightly_build() {
782+
" @path Read newline separated options from `path`\n"
783+
} else {
784+
""
785+
};
786+
println!("{options}{at_path}\nAdditional help:
781787
-C help Print codegen options
782788
-W help \
783-
Print 'lint' options and default settings{}{}\n",
784-
options.usage(message),
785-
nightly_help,
786-
verbose_help);
789+
Print 'lint' options and default settings{nightly}{verbose}\n",
790+
options = options.usage(message),
791+
at_path = at_path,
792+
nightly = nightly_help,
793+
verbose = verbose_help);
787794
}
788795

789796
fn print_wall_help() {
@@ -1008,6 +1015,12 @@ pub fn handle_options(args: &[String]) -> Option<getopts::Matches> {
10081015
// (unstable option being used on stable)
10091016
nightly_options::check_nightly_options(&matches, &config::rustc_optgroups());
10101017

1018+
// Late check to see if @file was used without unstable options enabled
1019+
if crate::args::used_unstable_argsfile() && !nightly_options::is_unstable_enabled(&matches) {
1020+
early_error(ErrorOutputType::default(),
1021+
"@path is unstable - use -Z unstable-options to enable its use");
1022+
}
1023+
10111024
if matches.opt_present("h") || matches.opt_present("help") {
10121025
// Only show unstable options in --help if we accept unstable options.
10131026
usage(matches.opt_present("verbose"), nightly_options::is_unstable_enabled(&matches));
@@ -1186,10 +1199,10 @@ pub fn main() {
11861199
init_rustc_env_logger();
11871200
let mut callbacks = TimePassesCallbacks::default();
11881201
let result = report_ices_to_stderr_if_any(|| {
1189-
let args = env::args_os().enumerate()
1190-
.map(|(i, arg)| arg.into_string().unwrap_or_else(|arg| {
1202+
let args = args::ArgsIter::new().enumerate()
1203+
.map(|(i, arg)| arg.unwrap_or_else(|err| {
11911204
early_error(ErrorOutputType::default(),
1192-
&format!("Argument {} is not valid Unicode: {:?}", i, arg))
1205+
&format!("Argument {} is not valid: {}", i, err))
11931206
}))
11941207
.collect::<Vec<_>>();
11951208
run_compiler(&args, &mut callbacks, None, None)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--cfg
2+
unbroken�
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Check to see if we can get parameters from an @argsfile file
2+
//
3+
// build-fail
4+
// normalize-stderr-test: "Argument \d+" -> "Argument $$N"
5+
// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-badutf8.args
6+
7+
#[cfg(not(cmdline_set))]
8+
compile_error!("cmdline_set not set");
9+
10+
#[cfg(not(unbroken))]
11+
compile_error!("unbroken not set");
12+
13+
fn main() {
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: Argument $N is not valid: Utf8 error in $DIR/commandline-argfile-badutf8.args
2+
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Check to see if we can get parameters from an @argsfile file
2+
//
3+
// build-fail
4+
// normalize-stderr-test: "Argument \d+" -> "Argument $$N"
5+
// normalize-stderr-test: "os error \d+" -> "os error $$ERR"
6+
// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-missing.args
7+
8+
#[cfg(not(cmdline_set))]
9+
compile_error!("cmdline_set not set");
10+
11+
#[cfg(not(unbroken))]
12+
compile_error!("unbroken not set");
13+
14+
fn main() {
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: Argument $N is not valid: IO Error: $DIR/commandline-argfile-missing.args: No such file or directory (os error $ERR)
2+

src/test/ui/commandline-argfile.args

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--cfg
2+
unbroken

src/test/ui/commandline-argfile.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Check to see if we can get parameters from an @argsfile file
2+
//
3+
// build-pass
4+
// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile.args
5+
6+
#[cfg(not(cmdline_set))]
7+
compile_error!("cmdline_set not set");
8+
9+
#[cfg(not(unbroken))]
10+
compile_error!("unbroken not set");
11+
12+
fn main() {
13+
}

0 commit comments

Comments
 (0)