deb,build: export ddebs from binary builds
CI / build (push) Failing after 54s
CI / test (push) Skipped
CI / snap (push) Skipped
CI / publish (push) Skipped

dh_strip registers debug-symbol packages in debian/files under their
renamed .ddeb name; they are deliverables of the build just like the
regular .debs. Both artifact-type filters, the one collecting the
build's artifacts and the one feeding the .buildinfo/.changes
checksum sections, accepted only deb and udeb, so every ddeb a build
produced was silently discarded: nothing exported next to the .debs,
nothing in the upload metadata.

The filter is now a shared FilesEntry::is_binary_artifact() predicate
(plus unit tests) used by both call sites, so the two cannot drift
apart again.

One dpkg distinction has to be copied rather than shared:
dpkg-genchanges lists only deb/udeb packages in the changes' Binary:
and Description (a ddeb travels in Files and the checksum sections
but is not a listed binary of the upload), while dpkg-genbuildinfo
lists every binary artifact's package, ddebs included. The package
list is therefore accumulated twice, once per consumer.

The binary-metadata differential fixture gains a Package-Type: ddeb
package, so the native .changes/.buildinfo generation is compared
against real dpkg with a ddeb present. Without the fix the comparison
fails on the checksum sections (golden distributes the ddeb, ours
dropped it); with it every field matches.
This commit is contained in:
2026-09-30 14:18:45 +02:00
parent 72992f6ec8
commit ed76b3ef7f
4 changed files with 55 additions and 10 deletions
+18 -6
View File
@@ -92,7 +92,7 @@ pub fn generate_binary_metadata(
// ------------------------------------------------------------------ // ------------------------------------------------------------------
let artifact_names: Vec<String> = files_list let artifact_names: Vec<String> = files_list
.iter() .iter()
.filter(|e| matches!(e.package_type.as_deref(), Some("deb") | Some("udeb"))) .filter(|e| e.is_binary_artifact())
.map(|e| e.filename.clone()) .map(|e| e.filename.clone())
.collect(); .collect();
@@ -159,16 +159,28 @@ pub fn generate_binary_metadata(
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// Binary package names and descriptions // Binary package names and descriptions
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// dpkg-genchanges lists only deb/udeb packages in `Binary:` (a ddeb
// is distributed through Files but is not a listed binary of the
// upload), while dpkg-genbuildinfo lists every binary artifact's
// package, ddebs included.
let mut binaries: Vec<String> = Vec::new(); let mut binaries: Vec<String> = Vec::new();
let mut buildinfo_binaries: Vec<String> = Vec::new();
for name in &artifact_names { for name in &artifact_names {
if let Some(e) = files_list.get(name) let Some(e) = files_list.get(name) else {
&& let Some(pkg) = &e.package continue;
&& !binaries.contains(pkg) };
{ let Some(pkg) = &e.package else {
continue;
};
if !buildinfo_binaries.contains(pkg) {
buildinfo_binaries.push(pkg.clone());
}
if e.package_type.as_deref() != Some("ddeb") && !binaries.contains(pkg) {
binaries.push(pkg.clone()); binaries.push(pkg.clone());
} }
} }
binaries.sort(); binaries.sort();
buildinfo_binaries.sort();
// Description lines: first line of each binary stanza's Description, // Description lines: first line of each binary stanza's Description,
// formatted exactly like dpkg-genchanges, sorted. // formatted exactly like dpkg-genchanges, sorted.
@@ -238,7 +250,7 @@ pub fn generate_binary_metadata(
let buildinfo_doc = let buildinfo_doc =
crate::build::buildinfo::render_buildinfo(&crate::build::buildinfo::BuildInfoInput { crate::build::buildinfo::render_buildinfo(&crate::build::buildinfo::BuildInfoInput {
source: source_display.clone(), source: source_display.clone(),
binaries: binaries.clone(), binaries: buildinfo_binaries.clone(),
architecture: buildinfo_arch_values.join(" "), architecture: buildinfo_arch_values.join(" "),
version: entry.version.full(), version: entry.version.full(),
binary_only_changes: binary_only_changes.clone(), binary_only_changes: binary_only_changes.clone(),
+6 -3
View File
@@ -1889,19 +1889,22 @@ Provides: virtual-thing (= 2.0), plain-virtual
} }
/// debian/control shared by the binary-metadata differential cases: one /// debian/control shared by the binary-metadata differential cases: one
/// arch:any deb and one arch:all udeb. /// arch:any deb, one arch:all udeb and one arch:all ddeb (the
/// debug-symbol shape dh_strip leaves behind: the package type
/// drives the file name in debian/files).
fn binary_test_control(name: &str) -> String { fn binary_test_control(name: &str) -> String {
format!( format!(
"Source: {name}\nSection: utils\nPriority: optional\nMaintainer: {MAINTAINER}\nBuild-Depends: libc6\n\n\ "Source: {name}\nSection: utils\nPriority: optional\nMaintainer: {MAINTAINER}\nBuild-Depends: libc6\n\n\
Package: {name}\nArchitecture: any\nDescription: test package main\n long description\n\n\ Package: {name}\nArchitecture: any\nDescription: test package main\n long description\n\n\
Package: {name}-u\nPackage-Type: udeb\nArchitecture: all\nDescription: test udeb\n short\n" Package: {name}-u\nPackage-Type: udeb\nArchitecture: all\nDescription: test udeb\n short\n\n\
Package: {name}-d\nPackage-Type: ddeb\nArchitecture: all\nDescription: test ddeb\n short\n"
) )
} }
/// debian/rules driving dpkg-gencontrol/dpkg-deb directly (no debhelper). /// debian/rules driving dpkg-gencontrol/dpkg-deb directly (no debhelper).
fn binary_test_rules(name: &str) -> String { fn binary_test_rules(name: &str) -> String {
format!( format!(
"#!/usr/bin/make -f\nV = $(shell dpkg-parsechangelog -S Version)\nA = $(shell dpkg-architecture -qDEB_HOST_ARCH)\n\nbuild:\n\tmkdir -p debian/tmp/usr/bin\n\tprintf '#!/bin/sh\\necho hi\\n' > debian/tmp/usr/bin/hello\n\tchmod 755 debian/tmp/usr/bin/hello\n\ttouch $@\n\nbinary: build\n\trm -rf debian/{name} debian/{name}-u\n\tmkdir -p debian/{name}/usr/bin debian/{name}/DEBIAN\n\tcp -r debian/tmp/. debian/{name}/\n\tdpkg-gencontrol -p{name} -Pdebian/{name}\n\tdpkg-deb --build debian/{name} ..\n\tmkdir -p debian/{name}-u/usr/share debian/{name}-u/DEBIAN\n\techo data > debian/{name}-u/usr/share/data.txt\n\tdpkg-gencontrol -p{name}-u -Pdebian/{name}-u\n\tdpkg-deb --build debian/{name}-u ..\n\tmv ../{name}-u_$(V)_all.deb ../{name}-u_$(V)_all.udeb\n\nclean:\n\trm -rf debian/tmp debian/{name} debian/{name}-u build-stamp debian/files debian/*.substvars\n\n.PHONY: build binary clean\n" "#!/usr/bin/make -f\nV = $(shell dpkg-parsechangelog -S Version)\nA = $(shell dpkg-architecture -qDEB_HOST_ARCH)\n\nbuild:\n\tmkdir -p debian/tmp/usr/bin\n\tprintf '#!/bin/sh\\necho hi\\n' > debian/tmp/usr/bin/hello\n\tchmod 755 debian/tmp/usr/bin/hello\n\ttouch $@\n\nbinary: build\n\trm -rf debian/{name} debian/{name}-u debian/{name}-d\n\tmkdir -p debian/{name}/usr/bin debian/{name}/DEBIAN\n\tcp -r debian/tmp/. debian/{name}/\n\tdpkg-gencontrol -p{name} -Pdebian/{name}\n\tdpkg-deb --build debian/{name} ..\n\tmkdir -p debian/{name}-u/usr/share debian/{name}-u/DEBIAN\n\techo data > debian/{name}-u/usr/share/data.txt\n\tdpkg-gencontrol -p{name}-u -Pdebian/{name}-u\n\tdpkg-deb --build debian/{name}-u ..\n\tmv ../{name}-u_$(V)_all.deb ../{name}-u_$(V)_all.udeb\n\tmkdir -p debian/{name}-d/usr/share debian/{name}-d/DEBIAN\n\techo data > debian/{name}-d/usr/share/data.txt\n\tdpkg-gencontrol -p{name}-d -Pdebian/{name}-d\n\tdpkg-deb --build debian/{name}-d ..\n\tmv ../{name}-d_$(V)_all.deb ../{name}-d_$(V)_all.ddeb\n\nclean:\n\trm -rf debian/tmp debian/{name} debian/{name}-u debian/{name}-d build-stamp debian/files debian/*.substvars\n\n.PHONY: build binary clean\n"
) )
} }
+1 -1
View File
@@ -939,7 +939,7 @@ fn collect_binary_artifacts(
let upload_dir = Path::new(build_root); let upload_dir = Path::new(build_root);
let mut artifacts = Vec::new(); let mut artifacts = Vec::new();
for entry in files_list.iter() { for entry in files_list.iter() {
if matches!(entry.package_type.as_deref(), Some("deb") | Some("udeb")) { if entry.is_binary_artifact() {
artifacts.push(upload_dir.join(&entry.filename)); artifacts.push(upload_dir.join(&entry.filename));
} }
} }
+30
View File
@@ -46,6 +46,21 @@ impl FilesEntry {
entry.priority = priority.to_string(); entry.priority = priority.to_string();
entry entry
} }
/// Whether this entry is a binary-package artifact that must be
/// exported from the build: a `.deb`, a `.udeb`, or a `.ddeb`.
///
/// Debug-symbol packages are registered in `debian/files` by
/// `dh_strip` under their renamed `.ddeb` name; they are as much
/// a deliverable of the build as the regular `.deb`s (they belong
/// in the upload set), so any filter keyed on artifact type has
/// to accept them.
pub fn is_binary_artifact(&self) -> bool {
matches!(
self.package_type.as_deref(),
Some("deb") | Some("udeb") | Some("ddeb")
)
}
} }
/// Derive metadata from an artifact file name following the common /// Derive metadata from an artifact file name following the common
@@ -243,6 +258,21 @@ mod tests {
assert_eq!(e.package_type, None); assert_eq!(e.package_type, None);
} }
#[test]
fn binary_artifact_filter_accepts_ddebs() {
let deb = parse_filename("hello_2.10-3_amd64.deb").unwrap();
let udeb = parse_filename("hello_2.10-3_amd64.udeb").unwrap();
let ddeb = parse_filename("hello-dbgsym_2.10-3_amd64.ddeb").unwrap();
let buildinfo = parse_filename("hello_2.10-3_amd64.buildinfo").unwrap();
let changes = parse_filename("hello_2.10-3_amd64.changes").unwrap();
assert!(deb.is_binary_artifact());
assert!(udeb.is_binary_artifact());
assert!(ddeb.is_binary_artifact(), "ddebs must be exported");
assert!(!buildinfo.is_binary_artifact());
assert!(!changes.is_binary_artifact());
}
#[test] #[test]
fn load_save_roundtrip() { fn load_save_roundtrip() {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();