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:
2026-09-18 20:22:45 +02:00
parent 27b1083b15
commit 052c02cdc3
8 changed files with 390 additions and 428 deletions
+17 -26
View File
@@ -1,5 +1,6 @@
use crate::context::{self, Context, ContextConfig};
use crate::ui::deb::{DebUi, Phase};
use crate::deb::{Phase, enter_phase};
use crate::report::BuildView;
use directories::ProjectDirs;
use std::any::Any;
use std::error::Error;
@@ -312,7 +313,7 @@ impl EphemeralContextGuard {
series: &str,
arch: Option<&str>,
base_ctx: Arc<Context>,
ui: Option<Arc<DebUi>>,
view: &dyn BuildView,
) -> Result<Self, Box<dyn Error>> {
// Save the globally-installed context so Drop can restore exactly
// this handle: concurrent builds install their own ephemeral
@@ -355,7 +356,7 @@ impl EphemeralContextGuard {
// Download and extract the chroot tarball
if let Err(e) =
Self::download_and_extract_chroot(series, arch, &chroot_path, base_ctx.clone(), &ui)
Self::download_and_extract_chroot(series, arch, &chroot_path, base_ctx.clone(), view)
.await
{
// The guard (and its Drop) never materializes on this path, so
@@ -408,7 +409,7 @@ impl EphemeralContextGuard {
arch: Option<&str>,
chroot_path: &PathBuf,
ctx: Arc<context::Context>,
ui: &Option<Arc<DebUi>>,
view: &dyn BuildView,
) -> Result<(), Box<dyn Error>> {
// Clone ctx for use in create_device_nodes after download_chroot_tarball consumes it
let ctx_for_devices = ctx.clone();
@@ -464,10 +465,8 @@ impl EphemeralContextGuard {
series,
arch
);
if let Some(u) = ui {
u.phase(Phase::PreparingChroot);
}
Self::download_chroot_tarball(series, arch, &tarball_path, ctx, ui).await?;
enter_phase(view, Phase::PreparingChroot);
Self::download_chroot_tarball(series, arch, &tarball_path, ctx, view).await?;
} else {
log::debug!(
"Using cached chroot tarball for {} (arch: {:?})",
@@ -478,16 +477,12 @@ impl EphemeralContextGuard {
// Extract tarball to chroot directory
log::debug!("Extracting chroot tarball to {}...", chroot_path.display());
if let Some(u) = ui {
u.phase(Phase::ExtractingChroot);
}
Self::extract_tarball(&tarball_path, chroot_path, ui.as_deref())?;
enter_phase(view, Phase::ExtractingChroot);
Self::extract_tarball(&tarball_path, chroot_path, view)?;
// Create device nodes in the chroot
log::debug!("Creating device nodes in chroot...");
if let Some(u) = ui {
u.phase(Phase::FinalizingChroot);
}
enter_phase(view, Phase::FinalizingChroot);
Self::create_device_nodes(chroot_path, ctx_for_devices.clone())?;
// Bind mount /proc from host into chroot (before entering unshare namespace)
@@ -503,7 +498,7 @@ impl EphemeralContextGuard {
arch: Option<&str>,
tarball_path: &Path,
ctx: Arc<context::Context>,
ui: &Option<Arc<DebUi>>,
view: &dyn BuildView,
) -> Result<(), Box<dyn Error>> {
// Create a lock file to make sure that noone tries to use the file while it's not fully downloaded
let lockfile_path = tarball_path.with_extension("lock");
@@ -537,8 +532,8 @@ impl EphemeralContextGuard {
cmd.arg(series)
.arg(tarball_path.to_string_lossy().to_string());
if let Some(u) = ui {
cmd.capture(u.sink());
if let Some(s) = view.sink() {
cmd.capture(s);
}
let status = cmd.status()?;
@@ -575,7 +570,7 @@ impl EphemeralContextGuard {
fn extract_tarball(
tarball_path: &PathBuf,
chroot_path: &PathBuf,
ui: Option<&DebUi>,
view: &dyn BuildView,
) -> Result<(), Box<dyn Error>> {
// Create the chroot directory
fs::create_dir_all(chroot_path)?;
@@ -593,15 +588,11 @@ impl EphemeralContextGuard {
let mut entry = entry?;
entry.unpack_in(chroot_path)?;
count += 1;
if count.is_multiple_of(100)
&& let Some(u) = ui
{
u.progress_message(&format!("Extracting chroot… ({count} files)"));
if count.is_multiple_of(100) {
view.message(&format!("Extracting chroot… ({count} files)"));
}
}
if let Some(u) = ui {
u.progress_message(&format!("Extracting chroot… ({count} files)"));
}
view.message(&format!("Extracting chroot… ({count} files)"));
Ok(())
}
+75 -88
View File
@@ -1,9 +1,9 @@
/// Local binary package building
/// Directly calling 'debian/rules' in current context
use crate::context::{Context, ContextCommand, LineSink};
use crate::deb::find_dsc_file;
use crate::deb::{Phase, enter_phase, find_dsc_file};
use crate::logfmt::QuiltClassifier;
use crate::ui::deb::{DebUi, Phase};
use crate::report::BuildView;
use log::warn;
use std::collections::{BTreeMap, HashMap};
use std::error::Error;
@@ -33,13 +33,13 @@ pub async fn build(
pocket: Option<&str>,
build_root: &str,
cross: bool,
ppa: Option<&[&str]>,
inject_packages: Option<&[&str]>,
ppa: &[String],
inject_packages: &[String],
ctx: Arc<Context>,
ui: Option<Arc<DebUi>>,
view: &dyn BuildView,
jobs: Option<usize>,
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
let sink: Option<Arc<dyn LineSink>> = ui.as_ref().map(|u| u.sink());
let sink: Option<Arc<dyn LineSink>> = view.sink();
// Environment
let mut env = HashMap::<String, String>::new();
@@ -83,55 +83,53 @@ pub async fn build(
let mut added_ppas: Vec<(&str, &str)> = Vec::new();
// Add PPA repositories if specified
if let Some(ppas) = ppa {
for ppa_str in ppas {
// PPA format: user/ppa_name
let parts: Vec<&str> = ppa_str.split('/').collect();
if parts.len() == 2 {
let base_url = crate::package_info::ppa_to_base_url(parts[0], parts[1]);
for ppa_str in ppa {
// PPA format: user/ppa_name
let parts: Vec<&str> = ppa_str.split('/').collect();
if parts.len() == 2 {
let base_url = crate::package_info::ppa_to_base_url(parts[0], parts[1]);
// Add new PPA source if not found
if !sources.iter().any(|s| s.uri.contains(&base_url)) {
// Get host and target architectures
let host_arch = crate::get_current_arch();
let target_arch = arch;
// Add new PPA source if not found
if !sources.iter().any(|s| s.uri.contains(&base_url)) {
// Get host and target architectures
let host_arch = crate::get_current_arch();
let target_arch = arch;
// Create architectures list with both host and target if different
let mut architectures = vec![host_arch.clone()];
if host_arch != *target_arch {
architectures.push(target_arch.to_string());
}
// Create suite list with all Ubuntu series
let suites = vec![series.to_string()];
let new_source = crate::apt::sources::SourceEntry {
enabled: true,
kind: crate::apt::sources::SourceKind::Deb,
components: vec!["main".to_string()],
architectures: architectures.clone(),
signed_by: None,
trusted: None,
suite: suites,
uri: base_url,
// No origin: saved to the pkh-owned added-sources file
origin: None,
};
sources.push(new_source);
modified = true;
added_ppas.push((parts[0], parts[1]));
log::info!(
"Added PPA: {} for series {} with architectures {:?}",
ppa_str,
series,
architectures
);
// Create architectures list with both host and target if different
let mut architectures = vec![host_arch.clone()];
if host_arch != *target_arch {
architectures.push(target_arch.to_string());
}
} else {
return Err(
format!("Invalid PPA format: '{}'. Expected: user/ppa_name", ppa_str).into(),
// Create suite list with all Ubuntu series
let suites = vec![series.to_string()];
let new_source = crate::apt::sources::SourceEntry {
enabled: true,
kind: crate::apt::sources::SourceKind::Deb,
components: vec!["main".to_string()],
architectures: architectures.clone(),
signed_by: None,
trusted: None,
suite: suites,
uri: base_url,
// No origin: saved to the pkh-owned added-sources file
origin: None,
};
sources.push(new_source);
modified = true;
added_ppas.push((parts[0], parts[1]));
log::info!(
"Added PPA: {} for series {} with architectures {:?}",
ppa_str,
series,
architectures
);
}
} else {
return Err(
format!("Invalid PPA format: '{}'. Expected: user/ppa_name", ppa_str).into(),
);
}
}
@@ -193,9 +191,7 @@ pub async fn build(
// Update package lists
log::debug!("Updating package lists for local build...");
if let Some(u) = &ui {
u.phase(Phase::UpdatingPackageLists);
}
enter_phase(view, Phase::UpdatingPackageLists);
let status = cap(
ctx.command("apt-get").envs(env.clone()).arg("update"),
&sink,
@@ -234,9 +230,7 @@ pub async fn build(
cmd.arg(format!("libc6:{arch}"));
cmd.arg(format!("libc6-dev:{arch}"));
}
if let Some(u) = &ui {
u.phase(Phase::InstallingEssentials);
}
enter_phase(view, Phase::InstallingEssentials);
let status = cap(&mut cmd, &sink).status()?;
if !status.success() {
return Err("Could not install essential packages for the build".into());
@@ -261,18 +255,16 @@ pub async fn build(
}
// Apply quilt patches if the package provides a patch series
apply_quilt_patches(package_dir_str, &env, ctx.clone(), &ui, &sink)?;
apply_quilt_patches(package_dir_str, &env, ctx.clone(), view, &sink)?;
// Install injected packages if specified
if let Some(packages) = inject_packages {
install_injected_packages(packages, &env, ctx.clone(), &ui, &sink)?;
if !inject_packages.is_empty() {
install_injected_packages(inject_packages, &env, ctx.clone(), view, &sink)?;
}
// Install arch-specific build dependencies
log::debug!("Installing arch-specific build dependencies...");
if let Some(u) = &ui {
u.phase(Phase::InstallingBuildDeps);
}
enter_phase(view, Phase::InstallingBuildDeps);
let mut cmd = ctx.command("apt-get");
cmd.current_dir(package_dir_str)
.envs(env.clone())
@@ -286,9 +278,7 @@ pub async fn build(
// If build-dep fails, we try to explain the failure using dose-debcheck
if !status.success() {
if let Some(u) = &ui {
u.suspend();
}
view.suspend();
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
return Err("Could not install build-dependencies for the build".into());
}
@@ -320,9 +310,7 @@ pub async fn build(
// If build-dep fails, we try to explain the failure using dose-debcheck
if !status.success() {
if let Some(u) = &ui {
u.suspend();
}
view.suspend();
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
return Err("Could not install build-dependencies for the build".into());
}
@@ -330,9 +318,7 @@ pub async fn build(
// Run the build step
log::debug!("Building (debian/rules build) package...");
if let Some(u) = &ui {
u.phase(Phase::Building);
}
enter_phase(view, Phase::Building);
let status = cap(
ctx.command("debian/rules")
.current_dir(package_dir_str)
@@ -346,9 +332,7 @@ pub async fn build(
}
// Run the 'binary' step to produce deb
if let Some(u) = &ui {
u.phase(Phase::ProducingBinaries);
}
enter_phase(view, Phase::ProducingBinaries);
let status = cap(
ctx.command("fakeroot")
.current_dir(package_dir_str)
@@ -495,7 +479,7 @@ fn apply_quilt_patches(
package_dir: &str,
env: &HashMap<String, String>,
ctx: Arc<Context>,
ui: &Option<Arc<DebUi>>,
view: &dyn BuildView,
sink: &Option<Arc<dyn LineSink>>,
) -> Result<(), Box<dyn Error>> {
let series_path = Path::new(package_dir).join("debian/patches/series");
@@ -555,12 +539,10 @@ fn apply_quilt_patches(
}
// Apply all patches listed in the series
if let Some(u) = ui {
u.phase_with(
Phase::ApplyingPatches,
Box::new(QuiltClassifier::new(total_patches)),
);
}
view.phase(
Phase::ApplyingPatches.label(),
Box::new(QuiltClassifier::new(total_patches)),
);
let mut patch_env = env.clone();
patch_env.insert("QUILT_PATCHES".to_string(), "debian/patches".to_string());
let status = cap(
@@ -631,17 +613,15 @@ fn pin_pocket(pocket_suite: &str, ctx: &Arc<Context>) -> Result<(), Box<dyn Erro
}
fn install_injected_packages(
packages: &[&str],
packages: &[String],
env: &HashMap<String, String>,
ctx: Arc<Context>,
ui: &Option<Arc<DebUi>>,
view: &dyn BuildView,
sink: &Option<Arc<dyn LineSink>>,
) -> Result<(), Box<dyn Error>> {
log::info!("Installing injected packages: {:?}", packages);
if let Some(u) = ui {
u.phase(Phase::InjectingPackages);
}
enter_phase(view, Phase::InjectingPackages);
// Separate .deb files from package names
let mut deb_files: Vec<String> = Vec::new();
@@ -661,7 +641,7 @@ fn install_injected_packages(
);
deb_files.push(chroot_path.to_string_lossy().to_string());
} else {
package_names.push(pkg);
package_names.push(pkg.as_str());
}
}
@@ -855,6 +835,13 @@ mod tests {
)
.unwrap();
apply_quilt_patches(tree.to_str().unwrap(), &HashMap::new(), ctx, &None, &None).unwrap();
apply_quilt_patches(
tree.to_str().unwrap(),
&HashMap::new(),
ctx,
&crate::report::Quiet,
&None,
)
.unwrap();
}
}
+183 -102
View File
@@ -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(&current_arch);
let arch = arch.as_deref().unwrap_or(&current_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");