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
+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();
}
}