| 1 | //! Miscellaneous helpers for running commands | 
| 2 |  | 
|---|
| 3 | use std::{ | 
|---|
| 4 | borrow::Cow, | 
|---|
| 5 | collections::hash_map, | 
|---|
| 6 | ffi::OsString, | 
|---|
| 7 | fmt::Display, | 
|---|
| 8 | fs, | 
|---|
| 9 | hash::Hasher, | 
|---|
| 10 | io::{self, Read, Write}, | 
|---|
| 11 | path::Path, | 
|---|
| 12 | process::{Child, ChildStderr, Command, Stdio}, | 
|---|
| 13 | sync::{ | 
|---|
| 14 | atomic::{AtomicBool, Ordering}, | 
|---|
| 15 | Arc, | 
|---|
| 16 | }, | 
|---|
| 17 | }; | 
|---|
| 18 |  | 
|---|
| 19 | use crate::{Error, ErrorKind, Object}; | 
|---|
| 20 |  | 
|---|
| 21 | #[ derive(Clone, Debug)] | 
|---|
| 22 | pub(crate) struct CargoOutput { | 
|---|
| 23 | pub(crate) metadata: bool, | 
|---|
| 24 | pub(crate) warnings: bool, | 
|---|
| 25 | pub(crate) debug: bool, | 
|---|
| 26 | pub(crate) output: OutputKind, | 
|---|
| 27 | checked_dbg_var: Arc<AtomicBool>, | 
|---|
| 28 | } | 
|---|
| 29 |  | 
|---|
| 30 | /// Different strategies for handling compiler output (to stdout) | 
|---|
| 31 | #[ derive(Clone, Debug)] | 
|---|
| 32 | pub(crate) enum OutputKind { | 
|---|
| 33 | /// Forward the output to this process' stdout ([`Stdio::inherit()`]) | 
|---|
| 34 | Forward, | 
|---|
| 35 | /// Discard the output ([`Stdio::null()`]) | 
|---|
| 36 | Discard, | 
|---|
| 37 | /// Capture the result (`[Stdio::piped()`]) | 
|---|
| 38 | Capture, | 
|---|
| 39 | } | 
|---|
| 40 |  | 
|---|
| 41 | impl CargoOutput { | 
|---|
| 42 | pub(crate) fn new() -> Self { | 
|---|
| 43 | #[ allow(clippy::disallowed_methods)] | 
|---|
| 44 | Self { | 
|---|
| 45 | metadata: true, | 
|---|
| 46 | warnings: true, | 
|---|
| 47 | output: OutputKind::Forward, | 
|---|
| 48 | debug: match std::env::var_os( "CC_ENABLE_DEBUG_OUTPUT") { | 
|---|
| 49 | Some(v) => v != "0"&& v != "false"&& v != "", | 
|---|
| 50 | None => false, | 
|---|
| 51 | }, | 
|---|
| 52 | checked_dbg_var: Arc::new(AtomicBool::new(false)), | 
|---|
| 53 | } | 
|---|
| 54 | } | 
|---|
| 55 |  | 
|---|
| 56 | pub(crate) fn print_metadata(&self, s: &dyn Display) { | 
|---|
| 57 | if self.metadata { | 
|---|
| 58 | println!( "{} ", s); | 
|---|
| 59 | } | 
|---|
| 60 | } | 
|---|
| 61 |  | 
|---|
| 62 | pub(crate) fn print_warning(&self, arg: &dyn Display) { | 
|---|
| 63 | if self.warnings { | 
|---|
| 64 | println!( "cargo:warning={} ", arg); | 
|---|
| 65 | } | 
|---|
| 66 | } | 
|---|
| 67 |  | 
|---|
| 68 | pub(crate) fn print_debug(&self, arg: &dyn Display) { | 
|---|
| 69 | if self.metadata && !self.checked_dbg_var.load(Ordering::Relaxed) { | 
|---|
| 70 | self.checked_dbg_var.store(true, Ordering::Relaxed); | 
|---|
| 71 | println!( "cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT"); | 
|---|
| 72 | } | 
|---|
| 73 | if self.debug { | 
|---|
| 74 | println!( "{} ", arg); | 
|---|
| 75 | } | 
|---|
| 76 | } | 
|---|
| 77 |  | 
|---|
| 78 | fn stdio_for_warnings(&self) -> Stdio { | 
|---|
| 79 | if self.warnings { | 
|---|
| 80 | Stdio::piped() | 
|---|
| 81 | } else { | 
|---|
| 82 | Stdio::null() | 
|---|
| 83 | } | 
|---|
| 84 | } | 
|---|
| 85 |  | 
|---|
| 86 | fn stdio_for_output(&self) -> Stdio { | 
|---|
| 87 | match self.output { | 
|---|
| 88 | OutputKind::Capture => Stdio::piped(), | 
|---|
| 89 | OutputKind::Forward => Stdio::inherit(), | 
|---|
| 90 | OutputKind::Discard => Stdio::null(), | 
|---|
| 91 | } | 
|---|
| 92 | } | 
|---|
| 93 | } | 
|---|
| 94 |  | 
|---|
| 95 | pub(crate) struct StderrForwarder { | 
|---|
| 96 | inner: Option<(ChildStderr, Vec<u8>)>, | 
|---|
| 97 | #[ cfg(feature = "parallel")] | 
|---|
| 98 | is_non_blocking: bool, | 
|---|
| 99 | #[ cfg(feature = "parallel")] | 
|---|
| 100 | bytes_available_failed: bool, | 
|---|
| 101 | /// number of bytes buffered in inner | 
|---|
| 102 | bytes_buffered: usize, | 
|---|
| 103 | } | 
|---|
| 104 |  | 
|---|
| 105 | const MIN_BUFFER_CAPACITY: usize = 100; | 
|---|
| 106 |  | 
|---|
| 107 | impl StderrForwarder { | 
|---|
| 108 | pub(crate) fn new(child: &mut Child) -> Self { | 
|---|
| 109 | Self { | 
|---|
| 110 | inner: child | 
|---|
| 111 | .stderr | 
|---|
| 112 | .take() | 
|---|
| 113 | .map(|stderr| (stderr, Vec::with_capacity(MIN_BUFFER_CAPACITY))), | 
|---|
| 114 | bytes_buffered: 0, | 
|---|
| 115 | #[ cfg(feature = "parallel")] | 
|---|
| 116 | is_non_blocking: false, | 
|---|
| 117 | #[ cfg(feature = "parallel")] | 
|---|
| 118 | bytes_available_failed: false, | 
|---|
| 119 | } | 
|---|
| 120 | } | 
|---|
| 121 |  | 
|---|
| 122 | fn forward_available(&mut self) -> bool { | 
|---|
| 123 | if let Some((stderr, buffer)) = self.inner.as_mut() { | 
|---|
| 124 | loop { | 
|---|
| 125 | // For non-blocking we check to see if there is data available, so we should try to | 
|---|
| 126 | // read at least that much. For blocking, always read at least the minimum amount. | 
|---|
| 127 | #[ cfg(not(feature = "parallel"))] | 
|---|
| 128 | let to_reserve = MIN_BUFFER_CAPACITY; | 
|---|
| 129 | #[ cfg(feature = "parallel")] | 
|---|
| 130 | let to_reserve = if self.is_non_blocking && !self.bytes_available_failed { | 
|---|
| 131 | match crate::parallel::stderr::bytes_available(stderr) { | 
|---|
| 132 | #[ cfg(windows)] | 
|---|
| 133 | Ok(0) => break false, | 
|---|
| 134 | #[ cfg(unix)] | 
|---|
| 135 | Ok(0) => { | 
|---|
| 136 | // On Unix, depending on the implementation, we may sometimes get 0 in a | 
|---|
| 137 | // loop (either there is data available or the pipe is broken), so | 
|---|
| 138 | // continue with the non-blocking read anyway. | 
|---|
| 139 | MIN_BUFFER_CAPACITY | 
|---|
| 140 | } | 
|---|
| 141 | #[ cfg(windows)] | 
|---|
| 142 | Err(_) => { | 
|---|
| 143 | // On Windows, if we get an error then the pipe is broken, so flush | 
|---|
| 144 | // the buffer and bail. | 
|---|
| 145 | if !buffer.is_empty() { | 
|---|
| 146 | write_warning(&buffer[..]); | 
|---|
| 147 | } | 
|---|
| 148 | self.inner = None; | 
|---|
| 149 | break true; | 
|---|
| 150 | } | 
|---|
| 151 | #[ cfg(unix)] | 
|---|
| 152 | Err(_) => { | 
|---|
| 153 | // On Unix, depending on the implementation, we may get spurious | 
|---|
| 154 | // errors so make a note not to use bytes_available again and try | 
|---|
| 155 | // the non-blocking read anyway. | 
|---|
| 156 | self.bytes_available_failed = true; | 
|---|
| 157 | MIN_BUFFER_CAPACITY | 
|---|
| 158 | } | 
|---|
| 159 | #[ cfg(target_family = "wasm")] | 
|---|
| 160 | Err(_) => panic!( "bytes_available should always succeed on wasm"), | 
|---|
| 161 | Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available), | 
|---|
| 162 | } | 
|---|
| 163 | } else { | 
|---|
| 164 | MIN_BUFFER_CAPACITY | 
|---|
| 165 | }; | 
|---|
| 166 | if self.bytes_buffered + to_reserve > buffer.len() { | 
|---|
| 167 | buffer.resize(self.bytes_buffered + to_reserve, 0); | 
|---|
| 168 | } | 
|---|
| 169 |  | 
|---|
| 170 | match stderr.read(&mut buffer[self.bytes_buffered..]) { | 
|---|
| 171 | Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { | 
|---|
| 172 | // No data currently, yield back. | 
|---|
| 173 | break false; | 
|---|
| 174 | } | 
|---|
| 175 | Err(err) if err.kind() == std::io::ErrorKind::Interrupted => { | 
|---|
| 176 | // Interrupted, try again. | 
|---|
| 177 | continue; | 
|---|
| 178 | } | 
|---|
| 179 | Ok(bytes_read) if bytes_read != 0 => { | 
|---|
| 180 | self.bytes_buffered += bytes_read; | 
|---|
| 181 | let mut consumed = 0; | 
|---|
| 182 | for line in buffer[..self.bytes_buffered].split_inclusive(|&b| b == b'\n ') { | 
|---|
| 183 | // Only forward complete lines, leave the rest in the buffer. | 
|---|
| 184 | if let Some(( b'\n ', line)) = line.split_last() { | 
|---|
| 185 | consumed += line.len() + 1; | 
|---|
| 186 | write_warning(line); | 
|---|
| 187 | } | 
|---|
| 188 | } | 
|---|
| 189 | if consumed > 0 && consumed < self.bytes_buffered { | 
|---|
| 190 | // Remove the consumed bytes from buffer | 
|---|
| 191 | buffer.copy_within(consumed.., 0); | 
|---|
| 192 | } | 
|---|
| 193 | self.bytes_buffered -= consumed; | 
|---|
| 194 | } | 
|---|
| 195 | res => { | 
|---|
| 196 | // End of stream: flush remaining data and bail. | 
|---|
| 197 | if self.bytes_buffered > 0 { | 
|---|
| 198 | write_warning(&buffer[..self.bytes_buffered]); | 
|---|
| 199 | } | 
|---|
| 200 | if let Err(err) = res { | 
|---|
| 201 | write_warning( | 
|---|
| 202 | format!( "Failed to read from child stderr: {err} ").as_bytes(), | 
|---|
| 203 | ); | 
|---|
| 204 | } | 
|---|
| 205 | self.inner.take(); | 
|---|
| 206 | break true; | 
|---|
| 207 | } | 
|---|
| 208 | } | 
|---|
| 209 | } | 
|---|
| 210 | } else { | 
|---|
| 211 | true | 
|---|
| 212 | } | 
|---|
| 213 | } | 
|---|
| 214 |  | 
|---|
| 215 | #[ cfg(feature = "parallel")] | 
|---|
| 216 | pub(crate) fn set_non_blocking(&mut self) -> Result<(), Error> { | 
|---|
| 217 | assert!(!self.is_non_blocking); | 
|---|
| 218 |  | 
|---|
| 219 | #[ cfg(unix)] | 
|---|
| 220 | if let Some((stderr, _)) = self.inner.as_ref() { | 
|---|
| 221 | crate::parallel::stderr::set_non_blocking(stderr)?; | 
|---|
| 222 | } | 
|---|
| 223 |  | 
|---|
| 224 | self.is_non_blocking = true; | 
|---|
| 225 | Ok(()) | 
|---|
| 226 | } | 
|---|
| 227 |  | 
|---|
| 228 | #[ cfg(feature = "parallel")] | 
|---|
| 229 | fn forward_all(&mut self) { | 
|---|
| 230 | while !self.forward_available() {} | 
|---|
| 231 | } | 
|---|
| 232 |  | 
|---|
| 233 | #[ cfg(not(feature = "parallel"))] | 
|---|
| 234 | fn forward_all(&mut self) { | 
|---|
| 235 | let forward_result = self.forward_available(); | 
|---|
| 236 | assert!(forward_result, "Should have consumed all data"); | 
|---|
| 237 | } | 
|---|
| 238 | } | 
|---|
| 239 |  | 
|---|
| 240 | fn write_warning(line: &[u8]) { | 
|---|
| 241 | let stdout: Stdout = io::stdout(); | 
|---|
| 242 | let mut stdout: StdoutLock<'static> = stdout.lock(); | 
|---|
| 243 | stdout.write_all(buf: b"cargo:warning=").unwrap(); | 
|---|
| 244 | stdout.write_all(buf:line).unwrap(); | 
|---|
| 245 | stdout.write_all(buf: b"\n ").unwrap(); | 
|---|
| 246 | } | 
|---|
| 247 |  | 
|---|
| 248 | fn wait_on_child( | 
|---|
| 249 | cmd: &Command, | 
|---|
| 250 | child: &mut Child, | 
|---|
| 251 | cargo_output: &CargoOutput, | 
|---|
| 252 | ) -> Result<(), Error> { | 
|---|
| 253 | StderrForwarder::new(child).forward_all(); | 
|---|
| 254 |  | 
|---|
| 255 | let status: ExitStatus = match child.wait() { | 
|---|
| 256 | Ok(s: ExitStatus) => s, | 
|---|
| 257 | Err(e: Error) => { | 
|---|
| 258 | return Err(Error::new( | 
|---|
| 259 | kind:ErrorKind::ToolExecError, | 
|---|
| 260 | message:format!( "failed to wait on spawned child process `{cmd:?} `: {e} "), | 
|---|
| 261 | )); | 
|---|
| 262 | } | 
|---|
| 263 | }; | 
|---|
| 264 |  | 
|---|
| 265 | cargo_output.print_debug(&status); | 
|---|
| 266 |  | 
|---|
| 267 | if status.success() { | 
|---|
| 268 | Ok(()) | 
|---|
| 269 | } else { | 
|---|
| 270 | Err(Error::new( | 
|---|
| 271 | kind:ErrorKind::ToolExecError, | 
|---|
| 272 | message:format!( "command did not execute successfully (status code {status} ): {cmd:?} "), | 
|---|
| 273 | )) | 
|---|
| 274 | } | 
|---|
| 275 | } | 
|---|
| 276 |  | 
|---|
| 277 | /// Find the destination object path for each file in the input source files, | 
|---|
| 278 | /// and store them in the output Object. | 
|---|
| 279 | pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> { | 
|---|
| 280 | let mut objects = Vec::with_capacity(files.len()); | 
|---|
| 281 | for file in files { | 
|---|
| 282 | let basename = file | 
|---|
| 283 | .file_name() | 
|---|
| 284 | .ok_or_else(|| { | 
|---|
| 285 | Error::new( | 
|---|
| 286 | ErrorKind::InvalidArgument, | 
|---|
| 287 | "No file_name for object file path!", | 
|---|
| 288 | ) | 
|---|
| 289 | })? | 
|---|
| 290 | .to_string_lossy(); | 
|---|
| 291 | let dirname = file | 
|---|
| 292 | .parent() | 
|---|
| 293 | .ok_or_else(|| { | 
|---|
| 294 | Error::new( | 
|---|
| 295 | ErrorKind::InvalidArgument, | 
|---|
| 296 | "No parent for object file path!", | 
|---|
| 297 | ) | 
|---|
| 298 | })? | 
|---|
| 299 | .to_string_lossy(); | 
|---|
| 300 |  | 
|---|
| 301 | // Hash the dirname. This should prevent conflicts if we have multiple | 
|---|
| 302 | // object files with the same filename in different subfolders. | 
|---|
| 303 | let mut hasher = hash_map::DefaultHasher::new(); | 
|---|
| 304 |  | 
|---|
| 305 | // Make the dirname relative (if possible) to avoid full system paths influencing the sha | 
|---|
| 306 | // and making the output system-dependent | 
|---|
| 307 | // | 
|---|
| 308 | // NOTE: Here we allow using std::env::var (instead of Build::getenv) because | 
|---|
| 309 | // CARGO_* variables always trigger a rebuild when changed | 
|---|
| 310 | #[ allow(clippy::disallowed_methods)] | 
|---|
| 311 | let dirname = if let Some(root) = std::env::var_os( "CARGO_MANIFEST_DIR") { | 
|---|
| 312 | let root = root.to_string_lossy(); | 
|---|
| 313 | Cow::Borrowed(dirname.strip_prefix(&*root).unwrap_or(&dirname)) | 
|---|
| 314 | } else { | 
|---|
| 315 | dirname | 
|---|
| 316 | }; | 
|---|
| 317 |  | 
|---|
| 318 | hasher.write(dirname.as_bytes()); | 
|---|
| 319 | if let Some(extension) = file.extension() { | 
|---|
| 320 | hasher.write(extension.to_string_lossy().as_bytes()); | 
|---|
| 321 | } | 
|---|
| 322 | let obj = dst | 
|---|
| 323 | .join(format!( "{:016x} -{} ", hasher.finish(), basename)) | 
|---|
| 324 | .with_extension( "o"); | 
|---|
| 325 |  | 
|---|
| 326 | match obj.parent() { | 
|---|
| 327 | Some(s) => fs::create_dir_all(s)?, | 
|---|
| 328 | None => { | 
|---|
| 329 | return Err(Error::new( | 
|---|
| 330 | ErrorKind::InvalidArgument, | 
|---|
| 331 | "dst is an invalid path with no parent", | 
|---|
| 332 | )); | 
|---|
| 333 | } | 
|---|
| 334 | }; | 
|---|
| 335 |  | 
|---|
| 336 | objects.push(Object::new(file.to_path_buf(), obj)); | 
|---|
| 337 | } | 
|---|
| 338 |  | 
|---|
| 339 | Ok(objects) | 
|---|
| 340 | } | 
|---|
| 341 |  | 
|---|
| 342 | pub(crate) fn run(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<(), Error> { | 
|---|
| 343 | let mut child: Child = spawn(cmd, cargo_output)?; | 
|---|
| 344 | wait_on_child(cmd, &mut child, cargo_output) | 
|---|
| 345 | } | 
|---|
| 346 |  | 
|---|
| 347 | pub(crate) fn run_output(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<Vec<u8>, Error> { | 
|---|
| 348 | // We specifically need the output to be captured, so override default | 
|---|
| 349 | let mut captured_cargo_output: CargoOutput = cargo_output.clone(); | 
|---|
| 350 | captured_cargo_output.output = OutputKind::Capture; | 
|---|
| 351 | let mut child: Child = spawn(cmd, &captured_cargo_output)?; | 
|---|
| 352 |  | 
|---|
| 353 | let mut stdout: Vec = vec![]; | 
|---|
| 354 | childResult | 
|---|
| 355 | .stdout | 
|---|
| 356 | .take() | 
|---|
| 357 | .unwrap() | 
|---|
| 358 | .read_to_end(&mut stdout) | 
|---|
| 359 | .unwrap(); | 
|---|
| 360 |  | 
|---|
| 361 | // Don't care about this output, use the normal settings | 
|---|
| 362 | wait_on_child(cmd, &mut child, cargo_output)?; | 
|---|
| 363 |  | 
|---|
| 364 | Ok(stdout) | 
|---|
| 365 | } | 
|---|
| 366 |  | 
|---|
| 367 | pub(crate) fn spawn(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<Child, Error> { | 
|---|
| 368 | struct ResetStderr<'cmd>(&'cmd mut Command); | 
|---|
| 369 |  | 
|---|
| 370 | impl Drop for ResetStderr<'_> { | 
|---|
| 371 | fn drop(&mut self) { | 
|---|
| 372 | // Reset stderr to default to release pipe_writer so that print thread will | 
|---|
| 373 | // not block forever. | 
|---|
| 374 | self.0.stderr(Stdio::inherit()); | 
|---|
| 375 | } | 
|---|
| 376 | } | 
|---|
| 377 |  | 
|---|
| 378 | cargo_output.print_debug(&format_args!( "running: {:?} ", cmd)); | 
|---|
| 379 |  | 
|---|
| 380 | let cmd = ResetStderr(cmd); | 
|---|
| 381 | let child = cmd | 
|---|
| 382 | .0 | 
|---|
| 383 | .stderr(cargo_output.stdio_for_warnings()) | 
|---|
| 384 | .stdout(cargo_output.stdio_for_output()) | 
|---|
| 385 | .spawn(); | 
|---|
| 386 | match child { | 
|---|
| 387 | Ok(child) => Ok(child), | 
|---|
| 388 | Err(ref e) if e.kind() == io::ErrorKind::NotFound => { | 
|---|
| 389 | let extra = if cfg!(windows) { | 
|---|
| 390 | " (see https://docs.rs/cc/latest/cc/#compile-time-requirements for help)" | 
|---|
| 391 | } else { | 
|---|
| 392 | "" | 
|---|
| 393 | }; | 
|---|
| 394 | Err(Error::new( | 
|---|
| 395 | ErrorKind::ToolNotFound, | 
|---|
| 396 | format!( "failed to find tool {:?} : {e}{extra} ", cmd.0.get_program()), | 
|---|
| 397 | )) | 
|---|
| 398 | } | 
|---|
| 399 | Err(e) => Err(Error::new( | 
|---|
| 400 | ErrorKind::ToolExecError, | 
|---|
| 401 | format!( "command `{:?} ` failed to start: {e} ", cmd.0), | 
|---|
| 402 | )), | 
|---|
| 403 | } | 
|---|
| 404 | } | 
|---|
| 405 |  | 
|---|
| 406 | pub(crate) struct CmdAddOutputFileArgs { | 
|---|
| 407 | pub(crate) cuda: bool, | 
|---|
| 408 | pub(crate) is_assembler_msvc: bool, | 
|---|
| 409 | pub(crate) msvc: bool, | 
|---|
| 410 | pub(crate) clang: bool, | 
|---|
| 411 | pub(crate) gnu: bool, | 
|---|
| 412 | pub(crate) is_asm: bool, | 
|---|
| 413 | pub(crate) is_arm: bool, | 
|---|
| 414 | } | 
|---|
| 415 |  | 
|---|
| 416 | pub(crate) fn command_add_output_file(cmd: &mut Command, dst: &Path, args: CmdAddOutputFileArgs) { | 
|---|
| 417 | if args.is_assembler_msvc | 
|---|
| 418 | || !(!args.msvc || args.clang || args.gnu || args.cuda || (args.is_asm && args.is_arm)) | 
|---|
| 419 | { | 
|---|
| 420 | let mut s: OsString = OsString::from( "-Fo"); | 
|---|
| 421 | s.push(dst); | 
|---|
| 422 | cmd.arg(s); | 
|---|
| 423 | } else { | 
|---|
| 424 | cmd.arg( "-o").arg(dst); | 
|---|
| 425 | } | 
|---|
| 426 | } | 
|---|
| 427 |  | 
|---|
| 428 | #[ cfg(feature = "parallel")] | 
|---|
| 429 | pub(crate) fn try_wait_on_child( | 
|---|
| 430 | cmd: &Command, | 
|---|
| 431 | child: &mut Child, | 
|---|
| 432 | stdout: &mut dyn io::Write, | 
|---|
| 433 | stderr_forwarder: &mut StderrForwarder, | 
|---|
| 434 | ) -> Result<Option<()>, Error> { | 
|---|
| 435 | stderr_forwarder.forward_available(); | 
|---|
| 436 |  | 
|---|
| 437 | match child.try_wait() { | 
|---|
| 438 | Ok(Some(status)) => { | 
|---|
| 439 | stderr_forwarder.forward_all(); | 
|---|
| 440 |  | 
|---|
| 441 | let _ = writeln!(stdout, "{}", status); | 
|---|
| 442 |  | 
|---|
| 443 | if status.success() { | 
|---|
| 444 | Ok(Some(())) | 
|---|
| 445 | } else { | 
|---|
| 446 | Err(Error::new( | 
|---|
| 447 | ErrorKind::ToolExecError, | 
|---|
| 448 | format!( "command did not execute successfully (status code {status}): {cmd:?}"), | 
|---|
| 449 | )) | 
|---|
| 450 | } | 
|---|
| 451 | } | 
|---|
| 452 | Ok(None) => Ok(None), | 
|---|
| 453 | Err(e) => { | 
|---|
| 454 | stderr_forwarder.forward_all(); | 
|---|
| 455 | Err(Error::new( | 
|---|
| 456 | ErrorKind::ToolExecError, | 
|---|
| 457 | format!( "failed to wait on spawned child process `{cmd:?}`: {e}"), | 
|---|
| 458 | )) | 
|---|
| 459 | } | 
|---|
| 460 | } | 
|---|
| 461 | } | 
|---|
| 462 |  | 
|---|