report: add BuildView/Prompter ports and drive pkh build through them
Core flows no longer reach into the terminal UI: build_source_package takes a BuildSourceOptions struct (source tree, domain options, view, prompter) and reports phases, messages and outcomes through the environment-agnostic ports in the new report module. The classifiers move from ui/logfmt to the core logfmt module, DebUi becomes a BuildView adapter, the re-vendor retry asks the prompter instead of checking for a TTY, and artifact/success printing moves to the CLI. Headless consumers pass report::Quiet; an embedding (e.g. a builder server forwarding events to a web frontend) implements BuildView and maps the plain-data events onto its own wire format.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
//! Environment-agnostic reporting ports.
|
||||
//!
|
||||
//! The core flows report progress and ask questions exclusively through the
|
||||
//! traits in this module, so the same pipeline can drive a terminal live
|
||||
//! view, a headless library consumer, or a remote frontend (e.g. a builder
|
||||
//! server forwarding build events to a web UI over server-sent events): every
|
||||
//! event carries plain data — strings, numbers, paths — with no terminal,
|
||||
//! styling or locale assumptions. Adapters decide how events reach the user:
|
||||
//! the terminal live view ([`crate::ui::deb::DebUi`]) renders them in place,
|
||||
//! while another embedding maps each method onto its own wire format.
|
||||
//!
|
||||
//! Every [`BuildView`] method defaults to doing nothing, so implementations
|
||||
//! only override the events they care about; [`Quiet`] provides the inert
|
||||
//! implementations used by headless runs and tests. [`Prompter`] is
|
||||
//! deliberately blocking: an implementation may round-trip each question to
|
||||
//! a remote user, as long as it eventually answers (or takes the default).
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::context::LineSink;
|
||||
use crate::logfmt::Classifier;
|
||||
|
||||
/// Observer of a running build: target identification, phases, status
|
||||
/// messages, progress and the final outcome.
|
||||
///
|
||||
/// Implement this to observe [`crate::build`] flows from any frontend. All
|
||||
/// events arrive in order from the build thread; long-lived views are
|
||||
/// expected to be `Send + Sync` because builds may run inside async tasks.
|
||||
pub trait BuildView: Send + Sync {
|
||||
/// The build target was identified: `package` at `version`, built for
|
||||
/// `target` (a distribution series, optionally with an architecture).
|
||||
fn target(&self, _package: &str, _version: &str, _target: &str) {}
|
||||
|
||||
/// A named phase started (e.g. "Applying patches"). `classifier`
|
||||
/// rewrites the phase's raw subprocess lines (see [`crate::logfmt`])
|
||||
/// into display actions and countable progress; views that do not
|
||||
/// rewrite lines locally can ignore it and forward raw lines from
|
||||
/// [`BuildView::sink`] instead.
|
||||
fn phase(&self, _name: &str, _classifier: Box<dyn Classifier>) {}
|
||||
|
||||
/// A status message about in-process work that produces no subprocess
|
||||
/// output (e.g. "Generating .changes").
|
||||
fn message(&self, _text: &str) {}
|
||||
|
||||
/// Determinate progress within the current phase (e.g. artifact
|
||||
/// retrieval); `pos` runs from 0 to `total`.
|
||||
fn progress(&self, _label: &str, _pos: usize, _total: usize) {}
|
||||
|
||||
/// Sink receiving every raw subprocess line while a build command runs,
|
||||
/// when this view consumes the lines itself (live rewriting, tee to a
|
||||
/// log file, forwarding over the network). `None` lets the caller fall
|
||||
/// back to its default handling (e.g. test capture).
|
||||
fn sink(&self) -> Option<Arc<dyn LineSink>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// The build succeeded; `artifacts` lists the produced files in
|
||||
/// distribution order (dsc → tarballs → buildinfo → changes).
|
||||
fn finish_success(&self, _artifacts: &[PathBuf]) {}
|
||||
|
||||
/// The build failed; the view should release the display and may report
|
||||
/// diagnostics it collected through [`BuildView::sink`].
|
||||
fn finish_failure(&self) {}
|
||||
|
||||
/// Whether this view presents build results to the user by itself;
|
||||
/// callers use this to fall back to plain-line rendering when it does
|
||||
/// not (headless views, verbose mode).
|
||||
fn is_enabled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Answerer of the questions a core flow may ask mid-run (confirmations
|
||||
/// now, selections and free-text input as more flows migrate).
|
||||
///
|
||||
/// Questions are blocking on purpose: a terminal implementation waits for
|
||||
/// key presses, and a builder-server implementation may forward the question
|
||||
/// to a web client and await the answer on a channel. Implementations that
|
||||
/// cannot ask anyone answer with the question's default.
|
||||
pub trait Prompter: Send + Sync {
|
||||
/// Ask a yes/no question. `default` is the answer to take when no user
|
||||
/// can be reached or the question is cancelled.
|
||||
fn confirm(&self, _question: &str, default: bool) -> bool {
|
||||
default
|
||||
}
|
||||
}
|
||||
|
||||
/// Inert view and prompter: drops every event and answers every question
|
||||
/// with its default. The stand-in for headless library runs, verbose mode
|
||||
/// and tests.
|
||||
pub struct Quiet;
|
||||
|
||||
impl BuildView for Quiet {}
|
||||
impl Prompter for Quiet {}
|
||||
Reference in New Issue
Block a user