Auto merge of #63175 - jsgf:argsfile, r=jsgf
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.
This commit is contained in:
commit
201e52e5fe
10 changed files with 139 additions and 8 deletions
|
@ -304,3 +304,10 @@ to customize the output:
|
||||||
|
|
||||||
Note that it is invalid to combine the `--json` argument with the `--color`
|
Note that it is invalid to combine the `--json` argument with the `--color`
|
||||||
argument, and it is required to combine `--json` with `--error-format=json`.
|
argument, and it is required to combine `--json` with `--error-format=json`.
|
||||||
|
|
||||||
|
## `@path`: load command-line flags from a path
|
||||||
|
|
||||||
|
If you specify `@path` on the command-line, then it will open `path` and read
|
||||||
|
command line options from it. These options are one per line; a blank line indicates
|
||||||
|
an empty option. The file can use Unix or Windows style line endings, and must be
|
||||||
|
encoded as UTF-8.
|
||||||
|
|
53
src/librustc_driver/args.rs
Normal file
53
src/librustc_driver/args.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
|
use std::str;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
static USED_ARGSFILE_FEATURE: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
pub fn used_unstable_argsfile() -> bool {
|
||||||
|
USED_ARGSFILE_FEATURE.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arg_expand(arg: String) -> Result<Vec<String>, Error> {
|
||||||
|
if arg.starts_with("@") {
|
||||||
|
let path = &arg[1..];
|
||||||
|
let file = match fs::read_to_string(path) {
|
||||||
|
Ok(file) => {
|
||||||
|
USED_ARGSFILE_FEATURE.store(true, Ordering::Relaxed);
|
||||||
|
file
|
||||||
|
}
|
||||||
|
Err(ref err) if err.kind() == io::ErrorKind::InvalidData => {
|
||||||
|
return Err(Error::Utf8Error(Some(path.to_string())));
|
||||||
|
}
|
||||||
|
Err(err) => return Err(Error::IOError(path.to_string(), err)),
|
||||||
|
};
|
||||||
|
Ok(file.lines().map(ToString::to_string).collect())
|
||||||
|
} else {
|
||||||
|
Ok(vec![arg])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Utf8Error(Option<String>),
|
||||||
|
IOError(String, io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::Utf8Error(None) => write!(fmt, "Utf8 error"),
|
||||||
|
Error::Utf8Error(Some(path)) => write!(fmt, "Utf8 error in {}", path),
|
||||||
|
Error::IOError(path, err) => write!(fmt, "IO Error: {}: {}", path, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error {
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"argument error"
|
||||||
|
}
|
||||||
|
}
|
|
@ -68,6 +68,7 @@ use syntax::symbol::sym;
|
||||||
use syntax_pos::{DUMMY_SP, MultiSpan, FileName};
|
use syntax_pos::{DUMMY_SP, MultiSpan, FileName};
|
||||||
|
|
||||||
pub mod pretty;
|
pub mod pretty;
|
||||||
|
mod args;
|
||||||
|
|
||||||
/// Exit status code used for successful compilation and help output.
|
/// Exit status code used for successful compilation and help output.
|
||||||
pub const EXIT_SUCCESS: i32 = 0;
|
pub const EXIT_SUCCESS: i32 = 0;
|
||||||
|
@ -141,14 +142,22 @@ impl Callbacks for TimePassesCallbacks {
|
||||||
// See comments on CompilerCalls below for details about the callbacks argument.
|
// See comments on CompilerCalls below for details about the callbacks argument.
|
||||||
// The FileLoader provides a way to load files from sources other than the file system.
|
// The FileLoader provides a way to load files from sources other than the file system.
|
||||||
pub fn run_compiler(
|
pub fn run_compiler(
|
||||||
args: &[String],
|
at_args: &[String],
|
||||||
callbacks: &mut (dyn Callbacks + Send),
|
callbacks: &mut (dyn Callbacks + Send),
|
||||||
file_loader: Option<Box<dyn FileLoader + Send + Sync>>,
|
file_loader: Option<Box<dyn FileLoader + Send + Sync>>,
|
||||||
emitter: Option<Box<dyn Write + Send>>
|
emitter: Option<Box<dyn Write + Send>>
|
||||||
) -> interface::Result<()> {
|
) -> interface::Result<()> {
|
||||||
|
let mut args = Vec::new();
|
||||||
|
for arg in at_args {
|
||||||
|
match args::arg_expand(arg.clone()) {
|
||||||
|
Ok(arg) => args.extend(arg),
|
||||||
|
Err(err) => early_error(ErrorOutputType::default(),
|
||||||
|
&format!("Failed to load argument file: {}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
let diagnostic_output = emitter.map(|emitter| DiagnosticOutput::Raw(emitter))
|
let diagnostic_output = emitter.map(|emitter| DiagnosticOutput::Raw(emitter))
|
||||||
.unwrap_or(DiagnosticOutput::Default);
|
.unwrap_or(DiagnosticOutput::Default);
|
||||||
let matches = match handle_options(args) {
|
let matches = match handle_options(&args) {
|
||||||
Some(matches) => matches,
|
Some(matches) => matches,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
|
@ -779,13 +788,19 @@ fn usage(verbose: bool, include_unstable_options: bool) {
|
||||||
} else {
|
} else {
|
||||||
"\n --help -v Print the full set of options rustc accepts"
|
"\n --help -v Print the full set of options rustc accepts"
|
||||||
};
|
};
|
||||||
println!("{}\nAdditional help:
|
let at_path = if verbose && nightly_options::is_nightly_build() {
|
||||||
|
" @path Read newline separated options from `path`\n"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
println!("{options}{at_path}\nAdditional help:
|
||||||
-C help Print codegen options
|
-C help Print codegen options
|
||||||
-W help \
|
-W help \
|
||||||
Print 'lint' options and default settings{}{}\n",
|
Print 'lint' options and default settings{nightly}{verbose}\n",
|
||||||
options.usage(message),
|
options = options.usage(message),
|
||||||
nightly_help,
|
at_path = at_path,
|
||||||
verbose_help);
|
nightly = nightly_help,
|
||||||
|
verbose = verbose_help);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_wall_help() {
|
fn print_wall_help() {
|
||||||
|
@ -1010,6 +1025,12 @@ pub fn handle_options(args: &[String]) -> Option<getopts::Matches> {
|
||||||
// (unstable option being used on stable)
|
// (unstable option being used on stable)
|
||||||
nightly_options::check_nightly_options(&matches, &config::rustc_optgroups());
|
nightly_options::check_nightly_options(&matches, &config::rustc_optgroups());
|
||||||
|
|
||||||
|
// Late check to see if @file was used without unstable options enabled
|
||||||
|
if crate::args::used_unstable_argsfile() && !nightly_options::is_unstable_enabled(&matches) {
|
||||||
|
early_error(ErrorOutputType::default(),
|
||||||
|
"@path is unstable - use -Z unstable-options to enable its use");
|
||||||
|
}
|
||||||
|
|
||||||
if matches.opt_present("h") || matches.opt_present("help") {
|
if matches.opt_present("h") || matches.opt_present("help") {
|
||||||
// Only show unstable options in --help if we accept unstable options.
|
// Only show unstable options in --help if we accept unstable options.
|
||||||
usage(matches.opt_present("verbose"), nightly_options::is_unstable_enabled(&matches));
|
usage(matches.opt_present("verbose"), nightly_options::is_unstable_enabled(&matches));
|
||||||
|
@ -1190,7 +1211,7 @@ pub fn main() {
|
||||||
let result = report_ices_to_stderr_if_any(|| {
|
let result = report_ices_to_stderr_if_any(|| {
|
||||||
let args = env::args_os().enumerate()
|
let args = env::args_os().enumerate()
|
||||||
.map(|(i, arg)| arg.into_string().unwrap_or_else(|arg| {
|
.map(|(i, arg)| arg.into_string().unwrap_or_else(|arg| {
|
||||||
early_error(ErrorOutputType::default(),
|
early_error(ErrorOutputType::default(),
|
||||||
&format!("Argument {} is not valid Unicode: {:?}", i, arg))
|
&format!("Argument {} is not valid Unicode: {:?}", i, arg))
|
||||||
}))
|
}))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
2
src/test/ui/commandline-argfile-badutf8.args
Normal file
2
src/test/ui/commandline-argfile-badutf8.args
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
--cfg
|
||||||
|
unbroken€
|
13
src/test/ui/commandline-argfile-badutf8.rs
Normal file
13
src/test/ui/commandline-argfile-badutf8.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Check to see if we can get parameters from an @argsfile file
|
||||||
|
//
|
||||||
|
// build-fail
|
||||||
|
// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-badutf8.args
|
||||||
|
|
||||||
|
#[cfg(not(cmdline_set))]
|
||||||
|
compile_error!("cmdline_set not set");
|
||||||
|
|
||||||
|
#[cfg(not(unbroken))]
|
||||||
|
compile_error!("unbroken not set");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
}
|
2
src/test/ui/commandline-argfile-badutf8.stderr
Normal file
2
src/test/ui/commandline-argfile-badutf8.stderr
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
error: Failed to load argument file: Utf8 error in $DIR/commandline-argfile-badutf8.args
|
||||||
|
|
16
src/test/ui/commandline-argfile-missing.rs
Normal file
16
src/test/ui/commandline-argfile-missing.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// Check to see if we can get parameters from an @argsfile file
|
||||||
|
//
|
||||||
|
// ignore-tidy-linelength
|
||||||
|
// build-fail
|
||||||
|
// normalize-stderr-test: "os error \d+" -> "os error $$ERR"
|
||||||
|
// normalize-stderr-test: "commandline-argfile-missing.args:[^(]*" -> "commandline-argfile-missing.args: $$FILE_MISSING "
|
||||||
|
// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-missing.args
|
||||||
|
|
||||||
|
#[cfg(not(cmdline_set))]
|
||||||
|
compile_error!("cmdline_set not set");
|
||||||
|
|
||||||
|
#[cfg(not(unbroken))]
|
||||||
|
compile_error!("unbroken not set");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
}
|
2
src/test/ui/commandline-argfile-missing.stderr
Normal file
2
src/test/ui/commandline-argfile-missing.stderr
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
error: Failed to load argument file: IO Error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
|
||||||
|
|
2
src/test/ui/commandline-argfile.args
Normal file
2
src/test/ui/commandline-argfile.args
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
--cfg
|
||||||
|
unbroken
|
13
src/test/ui/commandline-argfile.rs
Normal file
13
src/test/ui/commandline-argfile.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Check to see if we can get parameters from an @argsfile file
|
||||||
|
//
|
||||||
|
// build-pass
|
||||||
|
// compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile.args
|
||||||
|
|
||||||
|
#[cfg(not(cmdline_set))]
|
||||||
|
compile_error!("cmdline_set not set");
|
||||||
|
|
||||||
|
#[cfg(not(unbroken))]
|
||||||
|
compile_error!("unbroken not set");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue