deb: drive build_binary_package through the BuildView port
The binary build joins the source build on the reporting ports: build_binary_package takes a DebBuildOptions struct (replacing eleven positional arguments), reports target, phases, progress and the outcome through the environment-agnostic BuildView, and the Phase enum with its default classifiers moves from the terminal widget into the deb module (announced through the enter_phase helper). DebUi loses its inherent event methods and only implements the port; tee logging and the SIGINT behavior are unchanged. No behavior change for the CLI; headless consumers pass report::Quiet.
This commit is contained in:
+48
-154
@@ -21,10 +21,8 @@ use directories::ProjectDirs;
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||
|
||||
use crate::context::{LineSink, Stream};
|
||||
use crate::logfmt::{
|
||||
Action, AptInstallClassifier, AptUpdateClassifier, Classifier, GenericClassifier,
|
||||
MakeClassifier, MmdebstrapClassifier, QuiltClassifier,
|
||||
};
|
||||
use crate::logfmt::{Action, Classifier, GenericClassifier};
|
||||
use crate::report::BuildTarget;
|
||||
|
||||
/// Number of lines displayed in the rolling pane
|
||||
const PANE_LINES: usize = 10;
|
||||
@@ -32,69 +30,6 @@ const PANE_LINES: usize = 10;
|
||||
/// Minimum interval between pane redraws
|
||||
const REDRAW_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
/// Build phases of `pkh deb`, shown in the status bar
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Phase {
|
||||
/// Downloading the chroot tarball (mmdebstrap)
|
||||
PreparingChroot,
|
||||
/// Extracting the chroot tarball
|
||||
ExtractingChroot,
|
||||
/// Device nodes, /proc bind mount, etc.
|
||||
FinalizingChroot,
|
||||
/// apt-get update
|
||||
UpdatingPackageLists,
|
||||
/// Installing build-essential & co
|
||||
InstallingEssentials,
|
||||
/// quilt push -a
|
||||
ApplyingPatches,
|
||||
/// --inject packages
|
||||
InjectingPackages,
|
||||
/// apt-get build-dep
|
||||
InstallingBuildDeps,
|
||||
/// debian/rules build
|
||||
Building,
|
||||
/// fakeroot debian/rules binary
|
||||
ProducingBinaries,
|
||||
/// Retrieving produced .deb files
|
||||
RetrievingArtifacts,
|
||||
}
|
||||
|
||||
impl Phase {
|
||||
/// Human-readable label displayed in the status bar
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
Phase::PreparingChroot => "Preparing chroot",
|
||||
Phase::ExtractingChroot => "Extracting chroot",
|
||||
Phase::FinalizingChroot => "Finalizing chroot",
|
||||
Phase::UpdatingPackageLists => "Updating package lists",
|
||||
Phase::InstallingEssentials => "Installing essential packages",
|
||||
Phase::ApplyingPatches => "Applying patches",
|
||||
Phase::InjectingPackages => "Injecting packages",
|
||||
Phase::InstallingBuildDeps => "Installing build dependencies",
|
||||
Phase::Building => "Building package",
|
||||
Phase::ProducingBinaries => "Producing binary packages",
|
||||
Phase::RetrievingArtifacts => "Retrieving artifacts",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Default classifier used for a given phase
|
||||
fn default_classifier(phase: Phase) -> Box<dyn Classifier> {
|
||||
match phase {
|
||||
Phase::PreparingChroot => Box::new(MmdebstrapClassifier::new()),
|
||||
Phase::ExtractingChroot | Phase::FinalizingChroot => Box::new(GenericClassifier::new()),
|
||||
Phase::UpdatingPackageLists => Box::new(AptUpdateClassifier::new()),
|
||||
Phase::InstallingEssentials => Box::new(AptInstallClassifier::new("Installing essentials")),
|
||||
Phase::ApplyingPatches => Box::new(QuiltClassifier::new(0)),
|
||||
Phase::InjectingPackages => Box::new(AptInstallClassifier::new("Injecting packages")),
|
||||
Phase::InstallingBuildDeps => {
|
||||
Box::new(AptInstallClassifier::new("Installing build dependencies"))
|
||||
}
|
||||
Phase::Building | Phase::ProducingBinaries => Box::new(MakeClassifier::new()),
|
||||
Phase::RetrievingArtifacts => Box::new(GenericClassifier::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Visual kind of a pane line, driving its color
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum Kind {
|
||||
@@ -129,11 +64,9 @@ struct Shared {
|
||||
|
||||
/// Live build view for `pkh deb` / `pkh build`
|
||||
///
|
||||
/// Create one per build (disabled automatically when stdout is not a TTY or
|
||||
/// when the user requests verbose output), then either pass it down through
|
||||
/// the [`crate::report::BuildView`] port (source builds) or as
|
||||
/// `Option<Arc<DebUi>>` (the `pkh deb` flow, until it migrates), and feed
|
||||
/// subprocess output through [`DebUi::sink`].
|
||||
/// Create one per build (it disables itself automatically when stdout is not
|
||||
/// a TTY) and pass it down through the [`crate::report::BuildView`] port.
|
||||
/// Subprocess output reaches it through [`crate::report::BuildView::sink`].
|
||||
pub struct DebUi {
|
||||
shared: Arc<Shared>,
|
||||
}
|
||||
@@ -252,20 +185,7 @@ impl DebUi {
|
||||
}
|
||||
}
|
||||
|
||||
/// Switch to a phase, installing its default classifier
|
||||
pub fn phase(&self, phase: Phase) {
|
||||
self.phase_custom(phase.label(), default_classifier(phase));
|
||||
}
|
||||
|
||||
/// Switch to a phase with a custom classifier (e.g. quilt with a known
|
||||
/// patch count)
|
||||
pub fn phase_with(&self, phase: Phase, classifier: Box<dyn Classifier>) {
|
||||
self.phase_custom(phase.label(), classifier);
|
||||
}
|
||||
|
||||
/// Switch to an arbitrary status label with a custom classifier; the
|
||||
/// shared implementation behind [`DebUi::phase`], [`DebUi::phase_with`]
|
||||
/// and the [`BuildView`] port
|
||||
/// Switch to an arbitrary status label with a custom classifier
|
||||
fn phase_custom(&self, label: &str, classifier: Box<dyn Classifier>) {
|
||||
{
|
||||
let mut st = self.shared.state.lock().unwrap();
|
||||
@@ -281,47 +201,18 @@ impl DebUi {
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the status bar message directly (for in-process work such as
|
||||
/// tarball extraction that has no subprocess output)
|
||||
pub fn progress_message(&self, msg: &str) {
|
||||
if self.active() {
|
||||
self.shared.top.set_message(msg.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
/// Drive the determinate progress bar directly (e.g. artifact retrieval)
|
||||
pub fn count_progress(&self, label: &str, pos: usize, total: usize) {
|
||||
if !self.active() || total == 0 {
|
||||
return;
|
||||
}
|
||||
apply_progress(
|
||||
&self.shared.top,
|
||||
&mut self.shared.state.lock().unwrap(),
|
||||
pos as u64,
|
||||
total as u64,
|
||||
);
|
||||
self.shared.top.set_message(label.to_string());
|
||||
}
|
||||
|
||||
/// Whether the widget is enabled and still drawn
|
||||
fn active(&self) -> bool {
|
||||
self.shared.enabled && !self.shared.suspended.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Obtain a sink feeding this view; pass it to `ContextCommand::capture`
|
||||
pub fn sink(self: &Arc<Self>) -> Arc<dyn LineSink> {
|
||||
Arc::new(Sink {
|
||||
shared: self.shared.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove the widget from the terminal (e.g. before printing passthrough
|
||||
/// diagnostics or letting child cleanup commands write to the terminal);
|
||||
/// idempotent
|
||||
/// Release the widget from the terminal (e.g. before printing
|
||||
/// passthrough diagnostics or letting child cleanup commands write to
|
||||
/// the terminal); idempotent
|
||||
///
|
||||
/// Steady ticks are disabled first: otherwise a tick can redraw a frame
|
||||
/// right after the clear, leaving stale copies of the widget on screen.
|
||||
pub fn suspend(&self) {
|
||||
fn suspend(&self) {
|
||||
if !self.shared.enabled {
|
||||
return;
|
||||
}
|
||||
@@ -334,20 +225,8 @@ impl DebUi {
|
||||
self.shared.pane.finish_and_clear();
|
||||
}
|
||||
|
||||
/// Clear the widget and print a success summary with the artifacts,
|
||||
/// Success outcome body: clear the widget and print the artifacts,
|
||||
/// rendered relative to the working directory when possible
|
||||
pub fn finish_success(&self, artifacts: &[PathBuf], elapsed: Duration) {
|
||||
self.success_summary(artifacts, elapsed);
|
||||
}
|
||||
|
||||
/// Clear the widget and print a failure summary (recent captured errors
|
||||
/// and the path to the full log)
|
||||
pub fn finish_failure(&self) {
|
||||
self.failure_summary();
|
||||
}
|
||||
|
||||
/// Shared body of the success outcome ([`DebUi::finish_success`] and the
|
||||
/// [`BuildView`] port)
|
||||
fn success_summary(&self, artifacts: &[PathBuf], elapsed: Duration) {
|
||||
self.suspend();
|
||||
if self.shared.enabled && !artifacts.is_empty() {
|
||||
@@ -358,8 +237,8 @@ impl DebUi {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared body of the failure outcome ([`DebUi::finish_failure`] and the
|
||||
/// [`BuildView`] port)
|
||||
/// Failure outcome body: clear the widget and print a summary (recent
|
||||
/// captured errors and the path to the full log)
|
||||
fn failure_summary(&self) {
|
||||
self.suspend();
|
||||
|
||||
@@ -383,31 +262,31 @@ impl DebUi {
|
||||
eprintln!("Full log: {}", log_path.display());
|
||||
}
|
||||
}
|
||||
|
||||
/// Time elapsed since the view was created
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
self.shared.started.elapsed()
|
||||
}
|
||||
|
||||
/// Path of the full build log file
|
||||
pub fn log_path(&self) -> PathBuf {
|
||||
self.shared.log_path.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// [`crate::report::BuildView`] port: forwards build events to the live
|
||||
/// widget, so core flows can drive the view without knowing it is a
|
||||
/// terminal widget. Where the inherent methods keep their richer signatures
|
||||
/// (e.g. [`DebUi::finish_success`] takes the elapsed duration explicitly for
|
||||
/// the `pkh deb` flow), the port bridges them.
|
||||
/// widget, so core flows drive the view without knowing it is a terminal
|
||||
/// widget.
|
||||
impl crate::report::BuildView for DebUi {
|
||||
fn target(&self, package: &str, version: &str, target: &str) {
|
||||
fn target(&self, target: BuildTarget<'_>) {
|
||||
let kind = if target.source_only {
|
||||
"source package "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
if self.shared.enabled {
|
||||
self.shared.top.set_prefix(format!(
|
||||
"Building source package {package} ({version}) for {target}"
|
||||
"Building {kind}{} ({}) for {}",
|
||||
target.package, target.version, target.target
|
||||
));
|
||||
}
|
||||
self.open_log("build", package, version, &format!("for {target}"));
|
||||
let log_kind = if target.source_only { "build" } else { "deb" };
|
||||
self.open_log(
|
||||
log_kind,
|
||||
target.package,
|
||||
target.version,
|
||||
&format!("for {}", target.target),
|
||||
);
|
||||
}
|
||||
|
||||
fn phase(&self, name: &str, classifier: Box<dyn Classifier>) {
|
||||
@@ -415,11 +294,22 @@ impl crate::report::BuildView for DebUi {
|
||||
}
|
||||
|
||||
fn message(&self, text: &str) {
|
||||
self.progress_message(text);
|
||||
if self.active() {
|
||||
self.shared.top.set_message(text.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
fn progress(&self, label: &str, pos: usize, total: usize) {
|
||||
self.count_progress(label, pos, total);
|
||||
if !self.active() || total == 0 {
|
||||
return;
|
||||
}
|
||||
apply_progress(
|
||||
&self.shared.top,
|
||||
&mut self.shared.state.lock().unwrap(),
|
||||
pos as u64,
|
||||
total as u64,
|
||||
);
|
||||
self.shared.top.set_message(label.to_string());
|
||||
}
|
||||
|
||||
fn sink(&self) -> Option<Arc<dyn LineSink>> {
|
||||
@@ -429,13 +319,17 @@ impl crate::report::BuildView for DebUi {
|
||||
}
|
||||
|
||||
fn finish_success(&self, artifacts: &[PathBuf]) {
|
||||
self.success_summary(artifacts, self.elapsed());
|
||||
self.success_summary(artifacts, self.shared.started.elapsed());
|
||||
}
|
||||
|
||||
fn finish_failure(&self) {
|
||||
self.failure_summary();
|
||||
}
|
||||
|
||||
fn suspend(&self) {
|
||||
DebUi::suspend(self);
|
||||
}
|
||||
|
||||
fn is_enabled(&self) -> bool {
|
||||
self.shared.enabled
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user