deb: change ui/ux of pkh deb
CI / build (push) Successful in 2m54s
CI / test (push) Skipped
CI / snap (push) Failing after 12s

This commit is contained in:
2026-08-22 22:26:20 +02:00
parent e2d201d815
commit e5adf600c3
16 changed files with 1899 additions and 199 deletions
+125 -40
View File
@@ -3,6 +3,7 @@ mod ephemeral;
mod local;
use crate::context::{self, Context};
use crate::ui::deb::{DebUi, Phase};
use std::error::Error;
use std::path::{Path, PathBuf};
use std::sync::Arc;
@@ -15,6 +16,11 @@ pub enum BuildMode {
}
/// Build package in 'cwd' to a .deb
///
/// Returns the list of produced .deb files retrieved locally. 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)]
pub async fn build_binary_package(
arch: Option<&str>,
@@ -26,7 +32,43 @@ pub async fn build_binary_package(
ppa: Option<&[&str]>,
inject_packages: Option<&[&str]>,
ctx: Option<Arc<Context>>,
) -> Result<(), Box<dyn Error>> {
ui: Option<Arc<DebUi>>,
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
let result = build_binary_package_impl(
arch,
series,
pocket,
cwd,
cross,
mode,
ppa,
inject_packages,
ctx,
&ui,
)
.await;
if let (Some(u), Err(_)) = (&ui, &result) {
u.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>>,
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
let cwd = cwd.unwrap_or_else(|| Path::new("."));
// Parse changelog to get package name, version and series
@@ -61,12 +103,22 @@ pub async fn build_binary_package(
// Use provided context or get current
let base_ctx = ctx.unwrap_or_else(context::current);
// Identify the target in the live UI 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);
}
// 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 {
Some(
ephemeral::EphemeralContextGuard::new_with_context(
series,
chroot_arch,
base_ctx.clone(),
ui.clone(),
)
.await?,
)
@@ -74,59 +126,91 @@ pub async fn build_binary_package(
None
};
// Get the build context - either the ephemeral context or the base context
let build_ctx = if mode == BuildMode::Local {
context::current()
} else {
base_ctx.clone()
};
let result = async {
// Get the build context - either the ephemeral context or the base context
let build_ctx = if mode == BuildMode::Local {
context::current()
} else {
base_ctx.clone()
};
// Prepare build directory
let build_root = build_ctx.create_temp_dir()?;
// Prepare build directory
let build_root = build_ctx.create_temp_dir()?;
// Ensure availability of all needed files for the build
let parent_dir = cwd.parent().ok_or("Cannot find parent directory")?;
build_ctx.ensure_available(parent_dir, &build_root)?;
let parent_dir_name = parent_dir
.file_name()
.ok_or("Cannot find parent directory name")?;
let build_root = format!("{}/{}", build_root, parent_dir_name.to_str().unwrap());
// Ensure availability of all needed files for the build
let parent_dir = cwd.parent().ok_or("Cannot find parent directory")?;
build_ctx.ensure_available(parent_dir, &build_root)?;
let parent_dir_name = parent_dir
.file_name()
.ok_or("Cannot find parent directory name")?;
let build_root = format!("{}/{}", build_root, parent_dir_name.to_str().unwrap());
// Run the build using target build mode
match mode {
BuildMode::Local => {
local::build(
&package,
&version,
arch,
series,
pocket,
&build_root,
cross,
ppa,
inject_packages,
build_ctx.clone(),
)
.await?
// Run the build using target build mode
match mode {
BuildMode::Local => {
local::build(
&package,
&version,
arch,
series,
pocket,
&build_root,
cross,
ppa,
inject_packages,
build_ctx.clone(),
ui.clone(),
)
.await?
}
}
}
// Retrieve produced .deb files
let remote_files = build_ctx.list_files(Path::new(&build_root))?;
for remote_file in remote_files {
if remote_file.extension().is_some_and(|ext| ext == "deb") {
// Retrieve produced .deb files
if let Some(u) = ui {
u.phase(Phase::RetrievingArtifacts);
}
let remote_files = build_ctx.list_files(Path::new(&build_root))?;
let deb_files: Vec<PathBuf> = remote_files
.into_iter()
.filter(|f| f.extension().is_some_and(|ext| ext == "deb"))
.collect();
let total_debs = deb_files.len();
let mut artifacts = Vec::with_capacity(total_debs);
for (idx, remote_file) in deb_files.iter().enumerate() {
let file_name = remote_file.file_name().ok_or("Invalid remote filename")?;
let local_dest = parent_dir.join(file_name);
build_ctx.retrieve_path(&remote_file, &local_dest)?;
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);
}
}
if let Some(u) = ui {
u.finish_success(&artifacts, u.elapsed());
}
Ok(artifacts)
}
.await;
// 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();
}
// Mark build as successful to trigger chroot cleanup
if let Some(ref mut g) = guard {
if result.is_ok()
&& let Some(ref mut g) = guard
{
g.mark_build_successful();
}
Ok(())
result
}
/// Find the current package directory by trying both patterns:
@@ -321,6 +405,7 @@ mod tests {
None,
None,
Some(ctx),
None,
)
.await
.expect("Cannot build binary package (deb)");