use super::{
StderrMode,
child_exit::{finalize_streaming, terminate_child},
failure_attribution::{
CommandListFailure, NinjaFailureOutputTail, forward_stderr_with_attribution,
},
streaming::{ForwardStats, forward_child_output, forward_child_output_with_ninja_status},
};
use std::{
io::{self, BufReader},
process::{Child, ExitStatus},
thread,
};
pub(super) type StatusObserver<'a> = &'a mut dyn FnMut(u32, u32, &str);
fn forward_stdout<W>(
stdout: impl io::Read,
output: &mut W,
status_observer: Option<StatusObserver<'_>>,
captures_ninja_failure_output: bool,
) -> (ForwardStats, Option<CommandListFailure>)
where
W: io::Write,
{
if captures_ninja_failure_output {
let mut tail_writer = NinjaFailureOutputTail::new(output);
let stats = match status_observer {
Some(observer) => forward_child_output_with_ninja_status(
BufReader::new(stdout),
&mut tail_writer,
observer,
"stdout",
),
None => forward_child_output(BufReader::new(stdout), &mut tail_writer, "stdout"),
};
return (stats, tail_writer.into_failure());
}
let stats = match status_observer {
Some(observer) => forward_child_output_with_ninja_status(
BufReader::new(stdout),
output,
observer,
"stdout",
),
None => forward_child_output(BufReader::new(stdout), output, "stdout"),
};
(stats, None)
}
pub(super) fn spawn_and_stream_output(
mut child: Child,
status_observer: Option<StatusObserver<'_>>,
stderr_mode: StderrMode,
captures_ninja_failure_output: bool,
) -> io::Result<(ExitStatus, Option<CommandListFailure>)> {
let Some(stdout) = child.stdout.take() else {
terminate_child(&mut child, "stdout pipe unavailable");
return Err(io::Error::other("child process missing stdout pipe"));
};
let Some(stderr) = child.stderr.take() else {
terminate_child(&mut child, "stderr pipe unavailable");
return Err(io::Error::other("child process missing stderr pipe"));
};
let err_handle = thread::spawn(move || match stderr_mode {
StderrMode::Suppress => forward_stderr_with_attribution(BufReader::new(stderr), io::sink()),
StderrMode::Forward => {
forward_stderr_with_attribution(BufReader::new(stderr), io::stderr())
}
});
let (stdout_stats, stdout_failure) = match stderr_mode {
StderrMode::Suppress => {
let mut output = io::sink();
forward_stdout(
stdout,
&mut output,
status_observer,
captures_ninja_failure_output,
)
}
StderrMode::Forward => {
let mut output = io::stdout().lock();
forward_stdout(
stdout,
&mut output,
status_observer,
captures_ninja_failure_output,
)
}
};
let wait_result = child.wait();
let (status, stderr_failure) = finalize_streaming(wait_result, stdout_stats, err_handle)?;
let failure = if status.success() {
stderr_failure
} else {
stderr_failure.or(stdout_failure)
};
Ok((status, failure))
}