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:
+183
-102
@@ -5,7 +5,11 @@ pub(crate) mod ephemeral;
|
||||
mod local;
|
||||
|
||||
use crate::context::{self, Context};
|
||||
use crate::ui::deb::{DebUi, Phase};
|
||||
use crate::logfmt::{
|
||||
AptInstallClassifier, AptUpdateClassifier, Classifier, GenericClassifier, MakeClassifier,
|
||||
MmdebstrapClassifier, QuiltClassifier,
|
||||
};
|
||||
use crate::report::{BuildTarget, BuildView};
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
@@ -17,67 +21,163 @@ pub enum BuildMode {
|
||||
Local,
|
||||
}
|
||||
|
||||
/// Phases of a binary build, announced to the [`BuildView`]
|
||||
#[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 line classifier rewriting a phase's subprocess output
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enter `phase` on the view with its default line classifier
|
||||
pub(crate) fn enter_phase(view: &dyn BuildView, phase: Phase) {
|
||||
view.phase(phase.label(), default_classifier(phase));
|
||||
}
|
||||
|
||||
/// Parameters of one [`build_binary_package`] call.
|
||||
pub struct DebBuildOptions<'a> {
|
||||
/// Target architecture; defaults to the host architecture.
|
||||
pub arch: Option<String>,
|
||||
/// Target distribution series; defaults to the changelog series
|
||||
/// (UNRELEASED resolves to the vendor's development series).
|
||||
pub series: Option<String>,
|
||||
/// Distribution pocket to resolve build-dependencies from.
|
||||
pub pocket: Option<String>,
|
||||
/// Source tree to build; defaults to the process working directory.
|
||||
pub cwd: Option<PathBuf>,
|
||||
/// Cross-compile for the target architecture instead of using
|
||||
/// qemu-binfmt.
|
||||
pub cross: bool,
|
||||
/// Build mode; defaults to [`BuildMode::Local`].
|
||||
pub mode: Option<BuildMode>,
|
||||
/// PPAs to add for build-dependencies (`user/ppa_name`).
|
||||
pub ppa: Vec<String>,
|
||||
/// Packages to inject into the build environment before build-dep
|
||||
/// (.deb paths, archive names or PPA packages).
|
||||
pub inject: Vec<String>,
|
||||
/// Parallel build jobs; defaults to the core count available in the
|
||||
/// build context.
|
||||
pub jobs: Option<usize>,
|
||||
/// Explicit build context; defaults to the current context.
|
||||
pub ctx: Option<Arc<Context>>,
|
||||
/// Where build events (phases, progress, outcome) are reported.
|
||||
pub view: &'a dyn BuildView,
|
||||
}
|
||||
|
||||
impl Default for DebBuildOptions<'_> {
|
||||
fn default() -> Self {
|
||||
static QUIET: crate::report::Quiet = crate::report::Quiet;
|
||||
DebBuildOptions {
|
||||
arch: None,
|
||||
series: None,
|
||||
pocket: None,
|
||||
cwd: None,
|
||||
cross: false,
|
||||
mode: None,
|
||||
ppa: Vec::new(),
|
||||
inject: Vec::new(),
|
||||
jobs: None,
|
||||
ctx: None,
|
||||
view: &QUIET,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build package in 'cwd' to a .deb
|
||||
///
|
||||
/// Returns the list of produced artifacts (.deb files plus the upload
|
||||
/// metadata `.buildinfo`/`.changes`) retrieved locally, identified from
|
||||
/// `debian/files` and the native metadata generation rather than by
|
||||
/// globbing the build root (which would surface stale files). When `ui` is
|
||||
/// set, a live view (status bar + rolling log pane) is displayed and all
|
||||
/// subprocess output is captured through it; on failure the widget is
|
||||
/// cleared and a summary of captured errors is printed.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// globbing the build root (which would surface stale files). Subprocess
|
||||
/// output is captured through the view's sink (live view + tee log for the
|
||||
/// terminal adapter); on failure the view is cleared and prints a summary
|
||||
/// of captured errors.
|
||||
pub async fn build_binary_package(
|
||||
arch: Option<&str>,
|
||||
series: Option<&str>,
|
||||
pocket: Option<&str>,
|
||||
cwd: Option<&Path>,
|
||||
cross: bool,
|
||||
mode: Option<BuildMode>,
|
||||
ppa: Option<&[&str]>,
|
||||
inject_packages: Option<&[&str]>,
|
||||
ctx: Option<Arc<Context>>,
|
||||
ui: Option<Arc<DebUi>>,
|
||||
jobs: Option<usize>,
|
||||
opts: DebBuildOptions<'_>,
|
||||
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
||||
let result = build_binary_package_impl(
|
||||
arch,
|
||||
series,
|
||||
pocket,
|
||||
cwd,
|
||||
cross,
|
||||
mode,
|
||||
ppa,
|
||||
inject_packages,
|
||||
ctx,
|
||||
&ui,
|
||||
jobs,
|
||||
)
|
||||
.await;
|
||||
let view = opts.view;
|
||||
let result = build_binary_package_impl(opts).await;
|
||||
|
||||
if let (Some(u), Err(_)) = (&ui, &result) {
|
||||
u.finish_failure();
|
||||
if result.is_err() {
|
||||
view.finish_failure();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Implementation of [`build_binary_package`], without failure handling
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn build_binary_package_impl(
|
||||
arch: Option<&str>,
|
||||
series: Option<&str>,
|
||||
pocket: Option<&str>,
|
||||
cwd: Option<&Path>,
|
||||
cross: bool,
|
||||
mode: Option<BuildMode>,
|
||||
ppa: Option<&[&str]>,
|
||||
inject_packages: Option<&[&str]>,
|
||||
ctx: Option<Arc<Context>>,
|
||||
ui: &Option<Arc<DebUi>>,
|
||||
jobs: Option<usize>,
|
||||
opts: DebBuildOptions<'_>,
|
||||
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
||||
let cwd = cwd.unwrap_or_else(|| Path::new("."));
|
||||
let DebBuildOptions {
|
||||
ref arch,
|
||||
ref series,
|
||||
ref pocket,
|
||||
ref cwd,
|
||||
cross,
|
||||
ref mode,
|
||||
ref ppa,
|
||||
ref inject,
|
||||
ref jobs,
|
||||
ref ctx,
|
||||
view,
|
||||
} = opts;
|
||||
let cwd = cwd.as_deref().unwrap_or_else(|| Path::new("."));
|
||||
|
||||
// Parse changelog to get package name, version and series
|
||||
let changelog_path = cwd.join("debian/changelog");
|
||||
@@ -101,44 +201,43 @@ async fn build_binary_package_impl(
|
||||
&package_series
|
||||
};
|
||||
let current_arch = crate::get_current_arch();
|
||||
let arch = arch.unwrap_or(¤t_arch);
|
||||
let arch = arch.as_deref().unwrap_or(¤t_arch);
|
||||
|
||||
// Make sure we select a specific mode, either using user-requested
|
||||
// or by using default for user-supplied parameters
|
||||
let mode = if let Some(m) = mode {
|
||||
m
|
||||
} else {
|
||||
// By default, we use local build
|
||||
BuildMode::Local
|
||||
};
|
||||
let default_mode = BuildMode::Local;
|
||||
let mode = mode.as_ref().unwrap_or(&default_mode);
|
||||
|
||||
// Create an ephemeral unshare context for all Local builds
|
||||
// Use qemu_binfmt when target architecture differs from host and cross is not requested
|
||||
let chroot_arch = if mode == BuildMode::Local && arch != current_arch && !cross {
|
||||
let chroot_arch = if mode == &BuildMode::Local && arch != current_arch && !cross {
|
||||
Some(arch)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Use provided context or get current
|
||||
let base_ctx = ctx.unwrap_or_else(context::current);
|
||||
let base_ctx = ctx.clone().unwrap_or_else(context::current);
|
||||
|
||||
// Identify the target in the live UI once the changelog is parsed, so
|
||||
// Identify the target in the live view once the changelog is parsed, so
|
||||
// even the chroot download output is attributed and tee'd
|
||||
if let Some(u) = ui {
|
||||
u.set_target(&package, &version, series, arch);
|
||||
}
|
||||
view.target(BuildTarget {
|
||||
package: &package,
|
||||
version: &version,
|
||||
target: &format!("{series}/{arch}"),
|
||||
source_only: false,
|
||||
});
|
||||
|
||||
// Create an ephemeral unshare context for all Local builds. It is kept in
|
||||
// this scope so it outlives the guarded section below and is only dropped
|
||||
// once the live view has been cleared.
|
||||
let mut guard = if mode == BuildMode::Local {
|
||||
let mut guard = if *mode == BuildMode::Local {
|
||||
Some(
|
||||
ephemeral::EphemeralContextGuard::new_with_context(
|
||||
series,
|
||||
chroot_arch,
|
||||
base_ctx.clone(),
|
||||
ui.clone(),
|
||||
view,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -179,14 +278,14 @@ async fn build_binary_package_impl(
|
||||
&version,
|
||||
arch,
|
||||
series,
|
||||
pocket,
|
||||
pocket.as_deref(),
|
||||
&build_root,
|
||||
cross,
|
||||
ppa,
|
||||
inject_packages,
|
||||
inject,
|
||||
build_ctx.clone(),
|
||||
ui.clone(),
|
||||
jobs,
|
||||
view,
|
||||
*jobs,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
@@ -194,9 +293,7 @@ async fn build_binary_package_impl(
|
||||
|
||||
// Retrieve the produced artifacts (binary packages plus the upload
|
||||
// metadata) to the parent directory.
|
||||
if let Some(u) = ui {
|
||||
u.phase(Phase::RetrievingArtifacts);
|
||||
}
|
||||
enter_phase(view, Phase::RetrievingArtifacts);
|
||||
let total_debs = remote_files.len();
|
||||
|
||||
let mut artifacts = Vec::with_capacity(total_debs);
|
||||
@@ -206,14 +303,10 @@ async fn build_binary_package_impl(
|
||||
build_ctx.retrieve_path(remote_file, &local_dest)?;
|
||||
artifacts.push(local_dest);
|
||||
|
||||
if let Some(u) = ui {
|
||||
u.count_progress("Retrieving artifacts", idx + 1, total_debs);
|
||||
}
|
||||
view.progress("Retrieving artifacts", idx + 1, total_debs);
|
||||
}
|
||||
|
||||
if let Some(u) = ui {
|
||||
u.finish_success(&artifacts, u.elapsed());
|
||||
}
|
||||
view.finish_success(&artifacts);
|
||||
|
||||
Ok(artifacts)
|
||||
}
|
||||
@@ -222,9 +315,7 @@ async fn build_binary_package_impl(
|
||||
// Clear the live view before returning: the ephemeral guard is dropped at
|
||||
// the end of this function and its cleanup commands (umount, rm -rf of
|
||||
// the chroot) inherit the terminal, so they must not fight the widget.
|
||||
if let Some(u) = ui {
|
||||
u.suspend();
|
||||
}
|
||||
view.suspend();
|
||||
|
||||
// Mark build as successful to trigger chroot cleanup
|
||||
if result.is_ok()
|
||||
@@ -446,19 +537,14 @@ mod tests {
|
||||
log::debug!("Package directory: {}", cwd.display());
|
||||
|
||||
log::info!("Starting binary package build...");
|
||||
crate::deb::build_binary_package(
|
||||
arch,
|
||||
Some(series),
|
||||
None,
|
||||
Some(&cwd),
|
||||
crate::deb::build_binary_package(DebBuildOptions {
|
||||
arch: arch.map(str::to_string),
|
||||
series: Some(series.to_string()),
|
||||
cross,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(ctx),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
cwd: Some(cwd.to_path_buf()),
|
||||
ctx: Some(ctx),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.expect("Cannot build binary package (deb)");
|
||||
log::info!("Successfully built binary package");
|
||||
@@ -620,19 +706,14 @@ mod tests {
|
||||
|
||||
let ctx = Arc::new(Context::new(crate::context::ContextConfig::Local).unwrap());
|
||||
|
||||
crate::deb::build_binary_package(
|
||||
Some("arm64"),
|
||||
Some("noble"),
|
||||
None,
|
||||
Some(&pkg_dir),
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(ctx),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
crate::deb::build_binary_package(DebBuildOptions {
|
||||
arch: Some("arm64".to_string()),
|
||||
series: Some("noble".to_string()),
|
||||
cwd: Some(pkg_dir),
|
||||
cross: true,
|
||||
ctx: Some(ctx),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.expect("Cannot cross-build package declaring Build-Depends-Indep");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user