build: stop redistributing the previous source on binNMU uploads

dpkg-genchanges/genbuildinfo handle a binary-only upload by referencing
the previous source version textually (Source: pkg (prev),
Binary-Only: yes, Binary-Only-Changes) while distributing no source
files at all: pkh instead pulled the previous .dsc and its tarballs
into both documents whenever they sat next to the artifacts, re-uploading
the whole source on every binNMU.

Drop that redistribution (and include_dsc_artifacts with it), and emit
the missing Binary-Only: yes field, which the new differential test
against real dpkg-buildpackage -b caught. The binNMU case shares its
runner with the regular binary metadata differential; a unit test pins
the exclusion even with the previous artifacts present.
This commit is contained in:
2026-09-18 00:24:42 +02:00
parent 2b017dcf43
commit af870cb7cb
3 changed files with 191 additions and 212 deletions
+75 -16
View File
@@ -638,6 +638,7 @@ pub fn run_source_build(
date: entry.date_raw.clone(),
source: source_display.clone(),
binaries: Vec::new(), // source-only upload
binary_only: entry.binary_only,
built_for_profiles: profiles.clone(),
architecture: "source".to_string(),
version: entry.version.full(),
@@ -1863,23 +1864,50 @@ Provides: virtual-thing (= 2.0), plain-virtual
#[test]
fn diff_binary_build_metadata() {
const NAME: &str = "pkh-diff-m";
let control = format!(
"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}-u\nPackage-Type: udeb\nArchitecture: all\nDescription: test udeb\n short\n"
);
let changelog = format!(
"{NAME} (1.0-1) unstable; urgency=medium\n\n * Binary build test.\n\n -- {MAINTAINER} {DATE}\n"
);
let rules = 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"
);
diff_binary_metadata_case(NAME, &changelog, "1.0-1", false);
}
/// debian/control shared by the binary-metadata differential cases: one
/// arch:any deb and one arch:all udeb.
fn binary_test_control(name: &str) -> String {
format!(
"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}-u\nPackage-Type: udeb\nArchitecture: all\nDescription: test udeb\n short\n"
)
}
/// debian/rules driving dpkg-gencontrol/dpkg-deb directly (no debhelper).
fn binary_test_rules(name: &str) -> String {
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"
)
}
/// Both-sides binary metadata comparison for one changelog: golden
/// `dpkg-buildpackage -b` against the pkh deb flow (rules targets with a
/// dpkg-buildpackage-like environment) plus native metadata generation.
/// `artifact_version` is the full version the artifacts are named after;
/// `with_prev_source` additionally places the previous version's `.dsc`
/// and tarball next to the tree on both sides (the binNMU trap: they must
/// not be redistributed).
fn diff_binary_metadata_case(
name: &str,
changelog: &str,
artifact_version: &str,
with_prev_source: bool,
) {
let control = binary_test_control(name);
let rules = binary_test_rules(name);
let write_tree = |root: &Path| {
fs::create_dir_all(root.join(format!("{NAME}/debian/source"))).expect("mkdir tree");
let tree = root.join(NAME);
fs::create_dir_all(root.join(format!("{name}/debian/source"))).expect("mkdir tree");
let tree = root.join(name);
fs::write(tree.join("debian/control"), &control).expect("write control");
fs::write(tree.join("debian/changelog"), &changelog).expect("write changelog");
fs::write(tree.join("debian/changelog"), changelog).expect("write changelog");
fs::write(tree.join("debian/source/format"), "3.0 (native)\n").expect("write format");
fs::write(tree.join("debian/rules"), &rules).expect("write rules");
#[cfg(unix)]
@@ -1900,6 +1928,20 @@ Provides: virtual-thing (= 2.0), plain-virtual
let golden_tree = write_tree(&golden_root);
let ours_tree = write_tree(&ours_root);
if with_prev_source {
for root in [&golden_root, &ours_root] {
let dsc = format!(
"Format: 3.0 (native)\nSource: {name}\nBinary: {name}\nArchitecture: all\n\
Version: 1.0-1\nMaintainer: {MAINTAINER}\nChecksums-Sha1:\n aaa111 12 \
{name}_1.0.tar.xz\nChecksums-Sha256:\n bbb222 12 {name}_1.0.tar.xz\nFiles:\n \
ddd333 12 utils optional {name}_1.0.tar.xz\n"
);
fs::write(root.join(format!("{name}_1.0-1.dsc")), dsc).expect("write previous dsc");
fs::write(root.join(format!("{name}_1.0.tar.xz")), "tarball byte")
.expect("write previous tarball");
}
}
// Golden side: real dpkg-buildpackage binary build.
let status = crate::test_support::run_logged(
Command::new("dpkg-buildpackage")
@@ -1915,7 +1957,7 @@ Provides: virtual-thing (= 2.0), plain-virtual
// rules targets directly by default (missing Rules-Requires-Root is
// treated as 'no'), so no fakeroot wrapper here either.
let entry =
crate::debian::parse_changelog_entry_from_str(&changelog).expect("parse changelog");
crate::debian::parse_changelog_entry_from_str(changelog).expect("parse changelog");
let vendor = env::current_vendor();
let profiles = env::resolve_build_profiles(&[], &vendor);
let parallel = env::num_parallel();
@@ -1959,15 +2001,32 @@ Provides: virtual-thing (= 2.0), plain-virtual
// Compare artifacts.
assert_changes_equivalent(
&golden_root.join(format!("{NAME}_1.0-1_amd64.changes")),
&ours_root.join(format!("{NAME}_1.0-1_amd64.changes")),
&golden_root.join(format!("{name}_{artifact_version}_amd64.changes")),
&ours_root.join(format!("{name}_{artifact_version}_amd64.changes")),
);
assert_buildinfo_equivalent(
&golden_root.join(format!("{NAME}_1.0-1_amd64.buildinfo")),
&ours_root.join(format!("{NAME}_1.0-1_amd64.buildinfo")),
&golden_root.join(format!("{name}_{artifact_version}_amd64.buildinfo")),
&ours_root.join(format!("{name}_{artifact_version}_amd64.buildinfo")),
);
}
/// Differential check of the binary-only (binNMU) metadata against real
/// `dpkg-buildpackage -b`: with the previous source artifacts sitting
/// next to the tree (as after a source build), the `.changes` must
/// distribute only the binaries and the `.buildinfo`, both documents
/// referencing the previous version textually only. Regression guard for
/// the previous-source redistribution pkh used to emit.
#[test]
fn diff_binmu_binary_metadata() {
const NAME: &str = "pkh-diff-r";
let changelog = format!(
"{NAME} (1.0-1+b1) unstable; urgency=medium, binary-only=yes\n\n * Binary-only \
rebuild.\n\n -- {MAINTAINER} {DATE}\n\n{NAME} (1.0-1) unstable; urgency=medium\n\n \
* Initial release.\n\n -- {MAINTAINER} {DATE}\n"
);
diff_binary_metadata_case(NAME, &changelog, "1.0-1+b1", true);
}
#[test]
fn diff_native_minimal() {
differential_case(&FixtureSpec::new("pkh-diff-a", "1.0-1", "unstable"));