Skip to content

Commit 9bcacb0

Browse files
Optionally log Cargo's stderr/stdout to stderr/stdout
Mostly intended for debugging.
1 parent e0b3764 commit 9bcacb0

File tree

2 files changed

+114
-11
lines changed

2 files changed

+114
-11
lines changed

collector/src/lib.rs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ use std::fmt;
66
use std::process::{self, Command};
77

88
pub mod api;
9+
mod read2;
910
pub mod self_profile;
1011

12+
use process::Stdio;
1113
pub use self_profile::{QueryData, SelfProfile};
1214

1315
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Deserialize)]
@@ -135,20 +137,47 @@ pub fn run_command(cmd: &mut Command) -> anyhow::Result<()> {
135137

136138
pub fn command_output(cmd: &mut Command) -> anyhow::Result<process::Output> {
137139
log::trace!("running: {:?}", cmd);
138-
let output = cmd.output()?;
139-
if !output.status.success() {
140+
let mut child = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
141+
142+
let mut stdout = Vec::new();
143+
let mut stderr = Vec::new();
144+
let mut stdout_writer = std::io::LineWriter::new(std::io::stdout());
145+
let mut stderr_writer = std::io::LineWriter::new(std::io::stderr());
146+
read2::read2(
147+
child.stdout.take().unwrap(),
148+
child.stderr.take().unwrap(),
149+
&mut |is_stdout, buffer, _is_done| {
150+
// Send output if trace logging is enabled
151+
if log::log_enabled!(target: "collector_raw_cargo", log::level::Trace) {
152+
use std::io::Write;
153+
if is_stdout {
154+
stdout_writer.write_all(&buffer[stdout.len()..]).unwrap();
155+
} else {
156+
stderr_writer.write_all(&buffer[stderr.len()..]).unwrap();
157+
}
158+
}
159+
if is_stdout {
160+
stdout = buffer.clone();
161+
} else {
162+
stderr = buffer.clone();
163+
}
164+
},
165+
)?;
166+
167+
let status = child.wait()?;
168+
if !status.success() {
140169
return Err(anyhow::anyhow!(
141170
"expected success, got {}\n\nstderr={}\n\n stdout={}",
142-
output.status,
143-
String::from_utf8_lossy(&output.stderr),
144-
String::from_utf8_lossy(&output.stdout)
171+
status,
172+
String::from_utf8_lossy(&stderr),
173+
String::from_utf8_lossy(&stdout)
145174
));
146-
} else {
147-
// log::trace!(
148-
// "stderr={}\n\nstdout={}",
149-
// String::from_utf8_lossy(&output.stderr),
150-
// String::from_utf8_lossy(&output.stdout),
151-
// );
152175
}
176+
177+
let output = process::Output {
178+
status,
179+
stdout: stdout,
180+
stderr: stderr,
181+
};
153182
Ok(output)
154183
}

collector/src/read2.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// This is copied from
2+
// https://github.com/rust-lang/rust/blob/master/src/tools/compiletest/src/read2.rs
3+
// and falls under the MIT or Apache 2.0 licenses.
4+
5+
use std::io;
6+
use std::io::prelude::*;
7+
use std::mem;
8+
use std::os::unix::prelude::*;
9+
use std::process::{ChildStderr, ChildStdout};
10+
11+
pub fn read2(
12+
mut out_pipe: ChildStdout,
13+
mut err_pipe: ChildStderr,
14+
data: &mut dyn FnMut(bool, &mut Vec<u8>, bool),
15+
) -> io::Result<()> {
16+
unsafe {
17+
libc::fcntl(out_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
18+
libc::fcntl(err_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
19+
}
20+
21+
let mut out_done = false;
22+
let mut err_done = false;
23+
let mut out = Vec::new();
24+
let mut err = Vec::new();
25+
26+
let mut fds: [libc::pollfd; 2] = unsafe { mem::zeroed() };
27+
fds[0].fd = out_pipe.as_raw_fd();
28+
fds[0].events = libc::POLLIN;
29+
fds[1].fd = err_pipe.as_raw_fd();
30+
fds[1].events = libc::POLLIN;
31+
let mut nfds = 2;
32+
let mut errfd = 1;
33+
34+
while nfds > 0 {
35+
// wait for either pipe to become readable using `select`
36+
let r = unsafe { libc::poll(fds.as_mut_ptr(), nfds, -1) };
37+
if r == -1 {
38+
let err = io::Error::last_os_error();
39+
if err.kind() == io::ErrorKind::Interrupted {
40+
continue;
41+
}
42+
return Err(err);
43+
}
44+
45+
// Read as much as we can from each pipe, ignoring EWOULDBLOCK or
46+
// EAGAIN. If we hit EOF, then this will happen because the underlying
47+
// reader will return Ok(0), in which case we'll see `Ok` ourselves. In
48+
// this case we flip the other fd back into blocking mode and read
49+
// whatever's leftover on that file descriptor.
50+
let handle = |res: io::Result<_>| match res {
51+
Ok(_) => Ok(true),
52+
Err(e) => {
53+
if e.kind() == io::ErrorKind::WouldBlock {
54+
Ok(false)
55+
} else {
56+
Err(e)
57+
}
58+
}
59+
};
60+
if !err_done && fds[errfd].revents != 0 && handle(err_pipe.read_to_end(&mut err))? {
61+
err_done = true;
62+
nfds -= 1;
63+
}
64+
data(false, &mut err, err_done);
65+
if !out_done && fds[0].revents != 0 && handle(out_pipe.read_to_end(&mut out))? {
66+
out_done = true;
67+
fds[0].fd = err_pipe.as_raw_fd();
68+
errfd = 0;
69+
nfds -= 1;
70+
}
71+
data(true, &mut out, out_done);
72+
}
73+
Ok(())
74+
}

0 commit comments

Comments
 (0)