mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-25 11:14:46 +00:00
Ensure that temporary argfile lives longer them command execution
This commit is contained in:
parent
8e0e68e647
commit
9155c0062b
@ -13,7 +13,7 @@ use std::fmt;
|
||||
use std::io;
|
||||
use std::iter::once;
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, ExitStatus, Output, Stdio};
|
||||
use std::process::{Command, ExitStatus, Output, Stdio};
|
||||
|
||||
/// A builder object for an external process, similar to [`std::process::Command`].
|
||||
#[derive(Clone, Debug)]
|
||||
@ -213,11 +213,19 @@ impl ProcessBuilder {
|
||||
|
||||
/// Like [`Command::status`] but with a better error message.
|
||||
pub fn status(&self) -> Result<ExitStatus> {
|
||||
self.build_and_spawn(|_| {})
|
||||
.and_then(|mut child| child.wait())
|
||||
.with_context(|| {
|
||||
ProcessError::new(&format!("could not execute process {self}"), None, None)
|
||||
})
|
||||
self._status()
|
||||
.with_context(|| ProcessError::could_not_execute(self))
|
||||
}
|
||||
|
||||
fn _status(&self) -> io::Result<ExitStatus> {
|
||||
let mut cmd = self.build_command();
|
||||
match cmd.spawn() {
|
||||
Err(ref e) if self.should_retry_with_argfile(e) => {}
|
||||
Err(e) => return Err(e),
|
||||
Ok(mut child) => return child.wait(),
|
||||
}
|
||||
let (mut cmd, _argfile) = self.build_command_with_argfile()?;
|
||||
cmd.spawn()?.wait()
|
||||
}
|
||||
|
||||
/// Runs the process, waiting for completion, and mapping non-success exit codes to an error.
|
||||
@ -256,15 +264,19 @@ impl ProcessBuilder {
|
||||
|
||||
/// Like [`Command::output`] but with a better error message.
|
||||
pub fn output(&self) -> Result<Output> {
|
||||
self.build_and_spawn(|cmd| {
|
||||
cmd.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::null());
|
||||
})
|
||||
.and_then(|child| child.wait_with_output())
|
||||
.with_context(|| {
|
||||
ProcessError::new(&format!("could not execute process {self}"), None, None)
|
||||
})
|
||||
self._output()
|
||||
.with_context(|| ProcessError::could_not_execute(self))
|
||||
}
|
||||
|
||||
fn _output(&self) -> io::Result<Output> {
|
||||
let mut cmd = self.build_command();
|
||||
match piped(&mut cmd).spawn() {
|
||||
Err(ref e) if self.should_retry_with_argfile(e) => {}
|
||||
Err(e) => return Err(e),
|
||||
Ok(child) => return child.wait_with_output(),
|
||||
}
|
||||
let (mut cmd, _argfile) = self.build_command_with_argfile()?;
|
||||
piped(&mut cmd).spawn()?.wait_with_output()
|
||||
}
|
||||
|
||||
/// Executes the process, returning the stdio output, or an error if non-zero exit status.
|
||||
@ -303,12 +315,20 @@ impl ProcessBuilder {
|
||||
let mut callback_error = None;
|
||||
let mut stdout_pos = 0;
|
||||
let mut stderr_pos = 0;
|
||||
|
||||
let spawn = |mut cmd| {
|
||||
match piped(&mut cmd).spawn() {
|
||||
Err(ref e) if self.should_retry_with_argfile(e) => {}
|
||||
Err(e) => return Err(e),
|
||||
Ok(child) => return Ok((child, None)),
|
||||
};
|
||||
let (mut cmd, argfile) = self.build_command_with_argfile()?;
|
||||
Ok((piped(&mut cmd).spawn()?, Some(argfile)))
|
||||
};
|
||||
|
||||
let status = (|| {
|
||||
let mut child = self.build_and_spawn(|cmd| {
|
||||
cmd.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::null());
|
||||
})?;
|
||||
let cmd = self.build_command();
|
||||
let (mut child, _argfile) = spawn(cmd)?;
|
||||
let out = child.stdout.take().unwrap();
|
||||
let err = child.stderr.take().unwrap();
|
||||
read2(out, err, &mut |is_out, data, eof| {
|
||||
@ -356,9 +376,7 @@ impl ProcessBuilder {
|
||||
})?;
|
||||
child.wait()
|
||||
})()
|
||||
.with_context(|| {
|
||||
ProcessError::new(&format!("could not execute process {}", self), None, None)
|
||||
})?;
|
||||
.with_context(|| ProcessError::could_not_execute(self))?;
|
||||
let output = Output {
|
||||
status,
|
||||
stdout,
|
||||
@ -386,28 +404,6 @@ impl ProcessBuilder {
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Builds a command from `ProcessBuilder` and spawn it.
|
||||
///
|
||||
/// There is a risk when spawning a process, it might hit "command line
|
||||
/// too big" OS error. To handle those kind of OS errors, this method try
|
||||
/// to reinvoke the command with a `@<path>` argfile that contains all the
|
||||
/// arguments.
|
||||
///
|
||||
/// * `apply`: Modify the command before invoking. Useful for updating [`Stdio`].
|
||||
fn build_and_spawn(&self, apply: impl Fn(&mut Command)) -> io::Result<Child> {
|
||||
let mut cmd = self.build_command();
|
||||
apply(&mut cmd);
|
||||
|
||||
match cmd.spawn() {
|
||||
Err(ref e) if self.should_retry_with_argfile(e) => {
|
||||
let (mut cmd, _argfile) = self.build_command_with_argfile()?;
|
||||
apply(&mut cmd);
|
||||
cmd.spawn()
|
||||
}
|
||||
res => res,
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the command with an `@<path>` argfile that contains all the
|
||||
/// arguments. This is primarily served for rustc/rustdoc command family.
|
||||
fn build_command_with_argfile(&self) -> io::Result<(Command, NamedTempFile)> {
|
||||
@ -509,6 +505,13 @@ impl ProcessBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new pipes for stderr and stdout. Ignores stdin.
|
||||
fn piped(cmd: &mut Command) -> &mut Command {
|
||||
cmd.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::null())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod imp {
|
||||
use super::{ProcessBuilder, ProcessError};
|
||||
|
@ -95,6 +95,13 @@ impl ProcessError {
|
||||
stderr: stderr.map(|s| s.to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`ProcessError`] with "could not execute process {cmd}".
|
||||
///
|
||||
/// * `cmd` is usually but not limited to [`std::process::Command`].
|
||||
pub fn could_not_execute(cmd: impl fmt::Display) -> ProcessError {
|
||||
ProcessError::new(&format!("could not execute process {cmd}"), None, None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an [`ExitStatus`] to a human-readable string suitable for
|
||||
|
Loading…
x
Reference in New Issue
Block a user