deb: print raw build output again with --verbose
The record-log wrapper provides a sink for every command, which removed the runner's subprocess-inherit fallback -- and with it the raw output of --verbose runs (and of piped stdout, where the live view disables itself): Quiet's None sink got wrapped inside, every line went to build.log, and the terminal showed nothing but pkh's own logs, breaking what --help still promises. When the caller's view has no sink of its own, a raw terminal sink (stdout/stderr writers, one per stream) now takes its place inside the record-log wrapper: the terminal shows the raw lines again while build.log keeps the full tee. A view that consumes lines itself (the live view on a tty) is wrapped exactly as before.
This commit is contained in:
+70
-5
@@ -364,11 +364,23 @@ async fn build_binary_package_impl(
|
||||
// Tee every captured build line into the record's log: the wrapper
|
||||
// view hands the combined sink to everything downstream (the chroot
|
||||
// bootstrap, the build phases), so the log is a full tee whatever
|
||||
// renders the output. A record-less build (non-local base context)
|
||||
// keeps the plain view.
|
||||
let record_log: Option<Result<Arc<session::RecordLogSink>, std::io::Error>> = session
|
||||
.as_ref()
|
||||
.map(|s| session::RecordLogSink::new(s, opts.view.sink()).map(Arc::new));
|
||||
// renders the output. A view that consumes lines itself (the live
|
||||
// view on a terminal) is wrapped as-is; a view with NO sink (Quiet,
|
||||
// i.e. --verbose or a piped stdout) would have let the subprocess
|
||||
// inherit the terminal — the wrapper removes that fallback, so the
|
||||
// raw lines are written by a terminal sink instead, and --verbose
|
||||
// keeps showing raw output. A record-less build (non-local base
|
||||
// context) keeps the plain view.
|
||||
let record_log: Option<Result<Arc<session::RecordLogSink>, std::io::Error>> =
|
||||
session.as_ref().map(|s| {
|
||||
session::RecordLogSink::new(
|
||||
s,
|
||||
opts.view
|
||||
.sink()
|
||||
.or_else(|| Some(Arc::new(session::RawWriterSink::terminal()))),
|
||||
)
|
||||
.map(Arc::new)
|
||||
});
|
||||
let view: &dyn BuildView = match record_log {
|
||||
Some(Ok(log)) => &RecordLogView {
|
||||
inner: opts.view,
|
||||
@@ -1726,4 +1738,57 @@ mod tests {
|
||||
"wrong error: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
/// The record-log view keeps the terminal alive for sink-less
|
||||
/// views: when the caller's view has no sink of its own (Quiet —
|
||||
/// `--verbose` or a piped stdout), the raw terminal sink takes its
|
||||
/// place, and every captured line lands BOTH in the record's
|
||||
/// build.log and on the writers (the terminal, in production).
|
||||
#[test]
|
||||
fn record_log_view_shows_raw_output_when_the_view_has_no_sink() {
|
||||
use crate::context::{LineSink, Stream};
|
||||
use crate::deb::session::record_fixtures::SharedWriter;
|
||||
|
||||
let (root, _tarball) = sessions_fixture();
|
||||
let identity = test_identity();
|
||||
let host_tree = root.path().join("tree");
|
||||
let record = session::new_record_in(root.path(), &host_tree, &identity, "1.0").unwrap();
|
||||
|
||||
// The production wiring, with the terminal sink replaced by
|
||||
// injectable capture buffers: the Quiet view has no sink, so
|
||||
// the raw writer sink fills the fallback.
|
||||
let stdout = SharedWriter::new();
|
||||
let stderr = SharedWriter::new();
|
||||
let quiet = crate::report::Quiet;
|
||||
let fallback: Option<Arc<dyn LineSink>> = quiet.sink().or_else(|| {
|
||||
Some(Arc::new(session::RawWriterSink::new(
|
||||
Box::new(stdout.clone()),
|
||||
Box::new(stderr.clone()),
|
||||
)))
|
||||
});
|
||||
let log = Arc::new(session::RecordLogSink::new(&record, fallback).unwrap());
|
||||
let view = RecordLogView { inner: &quiet, log };
|
||||
|
||||
let sink = view
|
||||
.sink()
|
||||
.expect("the record-log view always provides one");
|
||||
sink.line(Stream::Stdout, "raw stdout line");
|
||||
sink.line(Stream::Stderr, "raw stderr line");
|
||||
|
||||
assert_eq!(
|
||||
stdout.contents(),
|
||||
"raw stdout line\n",
|
||||
"the fallback writer received the raw line"
|
||||
);
|
||||
assert_eq!(
|
||||
stderr.contents(),
|
||||
"raw stderr line\n",
|
||||
"stderr lines reach the stderr writer"
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(record.log_file()).unwrap(),
|
||||
"raw stdout line\nraw stderr line\n",
|
||||
"build.log keeps the full tee"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1524,6 +1524,49 @@ impl LineSink for RecordLogSink {
|
||||
}
|
||||
}
|
||||
|
||||
/// A sink writing captured lines to two raw writers, one per stream:
|
||||
/// stdout lines to the first, stderr lines to the second.
|
||||
///
|
||||
/// This is the record-log view's stand-in for the subprocess-inherit
|
||||
/// fallback: with `--verbose` (or a piped stdout, where the live view
|
||||
/// disables itself) the caller's view is [`crate::report::Quiet`], whose
|
||||
/// sink is `None` — the runner would let the subprocess inherit the
|
||||
/// terminal — but the record-log wrapper always provides a sink, so
|
||||
/// the fallback is never reached anymore. Wrapping this sink in its
|
||||
/// place keeps the raw lines on the terminal while the record's
|
||||
/// build.log keeps the full tee.
|
||||
pub(crate) struct RawWriterSink {
|
||||
stdout: Mutex<Box<dyn Write + Send>>,
|
||||
stderr: Mutex<Box<dyn Write + Send>>,
|
||||
}
|
||||
|
||||
impl RawWriterSink {
|
||||
/// A sink over explicit writer pairs (tests capture into memory).
|
||||
pub(crate) fn new(stdout: Box<dyn Write + Send>, stderr: Box<dyn Write + Send>) -> Self {
|
||||
Self {
|
||||
stdout: Mutex::new(stdout),
|
||||
stderr: Mutex::new(stderr),
|
||||
}
|
||||
}
|
||||
|
||||
/// A sink over the real terminal: the process's stdout and stderr.
|
||||
pub(crate) fn terminal() -> Self {
|
||||
Self::new(Box::new(io::stdout()), Box::new(io::stderr()))
|
||||
}
|
||||
}
|
||||
|
||||
impl LineSink for RawWriterSink {
|
||||
fn line(&self, stream: Stream, line: &str) {
|
||||
let writer = match stream {
|
||||
Stream::Stdout => &self.stdout,
|
||||
Stream::Stderr => &self.stderr,
|
||||
};
|
||||
if let Ok(mut out) = writer.lock() {
|
||||
let _ = writeln!(out, "{line}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Host-tree snapshot and overlay upperdir sync
|
||||
//
|
||||
@@ -1854,6 +1897,34 @@ pub(crate) fn sync_upper_for_resume(session: &Session, host_parent: &Path) -> io
|
||||
pub(crate) mod record_fixtures {
|
||||
use super::*;
|
||||
|
||||
/// A writer copying everything it receives into a shared buffer:
|
||||
/// lets tests observe what a `Box<dyn Write + Send>` sink wrote.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SharedWriter(pub Arc<parking_lot::Mutex<Vec<u8>>>);
|
||||
|
||||
impl SharedWriter {
|
||||
/// A fresh empty capture buffer.
|
||||
pub(crate) fn new() -> Self {
|
||||
Self(Arc::new(parking_lot::Mutex::new(Vec::new())))
|
||||
}
|
||||
|
||||
/// What has been written so far.
|
||||
pub(crate) fn contents(&self) -> String {
|
||||
String::from_utf8_lossy(&self.0.lock()).into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Write for SharedWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.0.lock().extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_identity() -> SessionIdentity {
|
||||
SessionIdentity {
|
||||
package: "pkh".to_string(),
|
||||
@@ -2739,6 +2810,33 @@ mod tests {
|
||||
assert_eq!(out.len(), 0);
|
||||
}
|
||||
|
||||
/// The raw writer sink routes each stream to its own writer,
|
||||
/// newline-terminated: this is what keeps --verbose (and piped
|
||||
/// runs) showing raw output under the record-log wrapper.
|
||||
#[test]
|
||||
fn raw_writer_sink_routes_streams_to_their_writers() {
|
||||
use record_fixtures::SharedWriter;
|
||||
|
||||
let stdout = SharedWriter::new();
|
||||
let stderr = SharedWriter::new();
|
||||
let sink = RawWriterSink::new(Box::new(stdout.clone()), Box::new(stderr.clone()));
|
||||
|
||||
sink.line(Stream::Stdout, "from stdout");
|
||||
sink.line(Stream::Stderr, "from stderr");
|
||||
sink.line(Stream::Stdout, "second line");
|
||||
|
||||
assert_eq!(
|
||||
stdout.contents(),
|
||||
"from stdout\nsecond line\n",
|
||||
"stdout lines land in order, newline-terminated"
|
||||
);
|
||||
assert_eq!(
|
||||
stderr.contents(),
|
||||
"from stderr\n",
|
||||
"stderr lines land on their own writer only"
|
||||
);
|
||||
}
|
||||
|
||||
/// Ages render in the coarse units the list promises.
|
||||
#[test]
|
||||
fn age_rendering() {
|
||||
|
||||
Reference in New Issue
Block a user