State that arg in argfile must not contain newlines

This commit is contained in:
Weihang Lo 2022-04-10 12:57:30 +08:00
parent beaaefd90a
commit 877c0add35
No known key found for this signature in database
GPG Key ID: D7DBF189825E82E7

View File

@ -37,6 +37,7 @@ pub struct ProcessBuilder {
/// `true` to include environment variable in display. /// `true` to include environment variable in display.
display_env_vars: bool, display_env_vars: bool,
/// `true` to retry with an argfile if hitting "command line too big" error. /// `true` to retry with an argfile if hitting "command line too big" error.
/// See [`ProcessBuilder::retry_with_argfile`] for more information.
retry_with_argfile: bool, retry_with_argfile: bool,
} }
@ -185,6 +186,22 @@ impl ProcessBuilder {
} }
/// Enables retrying with an argfile if hitting "command line too big" error /// Enables retrying with an argfile if hitting "command line too big" error
///
/// This is primarily for the `@path` arg of rustc and rustdoc, which treat
/// each line as an command-line argument, so `LF` and `CRLF` bytes are not
/// valid as an argument for argfile at this moment.
/// For example, `RUSTDOCFLAGS="--crate-version foo\nbar" cargo doc` is
/// valid when invoking from command-line but not from argfile.
///
/// To sum up, the limitations of the argfile are:
///
/// - Must be valid UTF-8 encoded.
/// - Must not contain any newlines in each argument.
///
/// Ref:
///
/// - https://doc.rust-lang.org/rustdoc/command-line-arguments.html#path-load-command-line-flags-from-a-path
/// - https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path>
pub fn retry_with_argfile(&mut self, enabled: bool) -> &mut Self { pub fn retry_with_argfile(&mut self, enabled: bool) -> &mut Self {
self.retry_with_argfile = enabled; self.retry_with_argfile = enabled;
self self
@ -393,11 +410,6 @@ impl ProcessBuilder {
/// Builds the command with an `@<path>` argfile that contains all the /// Builds the command with an `@<path>` argfile that contains all the
/// arguments. This is primarily served for rustc/rustdoc command family. /// arguments. This is primarily served for rustc/rustdoc command family.
///
/// Ref:
///
/// - https://doc.rust-lang.org/rustdoc/command-line-arguments.html#path-load-command-line-flags-from-a-path
/// - https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path>
fn build_command_with_argfile(&self) -> io::Result<(Command, NamedTempFile)> { fn build_command_with_argfile(&self) -> io::Result<(Command, NamedTempFile)> {
use std::io::Write as _; use std::io::Write as _;
@ -411,21 +423,26 @@ impl ProcessBuilder {
log::debug!("created argfile at {path} for `{self}`"); log::debug!("created argfile at {path} for `{self}`");
let cap = self.get_args().map(|arg| arg.len() + 1).sum::<usize>(); let cap = self.get_args().map(|arg| arg.len() + 1).sum::<usize>();
let mut buf = String::with_capacity(cap); let mut buf = Vec::with_capacity(cap);
for arg in &self.args { for arg in &self.args {
let arg = arg let arg = arg.to_str().ok_or_else(|| {
.to_str() io::Error::new(
.ok_or_else(|| { io::ErrorKind::Other,
io::Error::new( format!(
io::ErrorKind::Other, "argument for argfile contains invalid UTF-8 characters: `{}`",
"argument contains invalid UTF-8 characters", arg.to_string_lossy()
) ),
})?; )
// TODO: Shall we escape line feed? })?;
buf.push_str(arg); if arg.contains('\n') {
buf.push('\n'); return Err(io::Error::new(
io::ErrorKind::Other,
format!("argument for argfile contains newlines: `{arg}`"),
));
}
writeln!(buf, "{arg}")?;
} }
tmp.write_all(buf.as_bytes())?; tmp.write_all(&mut buf)?;
Ok((cmd, tmp)) Ok((cmd, tmp))
} }
@ -552,10 +569,11 @@ mod imp {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::ProcessBuilder;
use std::fs; use std::fs;
#[test] #[test]
fn test_argfile() { fn argfile_build_succeeds() {
let mut cmd = ProcessBuilder::new("echo"); let mut cmd = ProcessBuilder::new("echo");
cmd.args(["foo", "bar"].as_slice()); cmd.args(["foo", "bar"].as_slice());
let (cmd, argfile) = cmd.build_command_with_argfile().unwrap(); let (cmd, argfile) = cmd.build_command_with_argfile().unwrap();
@ -569,4 +587,39 @@ mod tests {
let buf = fs::read_to_string(argfile.path()).unwrap(); let buf = fs::read_to_string(argfile.path()).unwrap();
assert_eq!(buf, "foo\nbar\n"); assert_eq!(buf, "foo\nbar\n");
} }
#[test]
fn argfile_build_fails_if_arg_contains_newline() {
let mut cmd = ProcessBuilder::new("echo");
cmd.arg("foo\n");
let err = cmd.build_command_with_argfile().unwrap_err();
assert_eq!(
err.to_string(),
"argument for argfile contains newlines: `foo\n`"
);
}
#[test]
fn argfile_build_fails_if_arg_contains_invalid_utf8() {
let mut cmd = ProcessBuilder::new("echo");
#[cfg(windows)]
let invalid_arg = {
use std::os::windows::prelude::*;
std::ffi::OsString::from_wide(&[0x0066, 0x006f, 0xD800, 0x006f])
};
#[cfg(unix)]
let invalid_arg = {
use std::os::unix::ffi::OsStrExt;
std::ffi::OsStr::from_bytes(&[0x66, 0x6f, 0x80, 0x6f]).to_os_string()
};
cmd.arg(invalid_arg);
let err = cmd.build_command_with_argfile().unwrap_err();
assert_eq!(
err.to_string(),
"argument for argfile contains invalid UTF-8 characters: `fo<66>o`"
);
}
} }