Skip to content

Commit f0e3258

Browse files
committed
rustc: implement argsfiles for command line
This makes `rustc` support `@path` arguments on the command line. The `path` is opened and the file is interpreted as new command line options which are logically inserted at that point in the command-line. The options in the file are one per line. The file is UTF-8 encoded, and may have either Unix or Windows line endings. It does not support recursive use of `@path`. This is useful for very large command lines, or when command-lines are being generated into files by other tooling.
1 parent 9703ef6 commit f0e3258

11 files changed

+347
-4
lines changed

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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#![allow(dead_code)]
2+
3+
use std::env;
4+
use std::error;
5+
use std::fmt;
6+
use std::fs;
7+
use std::io;
8+
use std::str;
9+
10+
#[cfg(test)]
11+
mod tests;
12+
13+
/// States for parsing text
14+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
15+
enum State {
16+
Normal, // within normal text
17+
Cr, // just saw \r
18+
Lf, // just saw \n
19+
}
20+
21+
struct FileArgs {
22+
path: String,
23+
input: Vec<u8>,
24+
offset: usize,
25+
}
26+
27+
impl FileArgs {
28+
pub fn new(path: String, input: Vec<u8>) -> Self {
29+
FileArgs { path, input, offset: 0 }
30+
}
31+
}
32+
33+
impl Iterator for FileArgs {
34+
type Item = Result<String, Error>;
35+
36+
fn next(&mut self) -> Option<Self::Item> {
37+
if self.offset >= self.input.len() {
38+
// All done
39+
return None;
40+
}
41+
42+
use State::*;
43+
let mut state = Normal;
44+
let start = self.offset;
45+
let mut end = start;
46+
47+
for (idx, b) in self.input[start..].iter().enumerate() {
48+
let idx = start + idx + 1;
49+
50+
self.offset = idx;
51+
52+
match (b, state) {
53+
(b'\r', Normal) => state = Cr,
54+
(b'\n', Normal) => state = Lf,
55+
56+
(b'\r', Lf) | (b'\n', Cr) => {
57+
// Two-character line break (accept \r\n and \n\r(?)), so consume them both
58+
break;
59+
}
60+
61+
(_, Cr) | (_, Lf) => {
62+
// Peeked at character after single-character line break, so rewind to visit it
63+
// next time around.
64+
self.offset = idx - 1;
65+
break;
66+
}
67+
68+
(_, _) => {
69+
end = idx;
70+
state = Normal;
71+
}
72+
}
73+
}
74+
75+
Some(
76+
String::from_utf8(self.input[start..end].to_vec())
77+
.map_err(|_| Error::Utf8Error(Some(self.path.clone()))),
78+
)
79+
}
80+
}
81+
82+
pub struct ArgsIter {
83+
base: env::ArgsOs,
84+
file: Option<FileArgs>,
85+
}
86+
87+
impl ArgsIter {
88+
pub fn new() -> Self {
89+
ArgsIter { base: env::args_os(), file: None }
90+
}
91+
}
92+
93+
impl Iterator for ArgsIter {
94+
type Item = Result<String, Error>;
95+
96+
fn next(&mut self) -> Option<Self::Item> {
97+
loop {
98+
if let Some(ref mut file) = &mut self.file {
99+
match file.next() {
100+
Some(res) => return Some(res.map_err(From::from)),
101+
None => self.file = None,
102+
}
103+
}
104+
105+
let arg =
106+
self.base.next().map(|arg| arg.into_string().map_err(|_| Error::Utf8Error(None)));
107+
match arg {
108+
Some(Err(err)) => return Some(Err(err)),
109+
Some(Ok(ref arg)) if arg.starts_with("@") => {
110+
// can't not be utf-8 now
111+
let path = str::from_utf8(&arg.as_bytes()[1..]).unwrap();
112+
let file = match fs::read(path) {
113+
Ok(file) => file,
114+
Err(err) => return Some(Err(Error::IOError(path.to_string(), err))),
115+
};
116+
self.file = Some(FileArgs::new(path.to_string(), file));
117+
}
118+
Some(Ok(arg)) => return Some(Ok(arg)),
119+
None => return None,
120+
}
121+
}
122+
}
123+
}
124+
125+
#[derive(Debug)]
126+
pub enum Error {
127+
Utf8Error(Option<String>),
128+
IOError(String, io::Error),
129+
}
130+
131+
impl fmt::Display for Error {
132+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
133+
match self {
134+
Error::Utf8Error(None) => write!(fmt, "Utf8 error"),
135+
Error::Utf8Error(Some(path)) => write!(fmt, "Utf8 error in {}", path),
136+
Error::IOError(path, err) => write!(fmt, "IO Error: {}: {}", path, err),
137+
}
138+
}
139+
}
140+
141+
impl error::Error for Error {
142+
fn description(&self) -> &'static str {
143+
"argument error"
144+
}
145+
}

src/librustc_driver/args/tests.rs

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

src/librustc_driver/lib.rs

Lines changed: 11 additions & 4 deletions
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,11 +778,17 @@ 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 {
782+
" @path Read newline separated options from `path`\n"
783+
} else {
784+
""
785+
};
786+
println!("{}{}\nAdditional help:
781787
-C help Print codegen options
782788
-W help \
783789
Print 'lint' options and default settings{}{}\n",
784790
options.usage(message),
791+
at_path,
785792
nightly_help,
786793
verbose_help);
787794
}
@@ -1186,10 +1193,10 @@ pub fn main() {
11861193
init_rustc_env_logger();
11871194
let mut callbacks = TimePassesCallbacks::default();
11881195
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| {
1196+
let args = args::ArgsIter::new().enumerate()
1197+
.map(|(i, arg)| arg.unwrap_or_else(|err| {
11911198
early_error(ErrorOutputType::default(),
1192-
&format!("Argument {} is not valid Unicode: {:?}", i, arg))
1199+
&format!("Argument {} is not valid: {}", i, err))
11931200
}))
11941201
.collect::<Vec<_>>();
11951202
run_compiler(&args, &mut callbacks, None, None)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--cfg
2+
unbroken�
Lines changed: 13 additions & 0 deletions
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-fail
4+
// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-badutf8.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+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: Argument 19 is not valid: Utf8 error in $DIR/commandline-argfile-badutf8.args
2+
Lines changed: 13 additions & 0 deletions
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-fail
4+
// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-missing.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+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: Argument 18 is not valid: IO Error: $DIR/commandline-argfile-missing.args: No such file or directory (os error 2)
2+

src/test/ui/commandline-argfile.args

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--cfg
2+
unbroken

0 commit comments

Comments
 (0)