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
+117 -55
View File
@@ -1,7 +1,9 @@
/// Local binary package building
/// Directly calling 'debian/rules' in current context
use crate::context::Context;
use crate::context::{Context, ContextCommand, LineSink};
use crate::deb::find_dsc_file;
use crate::ui::deb::{DebUi, Phase};
use crate::ui::logfmt::QuiltClassifier;
use log::warn;
use std::collections::HashMap;
use std::error::Error;
@@ -11,6 +13,17 @@ use std::sync::Arc;
use crate::apt;
use crate::deb::cross;
/// Attach the capture sink to a command when the live UI is active
fn cap<'a>(
cmd: &'a mut ContextCommand<'a>,
sink: &Option<Arc<dyn LineSink>>,
) -> &'a mut ContextCommand<'a> {
if let Some(s) = sink {
cmd.capture(s.clone());
}
cmd
}
#[allow(clippy::too_many_arguments)]
pub async fn build(
package: &str,
@@ -23,7 +36,10 @@ pub async fn build(
ppa: Option<&[&str]>,
inject_packages: Option<&[&str]>,
ctx: Arc<Context>,
ui: Option<Arc<DebUi>>,
) -> Result<(), Box<dyn Error>> {
let sink: Option<Arc<dyn LineSink>> = ui.as_ref().map(|u| u.sink());
// Environment
let mut env = HashMap::<String, String>::new();
env.insert("LANG".to_string(), "C".to_string());
@@ -155,19 +171,22 @@ pub async fn build(
// Update package lists
log::debug!("Updating package lists for local build...");
let status = ctx
.command("apt-get")
.envs(env.clone())
.arg("update")
.status()
.map_err(|e| {
format!(
"Failed to run 'apt-get update' inside the build context: {}. \
if let Some(u) = &ui {
u.phase(Phase::UpdatingPackageLists);
}
let status = cap(
ctx.command("apt-get").envs(env.clone()).arg("update"),
&sink,
)
.status()
.map_err(|e| {
format!(
"Failed to run 'apt-get update' inside the build context: {}. \
If this is a local build, make sure apt-get is available and \
try executing with sudo.",
e
)
})?;
e
)
})?;
if !status.success() {
return Err("apt-get update failed inside the build context. \
If this is a local build, try executing with sudo, \
@@ -193,7 +212,10 @@ pub async fn build(
cmd.arg(format!("libc6:{arch}"));
cmd.arg(format!("libc6-dev:{arch}"));
}
let status = cmd.status()?;
if let Some(u) = &ui {
u.phase(Phase::InstallingEssentials);
}
let status = cap(&mut cmd, &sink).status()?;
if !status.success() {
return Err("Could not install essential packages for the build".into());
}
@@ -206,15 +228,18 @@ pub async fn build(
.ok_or("Invalid package directory path")?;
// Apply quilt patches if the package provides a patch series
apply_quilt_patches(package_dir_str, &env, ctx.clone())?;
apply_quilt_patches(package_dir_str, &env, ctx.clone(), &ui, &sink)?;
// Install injected packages if specified
if let Some(packages) = inject_packages {
install_injected_packages(packages, &env, ctx.clone())?;
install_injected_packages(packages, &env, ctx.clone(), &ui, &sink)?;
}
// Install arch-specific build dependencies
log::debug!("Installing arch-specific build dependencies...");
if let Some(u) = &ui {
u.phase(Phase::InstallingBuildDeps);
}
let mut cmd = ctx.command("apt-get");
cmd.current_dir(package_dir_str)
.envs(env.clone())
@@ -224,51 +249,69 @@ pub async fn build(
cmd.arg(format!("--host-architecture={arch}"));
}
cmd.arg("--arch-only");
let status = cmd.arg("./").status()?;
let status = cap(&mut cmd, &sink).arg("./").status()?;
// If build-dep fails, we try to explain the failure using dose-debcheck
if !status.success() {
if let Some(u) = &ui {
u.suspend();
}
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
return Err("Could not install build-dependencies for the build".into());
}
// Install arch-independant build dependencies
log::debug!("Installing arch-independant build dependencies...");
let status = ctx
.command("apt-get")
.current_dir(package_dir_str)
.envs(env.clone())
.arg("-y")
.arg("build-dep")
.arg("./")
.status()?;
let status = cap(
ctx.command("apt-get")
.current_dir(package_dir_str)
.envs(env.clone())
.arg("-y")
.arg("build-dep")
.arg("./"),
&sink,
)
.status()?;
// If build-dep fails, we try to explain the failure using dose-debcheck
if !status.success() {
if let Some(u) = &ui {
u.suspend();
}
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
return Err("Could not install build-dependencies for the build".into());
}
// Run the build step
log::debug!("Building (debian/rules build) package...");
let status = ctx
.command("debian/rules")
.current_dir(package_dir_str)
.envs(env.clone())
.arg("build")
.status()?;
if let Some(u) = &ui {
u.phase(Phase::Building);
}
let status = cap(
ctx.command("debian/rules")
.current_dir(package_dir_str)
.envs(env.clone())
.arg("build"),
&sink,
)
.status()?;
if !status.success() {
return Err("Error while building the package".into());
}
// Run the 'binary' step to produce deb
let status = ctx
.command("fakeroot")
.current_dir(package_dir_str)
.envs(env.clone())
.arg("debian/rules")
.arg("binary")
.status()?;
if let Some(u) = &ui {
u.phase(Phase::ProducingBinaries);
}
let status = cap(
ctx.command("fakeroot")
.current_dir(package_dir_str)
.envs(env.clone())
.arg("debian/rules")
.arg("binary"),
&sink,
)
.status()?;
if !status.success() {
return Err(
"Error while building the binary artifacts (.deb) from the built package".into(),
@@ -284,6 +327,8 @@ fn apply_quilt_patches(
package_dir: &str,
env: &HashMap<String, String>,
ctx: Arc<Context>,
ui: &Option<Arc<DebUi>>,
sink: &Option<Arc<dyn LineSink>>,
) -> Result<(), Box<dyn Error>> {
let series_path = Path::new(package_dir).join("debian/patches/series");
if !ctx.exists(&series_path)? {
@@ -296,9 +341,11 @@ fn apply_quilt_patches(
// Skip patch application if the series file contains no patches
let series_content = ctx.read_file(&series_path)?;
let has_patches = series_content
let total_patches = series_content
.lines()
.any(|line| !line.trim().is_empty() && !line.trim().starts_with('#'));
.filter(|line| !line.trim().is_empty() && !line.trim().starts_with('#'))
.count();
let has_patches = total_patches > 0;
if !has_patches {
log::debug!(
"'{}' contains no patches, skipping quilt patch application",
@@ -309,28 +356,37 @@ fn apply_quilt_patches(
// Make sure quilt is available in the build context
log::debug!("Installing quilt for patch application...");
let status = ctx
.command("apt-get")
.envs(env.clone())
.arg("-y")
.arg("install")
.arg("quilt")
.status()?;
let status = cap(
ctx.command("apt-get")
.envs(env.clone())
.arg("-y")
.arg("install")
.arg("quilt"),
sink,
)
.status()?;
if !status.success() {
return Err("Could not install 'quilt', required to apply patches".into());
}
// Apply all patches listed in the series
log::info!("Applying quilt patches from debian/patches/series...");
if let Some(u) = ui {
u.phase_with(
Phase::ApplyingPatches,
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 = ctx
.command("quilt")
.current_dir(package_dir)
.envs(patch_env)
.arg("push")
.arg("-a")
.status()?;
let status = cap(
ctx.command("quilt")
.current_dir(package_dir)
.envs(patch_env)
.arg("push")
.arg("-a"),
sink,
)
.status()?;
if !status.success() {
return Err("Failed to apply quilt patches ('quilt push -a')".into());
}
@@ -360,9 +416,15 @@ fn install_injected_packages(
packages: &[&str],
env: &HashMap<String, String>,
ctx: Arc<Context>,
ui: &Option<Arc<DebUi>>,
sink: &Option<Arc<dyn LineSink>>,
) -> Result<(), Box<dyn Error>> {
log::info!("Installing injected packages: {:?}", packages);
if let Some(u) = ui {
u.phase(Phase::InjectingPackages);
}
// Separate .deb files from package names
let mut deb_files: Vec<String> = Vec::new();
let mut package_names: Vec<&str> = Vec::new();
@@ -400,7 +462,7 @@ fn install_injected_packages(
if !package_names.is_empty() {
cmd.args(&package_names);
}
let status = cmd.status()?;
let status = cap(&mut cmd, sink).status()?;
if !status.success() {
return Err(format!("Could not install injected packages: {:?}", deb_files).into());
}