diff --git a/src/deb/local.rs b/src/deb/local.rs index 8d24412..c067c15 100644 --- a/src/deb/local.rs +++ b/src/deb/local.rs @@ -7,7 +7,7 @@ use crate::ui::logfmt::QuiltClassifier; use log::warn; use std::collections::HashMap; use std::error::Error; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Arc; use crate::apt; @@ -38,7 +38,7 @@ pub async fn build( ctx: Arc, ui: Option>, jobs: Option, -) -> Result<(), Box> { +) -> Result, Box> { let sink: Option> = ui.as_ref().map(|u| u.sink()); // Environment @@ -334,15 +334,60 @@ pub async fn build( ); } + // Collect the exact set of artifacts produced by this build so the caller + // retrieves only those: the binary packages registered in debian/files + // (the contract between dh_builddeb/dpkg-gencontrol and the artifact + // generators) plus the .buildinfo/.changes generated below. This avoids + // globbing the build root, which would also surface stale files copied + // alongside the package tree. + let mut artifacts = collect_binary_artifacts(&ctx, package_dir_str, build_root)?; + // Generate the upload metadata (.buildinfo + .changes) natively, the // equivalent of dpkg-genbuildinfo -b + dpkg-genchanges -b, consuming // debian/files produced by the build. Failures are logged but do not // discard the produced binaries. - if let Err(e) = generate_upload_metadata(package_dir_str, build_root, arch, cross, &env, &ctx) { - warn!("failed to generate .buildinfo/.changes: {}", e); + match generate_upload_metadata(package_dir_str, build_root, arch, cross, &env, &ctx) { + Ok((buildinfo, changes)) => { + log::info!( + "generated upload metadata: {} and {}", + buildinfo.display(), + changes.display() + ); + artifacts.push(buildinfo); + artifacts.push(changes); + } + Err(e) => { + warn!("failed to generate .buildinfo/.changes: {}", e); + } } - Ok(()) + Ok(artifacts) +} + +/// Collect the binary artifacts (.deb/.udeb) registered by the build in +/// `debian/files`, returning their paths inside the build context +/// (`/`). `debian/files` is the canonical record of +/// which binary packages `debian/rules binary` produced, so we retrieve +/// exactly those instead of globbing the build root (which would also pick +/// up stale files copied alongside the package tree). +fn collect_binary_artifacts( + ctx: &Arc, + package_dir: &str, + build_root: &str, +) -> Result, Box> { + let files_content = ctx + .read_file(&Path::new(package_dir).join("debian/files")) + .unwrap_or_default(); + let files_list = crate::debian::FilesList::parse(&files_content) + .map_err(|e| format!("invalid debian/files in {package_dir}: {e}"))?; + let upload_dir = Path::new(build_root); + let mut artifacts = Vec::new(); + for entry in files_list.iter() { + if matches!(entry.package_type.as_deref(), Some("deb") | Some("udeb")) { + artifacts.push(upload_dir.join(&entry.filename)); + } + } + Ok(artifacts) } /// Generate `.buildinfo` and `.changes` for the finished binary build, @@ -354,9 +399,7 @@ fn generate_upload_metadata( cross: bool, env: &HashMap, ctx: &Arc, -) -> Result<(), Box> { - use std::path::Path; - +) -> Result<(PathBuf, PathBuf), Box> { let changelog_path = Path::new(package_dir).join("debian/changelog"); let changelog_content = ctx.read_file(&changelog_path)?; let entry = crate::debian::parse_changelog_entry_from_str(&changelog_content)?; @@ -414,12 +457,7 @@ fn generate_upload_metadata( Path::new(build_root), &opts, )?; - log::info!( - "generated upload metadata: {} and {}", - buildinfo.display(), - changes.display() - ); - Ok(()) + Ok((buildinfo, changes)) } /// Apply quilt patches before building, if the package provides a diff --git a/src/deb/mod.rs b/src/deb/mod.rs index 658d7bf..d1d96ce 100644 --- a/src/deb/mod.rs +++ b/src/deb/mod.rs @@ -17,10 +17,13 @@ pub enum BuildMode { /// Build package in 'cwd' to a .deb /// -/// Returns the list of produced .deb files retrieved locally. When `ui` is +/// 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. +/// 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>, @@ -148,8 +151,11 @@ async fn build_binary_package_impl( .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 { + // Run the build using target build mode. It returns the exact set of + // artifacts produced by this build (binary packages registered in + // debian/files plus the generated .buildinfo/.changes), as paths + // inside the build context. + let remote_files: Vec = match mode { BuildMode::Local => { local::build( &package, @@ -167,30 +173,20 @@ async fn build_binary_package_impl( ) .await? } - } + }; - // Retrieve produced artifacts (.deb files plus the upload metadata - // (.buildinfo/.changes) generated natively after the build) + // Retrieve the produced artifacts (binary packages plus the upload + // metadata) to the parent directory. if let Some(u) = ui { u.phase(Phase::RetrievingArtifacts); } - let remote_files = build_ctx.list_files(Path::new(&build_root))?; - let deb_files: Vec = remote_files - .into_iter() - .filter(|f| { - f.extension().is_some_and(|ext| { - matches!( - ext.to_str(), - Some("deb") | Some("buildinfo") | Some("changes") - ) - }) - }) - .collect(); - let total_debs = deb_files.len(); + let total_debs = remote_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")?; + for (idx, remote_file) in remote_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)?; artifacts.push(local_dest);