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:
2026-09-29 15:19:17 +02:00
parent 562626e878
commit 2de191cf6d
2 changed files with 168 additions and 5 deletions
+70 -5
View File
@@ -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"
);
}
}