diff --git a/crates/cargo-util/src/process_builder.rs b/crates/cargo-util/src/process_builder.rs index 0b4214498..ebe0e7953 100644 --- a/crates/cargo-util/src/process_builder.rs +++ b/crates/cargo-util/src/process_builder.rs @@ -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 { - 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 { + 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 { - 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 { + 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 `@` 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 { - 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 `@` 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}; diff --git a/crates/cargo-util/src/process_error.rs b/crates/cargo-util/src/process_error.rs index 57feffbef..e8607d661 100644 --- a/crates/cargo-util/src/process_error.rs +++ b/crates/cargo-util/src/process_error.rs @@ -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