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:
+109
-196
@@ -16,8 +16,6 @@ use std::sync::Arc;
|
||||
use crate::context::Context;
|
||||
use crate::debian::{ChecksumEntry, ControlInfo, FileChecksums, FilesList};
|
||||
|
||||
use super::parse_checksum_field;
|
||||
|
||||
/// Digests of one artifact.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct ArtifactHashes {
|
||||
@@ -57,7 +55,7 @@ pub struct BinaryMetadataOptions {
|
||||
/// `dpkg-genchanges -b`: sorted `Binary` list, encounter-order `Architecture`
|
||||
/// accumulation, sorted `Description` lines formatted like dpkg, `.buildinfo`
|
||||
/// registration in `debian/files`, and binary-NMU handling (`Source:
|
||||
/// pkg (prev)` + previous `.dsc` redistribution when present).
|
||||
/// pkg (prev)` + `Binary-Only-Changes`, with no source files distributed).
|
||||
pub fn generate_binary_metadata(
|
||||
ctx: &Arc<Context>,
|
||||
package_dir: &Path,
|
||||
@@ -137,28 +135,24 @@ pub fn generate_binary_metadata(
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Binary-NMU: redistribute the previous source when present
|
||||
// Binary-NMU: reference the previous source version, textually only
|
||||
// ------------------------------------------------------------------
|
||||
let sversion = entry.version.no_epoch();
|
||||
let mut source_display = entry.source.clone();
|
||||
let mut binary_only_changes = None;
|
||||
|
||||
if entry.binary_only {
|
||||
// A binary-only upload must reference the previous source version;
|
||||
// a changelog that cannot yield it is a hard error, like in the
|
||||
// source-build path. Reuse the changelog read above instead of
|
||||
// reading the file a second time.
|
||||
// Like dpkg-genchanges/genbuildinfo, a binary-only upload references
|
||||
// the previous source version in the `Source` field and records the
|
||||
// entry in `Binary-Only-Changes`, but distributes NO source files:
|
||||
// the previous `.dsc` and its tarballs already sit in the archive,
|
||||
// and are not re-uploaded even when present next to the tree.
|
||||
if let Some(prev) = &previous_entry {
|
||||
source_display = format!("{} ({})", entry.source, prev.version.full());
|
||||
binary_only_changes = Some(format!(
|
||||
"{}\n\n -- {} <{}> {}",
|
||||
entry.changes_field, entry.maintainer_name, entry.maintainer_email, entry.date_raw
|
||||
));
|
||||
let dsc_name = format!("{}_{}.dsc", entry.source, prev.version.no_epoch());
|
||||
let dsc_path = upload_dir.join(&dsc_name);
|
||||
if ctx.exists(&dsc_path)? {
|
||||
include_dsc_artifacts(ctx, upload_dir, &dsc_name, &mut checksums)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,6 +291,7 @@ pub fn generate_binary_metadata(
|
||||
date: entry.date_raw.clone(),
|
||||
source: source_display,
|
||||
binaries,
|
||||
binary_only: entry.binary_only,
|
||||
built_for_profiles: opts.profiles.clone(),
|
||||
architecture: arch_values.join(" "),
|
||||
version: entry.version.full(),
|
||||
@@ -400,90 +395,6 @@ fn hashes_in_context(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Pull the `.dsc` checksums (and its referenced tarballs) into the
|
||||
/// checksum registry, mirroring how binary-NMU uploads redistribute the
|
||||
/// previous source.
|
||||
fn include_dsc_artifacts(
|
||||
ctx: &Arc<Context>,
|
||||
upload_dir: &Path,
|
||||
dsc_name: &str,
|
||||
checksums: &mut FileChecksums,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let dsc_content = ctx.read_file(&upload_dir.join(dsc_name))?;
|
||||
let para = crate::debian::control::parse_paragraphs(
|
||||
crate::debian::control::strip_clearsigned_armour(&dsc_content),
|
||||
)
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or_else(|| format!("'{dsc_name}' is empty"))?;
|
||||
|
||||
// Names and partial checksums are filled from the very same validated
|
||||
// lines, so a listed name can never miss its checksum entry.
|
||||
// Distribution order follows the Checksums fields (Checksums-Sha1 then
|
||||
// Checksums-Sha256), like dpkg-genchanges; the `Files` field only
|
||||
// supplements the md5 digests.
|
||||
let mut names: Vec<String> = Vec::new();
|
||||
let mut partials: BTreeMap<String, super::PartialChecksum> = BTreeMap::new();
|
||||
for field in ["Checksums-Sha1", "Checksums-Sha256", "Files"] {
|
||||
let Some(value) = para.get(field) else {
|
||||
continue;
|
||||
};
|
||||
for cl in parse_checksum_field(field, value)
|
||||
.map_err(|e| format!("cannot parse '{dsc_name}': {e}"))?
|
||||
{
|
||||
let slot = partials.entry(cl.name.clone()).or_default();
|
||||
match field {
|
||||
"Checksums-Sha1" => slot.sha1 = Some(cl.digest),
|
||||
"Checksums-Sha256" => slot.sha256 = Some(cl.digest),
|
||||
_ => slot.md5 = Some(cl.digest),
|
||||
}
|
||||
slot.size = Some(cl.size);
|
||||
if field != "Files" && !names.contains(&cl.name) {
|
||||
names.push(cl.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The .dsc itself is hashed fresh (it may be signed/rewritten); the
|
||||
// tarballs reuse the .dsc-recorded digests, like dpkg-genchanges does.
|
||||
let dsc_hashes =
|
||||
hashes_in_context(ctx, upload_dir, std::slice::from_ref(&dsc_name.to_string()))?;
|
||||
if let Some(h) = dsc_hashes.get(dsc_name) {
|
||||
checksums.insert_entry(
|
||||
dsc_name,
|
||||
ChecksumEntry {
|
||||
size: h.size,
|
||||
md5: h.md5.clone(),
|
||||
sha1: h.sha1.clone(),
|
||||
sha256: h.sha256.clone(),
|
||||
// No SHA-512 digest available (see above).
|
||||
sha512: String::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
for name in &names {
|
||||
if name == dsc_name {
|
||||
continue;
|
||||
}
|
||||
let p = partials
|
||||
.get(name)
|
||||
.ok_or_else(|| format!("file '{name}' listed in '{dsc_name}' has no checksum entry"))?;
|
||||
checksums.insert_entry(
|
||||
name,
|
||||
ChecksumEntry {
|
||||
size: p.size.unwrap_or(0),
|
||||
md5: p.md5.clone().unwrap_or_default(),
|
||||
sha1: p.sha1.clone().unwrap_or_default(),
|
||||
sha256: p.sha256.clone().unwrap_or_default(),
|
||||
// The `.dsc` records no SHA-512 (dpkg only writes
|
||||
// sha1/sha256 there).
|
||||
sha512: String::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -521,109 +432,11 @@ mod tests {
|
||||
assert!(!environment.contains("DEBIAN_FRONTEND"), "{environment}");
|
||||
}
|
||||
|
||||
/// A minimal previous-version `.dsc` with a 3-column Checksums-Sha1
|
||||
/// field, a 4-column Checksums-Sha256 line and a 3-column `Files`.
|
||||
/// Regression: the old code filled `names` from any line with a third
|
||||
/// column but `partials` only from exactly-3-column lines, so the
|
||||
/// "bogus" name landed in `names` alone and `&partials["bogus"]`
|
||||
/// panicked. It must produce a build error instead.
|
||||
#[test]
|
||||
fn dsc_four_column_checksum_line_errors_instead_of_panicking() {
|
||||
let dsc_name = "hello_1.0-1.dsc";
|
||||
let dsc = "\
|
||||
Format: 3.0 (native)
|
||||
Source: hello
|
||||
Binary: hello
|
||||
Architecture: any
|
||||
Version: 1.0-1
|
||||
Maintainer: A B <a@b.c>
|
||||
Checksums-Sha1:
|
||||
aaa111 12 hello_1.0.orig.tar.xz
|
||||
Checksums-Sha256:
|
||||
bbb222 12 bogus hello_1.0-1.debian.tar.xz
|
||||
Files:
|
||||
ddd333 12 hello_1.0.orig.tar.xz
|
||||
";
|
||||
let base = tempfile::tempdir().expect("tempdir");
|
||||
std::fs::write(base.path().join(dsc_name), dsc).expect("write dsc");
|
||||
|
||||
let ctx = Arc::new(
|
||||
crate::context::Context::new(crate::context::ContextConfig::Local).expect("context"),
|
||||
);
|
||||
let mut checksums = FileChecksums::new();
|
||||
let err = include_dsc_artifacts(&ctx, base.path(), dsc_name, &mut checksums)
|
||||
.expect_err("malformed Checksums-Sha256 line must fail the build");
|
||||
let err = err.to_string();
|
||||
assert!(err.contains("Checksums-Sha256"), "{err}");
|
||||
assert!(err.contains("hello_1.0-1.debian.tar.xz"), "{err}");
|
||||
}
|
||||
|
||||
/// Happy path: tarball entries are assembled from the Checksums fields
|
||||
/// (sha1/sha256) and merged with the legacy 5-column `Files` md5, in
|
||||
/// Checksums-Sha1 order, with the `.dsc` itself hashed fresh first.
|
||||
#[test]
|
||||
fn include_dsc_artifacts_merges_legacy_files_layout() {
|
||||
let dsc_name = "hello_1.0-1.dsc";
|
||||
let tarball = "hello_1.0.orig.tar.xz";
|
||||
let dsc = "\
|
||||
Format: 3.0 (quilt)
|
||||
Source: hello
|
||||
Binary: hello
|
||||
Architecture: any
|
||||
Version: 1.0-1
|
||||
Maintainer: A B <a@b.c>
|
||||
Checksums-Sha1:
|
||||
aaa111 12 hello_1.0.orig.tar.xz
|
||||
Checksums-Sha256:
|
||||
bbb222 12 hello_1.0.orig.tar.xz
|
||||
Files:
|
||||
ddd333 12 devel optional hello_1.0.orig.tar.xz
|
||||
";
|
||||
let base = tempfile::tempdir().expect("tempdir");
|
||||
std::fs::write(base.path().join(dsc_name), dsc).expect("write dsc");
|
||||
std::fs::write(base.path().join(tarball), "tarball bytes").expect("write tarball");
|
||||
|
||||
let ctx = Arc::new(
|
||||
crate::context::Context::new(crate::context::ContextConfig::Local).expect("context"),
|
||||
);
|
||||
let mut checksums = FileChecksums::new();
|
||||
include_dsc_artifacts(&ctx, base.path(), dsc_name, &mut checksums)
|
||||
.expect("valid dsc must parse");
|
||||
|
||||
let collected: Vec<(String, crate::debian::ChecksumEntry)> = checksums
|
||||
.iter()
|
||||
.map(|(k, e)| (k.clone(), e.clone()))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
collected
|
||||
.iter()
|
||||
.map(|(k, _)| k.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![dsc_name, tarball],
|
||||
".dsc first, then Checksums-Sha1 order"
|
||||
);
|
||||
|
||||
// The .dsc is hashed fresh from disk.
|
||||
let dsc_entry = &collected[0].1;
|
||||
assert_eq!(dsc_entry.size, dsc.len() as u64);
|
||||
assert_eq!(dsc_entry.md5.len(), 32);
|
||||
assert_eq!(dsc_entry.sha1.len(), 40);
|
||||
assert_eq!(dsc_entry.sha256.len(), 64);
|
||||
|
||||
// The tarball reuses the .dsc-recorded digests, including the
|
||||
// legacy 5-column `Files` md5 (section/priority skipped).
|
||||
let tar_entry = &collected[1].1;
|
||||
assert_eq!(tar_entry.size, 12);
|
||||
assert_eq!(tar_entry.md5, "ddd333");
|
||||
assert_eq!(tar_entry.sha1, "aaa111");
|
||||
assert_eq!(tar_entry.sha256, "bbb222");
|
||||
}
|
||||
|
||||
/// A binary-only (binNMU) build whose changelog cannot yield the
|
||||
/// previous entry (malformed second header, unbalanced parenthesis) must
|
||||
/// fail the metadata generation with a diagnostic naming the problem,
|
||||
/// instead of silently emitting a plain `Source:` `.changes` with no
|
||||
/// `Binary-Only-Changes` and no redistributed previous `.dsc`.
|
||||
/// `Binary-Only-Changes` and no previous-version reference.
|
||||
#[test]
|
||||
fn binary_only_prev_version_parse_failure_errors_instead_of_wrong_metadata() {
|
||||
let changelog = "\
|
||||
@@ -739,4 +552,104 @@ Description: test package
|
||||
#[cfg(unix)]
|
||||
assert!(err.contains("Permission denied"), "{err}");
|
||||
}
|
||||
|
||||
/// A binary-only (binNMU) build references the previous source version
|
||||
/// (`Source: pkg (prev)`, `Binary-Only-Changes`) but must NOT
|
||||
/// redistribute any source file: like dpkg-genchanges/genbuildinfo, the
|
||||
/// previous `.dsc` and its tarballs stay out of both documents even when
|
||||
/// they exist next to the artifacts.
|
||||
#[test]
|
||||
fn binary_only_metadata_references_previous_source_without_redistributing_it() {
|
||||
let changelog = "\
|
||||
hello (1.0-1+b1) unstable; urgency=medium, binary-only=yes
|
||||
|
||||
* Binary-only rebuild.
|
||||
|
||||
-- A B <a@b.c> Mon, 01 Jan 2024 00:00:00 +0000
|
||||
|
||||
hello (1.0-1) unstable; urgency=medium
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- A B <a@b.c> Sun, 31 Dec 2023 00:00:00 +0000
|
||||
";
|
||||
let control = "\
|
||||
Source: hello
|
||||
Section: devel
|
||||
Priority: optional
|
||||
Maintainer: A B <a@b.c>
|
||||
|
||||
Package: hello
|
||||
Architecture: all
|
||||
Description: test package
|
||||
";
|
||||
let base = tempfile::tempdir().expect("tempdir");
|
||||
let tree = base.path().join("hello-1.0");
|
||||
std::fs::create_dir_all(tree.join("debian")).expect("mkdir tree");
|
||||
std::fs::write(tree.join("debian/changelog"), changelog).expect("write changelog");
|
||||
std::fs::write(tree.join("debian/control"), control).expect("write control");
|
||||
std::fs::write(
|
||||
tree.join("debian/files"),
|
||||
"hello_1.0-1+b1_all.deb devel optional\n",
|
||||
)
|
||||
.expect("write files");
|
||||
std::fs::write(base.path().join("hello_1.0-1+b1_all.deb"), "deb payload")
|
||||
.expect("write deb");
|
||||
|
||||
// The trap: the previous source artifacts sit right next to the
|
||||
// binaries, as they would after a source build. dpkg does not
|
||||
// redistribute them for a binary-only upload, and neither must we.
|
||||
std::fs::write(
|
||||
base.path().join("hello_1.0-1.dsc"),
|
||||
"Format: 3.0 (quilt)\nSource: hello\nBinary: hello\nArchitecture: any\nVersion: \
|
||||
1.0-1\nMaintainer: A B <a@b.c>\nChecksums-Sha1:\n aaa111 12 \
|
||||
hello_1.0.orig.tar.xz\n",
|
||||
)
|
||||
.expect("write previous dsc");
|
||||
std::fs::write(base.path().join("hello_1.0.orig.tar.xz"), "tarball bytes")
|
||||
.expect("write previous tarball");
|
||||
|
||||
let ctx = Arc::new(
|
||||
crate::context::Context::new(crate::context::ContextConfig::Local).expect("context"),
|
||||
);
|
||||
let opts = BinaryMetadataOptions {
|
||||
profiles: Vec::new(),
|
||||
vendor: "debian".to_string(),
|
||||
exported_env: BTreeMap::new(),
|
||||
build_arch: "amd64".to_string(),
|
||||
host_arch: "amd64".to_string(),
|
||||
};
|
||||
let (buildinfo_path, changes_path) =
|
||||
generate_binary_metadata(&ctx, &tree, base.path(), &opts)
|
||||
.expect("binNMU metadata generation must succeed");
|
||||
|
||||
let changes = std::fs::read_to_string(&changes_path).expect("read changes");
|
||||
let buildinfo = std::fs::read_to_string(&buildinfo_path).expect("read buildinfo");
|
||||
|
||||
// The previous version is referenced textually.
|
||||
assert!(
|
||||
changes.contains("Source: hello (1.0-1)"),
|
||||
"changes must reference the previous version: {changes}"
|
||||
);
|
||||
assert!(
|
||||
buildinfo.contains("Binary-Only-Changes"),
|
||||
"buildinfo must record the binary-only entry: {buildinfo}"
|
||||
);
|
||||
// ... but no source file is distributed, on either side.
|
||||
for (doc, text) in [("changes", &changes), ("buildinfo", &buildinfo)] {
|
||||
assert!(
|
||||
!text.contains("hello_1.0-1.dsc"),
|
||||
"{doc} must not redistribute the previous .dsc: {text}"
|
||||
);
|
||||
assert!(
|
||||
!text.contains("hello_1.0.orig.tar.xz"),
|
||||
"{doc} must not redistribute the previous tarball: {text}"
|
||||
);
|
||||
}
|
||||
// The distributed set is exactly the binary artifacts + buildinfo.
|
||||
assert!(
|
||||
changes.contains("hello_1.0-1+b1_all.deb") && changes.contains(".buildinfo"),
|
||||
"changes must distribute the deb and the buildinfo: {changes}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,9 @@ pub struct ChangesInput {
|
||||
pub source: String,
|
||||
/// Sorted binary package names with artifacts (empty for source-only).
|
||||
pub binaries: Vec<String>,
|
||||
/// Whether the changelog entry is a binary-only (binNMU) upload
|
||||
/// (`Binary-Only: yes` field).
|
||||
pub binary_only: bool,
|
||||
/// Active build profiles (`Built-For-Profiles`); omitted when empty.
|
||||
pub built_for_profiles: Vec<String>,
|
||||
/// `Architecture` field value in encounter order (e.g. `source`,
|
||||
@@ -142,6 +145,9 @@ pub fn render_changes(input: &ChangesInput) -> Paragraph {
|
||||
let joined = input.binaries.join(" ");
|
||||
p.set("Binary", &wrap_long(&joined));
|
||||
}
|
||||
if input.binary_only {
|
||||
p.set("Binary-Only", "yes");
|
||||
}
|
||||
if !input.built_for_profiles.is_empty() {
|
||||
p.set("Built-For-Profiles", &input.built_for_profiles.join(" "));
|
||||
}
|
||||
@@ -329,6 +335,7 @@ mod tests {
|
||||
date: "Sat, 22 Aug 2026 10:00:00 +0000".to_string(),
|
||||
source: "pkg".to_string(),
|
||||
binaries: vec![],
|
||||
binary_only: false,
|
||||
built_for_profiles: vec![],
|
||||
architecture: "source".to_string(),
|
||||
version: "1.0".to_string(),
|
||||
|
||||
+75
-16
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user