build: only redistribute the orig tarball on new upstream (-si)
dpkg-genchanges includes the upstream tarballs in the .changes only when the upload brings a new upstream: no previous changelog entry, a changed upstream version or a renamed source. On a plain revision bump the tarball already sits in the archive, and dpkg strips it (and its .asc) from the distribution set. pkh's native source pipeline listed every .dsc-referenced tarball unconditionally, making every upload re-ship the orig. Implement the dpkg source styles as --orig auto|always|never (auto being the -si default; always/never are -sa/-sd), stripping the tarballs out of the changes, buildinfo-free checksum set and artifact list like dpkg, with the explicit 'never' ignored for native packages. Comparison uses the epoch-less upstream version, exactly like dpkg's version(). Differential tests against real dpkg cover revision bumps, new upstream versions and both forced styles.
This commit is contained in:
+152
-33
@@ -27,6 +27,20 @@ use crate::debian::{
|
||||
use crate::ui::deb::DebUi;
|
||||
use crate::ui::logfmt::{DpkgSourceClassifier, GenericClassifier};
|
||||
|
||||
/// Whether the upload distributes the upstream orig tarballs (`--orig`),
|
||||
/// mirroring the `dpkg-genchanges` source styles.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum OrigSourceMode {
|
||||
/// Include them only when the upstream version changed since the
|
||||
/// previous changelog entry (`-si`, dpkg's default).
|
||||
#[default]
|
||||
Auto,
|
||||
/// Always include them (`-sa`).
|
||||
Always,
|
||||
/// Never include them (`-sd`).
|
||||
Never,
|
||||
}
|
||||
|
||||
/// Options for a native source-package build.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SourceBuildOptions {
|
||||
@@ -39,6 +53,10 @@ pub struct SourceBuildOptions {
|
||||
/// build (`-D`). `dpkg-buildpackage` skips `dpkg-checkbuilddeps`
|
||||
/// entirely for source-only builds unless forced.
|
||||
pub force_dep_check: bool,
|
||||
/// Whether the upload distributes the upstream orig tarballs
|
||||
/// (`--orig`). Defaults to [`OrigSourceMode::Auto`], like
|
||||
/// `dpkg-genchanges`' `-si`.
|
||||
pub orig_source: OrigSourceMode,
|
||||
}
|
||||
|
||||
/// Artifacts produced by a successful source build.
|
||||
@@ -70,6 +88,7 @@ pub struct SourceBuildOutput {
|
||||
/// exactly once.
|
||||
pub fn build_source_package(
|
||||
cwd: Option<&Path>,
|
||||
opts: SourceBuildOptions,
|
||||
ui: Option<Arc<DebUi>>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// Default to the process's current working directory, resolved to an
|
||||
@@ -81,10 +100,10 @@ pub fn build_source_package(
|
||||
None => std::env::current_dir()
|
||||
.map_err(|e| format!("cannot determine the current working directory: {e}"))?,
|
||||
};
|
||||
let output = match run_source_build(&cwd, &SourceBuildOptions::default(), ui.clone()) {
|
||||
let output = match run_source_build(&cwd, &opts, ui.clone()) {
|
||||
Ok(output) => output,
|
||||
Err(e) if e.downcast_ref::<VendorDriftError>().is_some() => {
|
||||
return retry_after_revendor(&cwd, ui, e);
|
||||
return retry_after_revendor(&cwd, ui, opts, e);
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(u) = &ui {
|
||||
@@ -133,6 +152,7 @@ pub fn build_source_package(
|
||||
fn retry_after_revendor(
|
||||
cwd: &Path,
|
||||
ui: Option<Arc<DebUi>>,
|
||||
opts: SourceBuildOptions,
|
||||
original: Box<dyn Error>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let interactive = std::io::stdin().is_terminal() && std::io::stdout().is_terminal();
|
||||
@@ -197,8 +217,8 @@ fn retry_after_revendor(
|
||||
}
|
||||
crate::new::orig::create_vendor_component(cwd, &entry.source, uversion)?;
|
||||
|
||||
// 3. One retry.
|
||||
run_source_build(cwd, &SourceBuildOptions::default(), ui).map(|_| ())
|
||||
// 3. One retry, with the options of the original attempt.
|
||||
run_source_build(cwd, &opts, ui).map(|_| ())
|
||||
}
|
||||
|
||||
/// Run the full native source-build pipeline in `cwd`.
|
||||
@@ -498,6 +518,13 @@ pub fn run_source_build(
|
||||
.next()
|
||||
.ok_or_else(|| format!("'{}' is empty", ref_dsc_path.display()))?;
|
||||
|
||||
// Whether the upload redistributes the upstream tarballs (dpkg
|
||||
// -sa/-si/-sd source styles). Stripping only applies to a split source
|
||||
// package — a native one has no orig tarball, and an explicit `never`
|
||||
// for one is only a warning, like dpkg-genchanges with -sd.
|
||||
let include_orig =
|
||||
changes::include_orig_tarball(opts.orig_source, &entry, previous_entry.as_ref());
|
||||
|
||||
let mut tarball_paths = Vec::new();
|
||||
let mut dsc_file_names: Vec<String> = Vec::new();
|
||||
let mut dsc_files: HashMap<String, PartialChecksum> = HashMap::new();
|
||||
@@ -523,10 +550,38 @@ pub fn run_source_build(
|
||||
slot.size = Some(cl.size);
|
||||
}
|
||||
}
|
||||
|
||||
let has_debian_part = dsc_file_names
|
||||
.iter()
|
||||
.any(|n| changes::is_debian_tarball_or_diff(n));
|
||||
let strip_origs = !include_orig && has_debian_part;
|
||||
if opts.orig_source == OrigSourceMode::Never && !has_debian_part {
|
||||
log::warn!("ignoring --orig never for a native Debian package");
|
||||
}
|
||||
if let Some(u) = &ui {
|
||||
u.progress_message(if strip_origs {
|
||||
"Not including original source code in upload"
|
||||
} else {
|
||||
"Including full source code in upload"
|
||||
});
|
||||
}
|
||||
// Stripped orig tarballs (and their detached .asc signatures) are not
|
||||
// distributed at all: not hashed, not required on disk, like
|
||||
// dpkg-genchanges.
|
||||
let is_stripped = |name: &str| {
|
||||
strip_origs
|
||||
&& (changes::is_orig_tarball(name)
|
||||
|| (name.ends_with(".asc")
|
||||
&& changes::is_orig_tarball(name.strip_suffix(".asc").unwrap_or(name))))
|
||||
};
|
||||
|
||||
for name in &dsc_file_names {
|
||||
if name == &ref_dsc_name {
|
||||
continue; // already computed directly above
|
||||
}
|
||||
if is_stripped(name) {
|
||||
continue;
|
||||
}
|
||||
let path = parent.join(name);
|
||||
if !path.exists() {
|
||||
return Err(format!(
|
||||
@@ -572,7 +627,7 @@ pub fn run_source_build(
|
||||
ctrl.priority(),
|
||||
));
|
||||
for name in &dsc_file_names {
|
||||
if name != &ref_dsc_name {
|
||||
if name != &ref_dsc_name && !is_stripped(name) {
|
||||
changes_files.add(FilesEntry::new(name, ctrl.section(), ctrl.priority()));
|
||||
}
|
||||
}
|
||||
@@ -1209,8 +1264,8 @@ mod differential_tests {
|
||||
body: &'static [&'static str],
|
||||
patches: &'static [&'static str],
|
||||
extra_source_fields: &'static [(&'static str, &'static str)],
|
||||
/// Two-entry changelog (previous entry) for binNMU cases.
|
||||
with_previous_entry: bool,
|
||||
/// Version of the previous changelog entry, when the fixture has one.
|
||||
previous_version: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl FixtureSpec {
|
||||
@@ -1227,7 +1282,7 @@ mod differential_tests {
|
||||
body: &["* Something changed."],
|
||||
patches: &[],
|
||||
extra_source_fields: &[],
|
||||
with_previous_entry: false,
|
||||
previous_version: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1250,13 +1305,7 @@ mod differential_tests {
|
||||
out.push('\n');
|
||||
}
|
||||
out.push_str(&format!("\n -- {MAINTAINER} {DATE}\n"));
|
||||
if self.with_previous_entry {
|
||||
let prev = match self.version.split_once(':') {
|
||||
Some((_, r)) => r.to_string(),
|
||||
None => self.version.to_string(),
|
||||
};
|
||||
// Turn "1.0-1+b1" into "1.0-1" for the previous entry.
|
||||
let prev = prev.rsplit_once('+').map(|(p, _)| p).unwrap_or(&prev);
|
||||
if let Some(prev) = self.previous_version {
|
||||
out.push_str(&format!(
|
||||
"\n{p} ({pv}) {d}; urgency={u}\n\n * Initial release.\n\n -- {MAINTAINER} {DATE}\n",
|
||||
p = self.name,
|
||||
@@ -1382,16 +1431,12 @@ mod differential_tests {
|
||||
);
|
||||
}
|
||||
|
||||
fn run_dpkg(tree: &Path) {
|
||||
fn run_dpkg(tree: &Path, source_style: &[&str]) {
|
||||
let status = crate::test_support::run_logged(
|
||||
Command::new("dpkg-buildpackage").current_dir(tree).args([
|
||||
"-S",
|
||||
"-I",
|
||||
"-i",
|
||||
"-nc",
|
||||
"-d",
|
||||
"--no-sign",
|
||||
]),
|
||||
Command::new("dpkg-buildpackage")
|
||||
.current_dir(tree)
|
||||
.args(["-S", "-I", "-i", "-nc", "-d", "--no-sign"])
|
||||
.args(source_style),
|
||||
)
|
||||
.expect("failed to run dpkg-buildpackage (is dpkg-dev installed?)");
|
||||
assert!(status.success(), "dpkg-buildpackage failed");
|
||||
@@ -1480,8 +1525,10 @@ mod differential_tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build `src_tree` with both implementations and compare all artifacts.
|
||||
fn differential_on_tree(src_tree: &Path) {
|
||||
/// Build `src_tree` with both implementations and compare all artifacts,
|
||||
/// the native side running with `opts` (dpkg receives the matching
|
||||
/// source style so both sides make the same orig-tarball decision).
|
||||
fn differential_on_tree(src_tree: &Path, opts: &SourceBuildOptions) {
|
||||
let src_parent = src_tree.parent().expect("tree has a parent directory");
|
||||
let tree_name = src_tree.file_name().expect("tree has a name").to_owned();
|
||||
|
||||
@@ -1508,9 +1555,13 @@ mod differential_tests {
|
||||
let golden_tree = golden_root.join(&tree_name);
|
||||
let ours_tree = ours_root.join(&tree_name);
|
||||
|
||||
run_dpkg(&golden_tree);
|
||||
run_source_build(&ours_tree, &SourceBuildOptions::default(), None)
|
||||
.expect("native source pipeline failed");
|
||||
let source_style: &[&str] = match opts.orig_source {
|
||||
OrigSourceMode::Auto => &[],
|
||||
OrigSourceMode::Always => &["-sa"],
|
||||
OrigSourceMode::Never => &["-sd"],
|
||||
};
|
||||
run_dpkg(&golden_tree, source_style);
|
||||
run_source_build(&ours_tree, opts, None).expect("native source pipeline failed");
|
||||
|
||||
let entry =
|
||||
crate::debian::parse_changelog_entry(&ours_tree.join("debian/changelog")).unwrap();
|
||||
@@ -1540,7 +1591,7 @@ mod differential_tests {
|
||||
fn differential_case(spec: &FixtureSpec) {
|
||||
let base = tempfile::tempdir().expect("tempdir");
|
||||
let tree = write_fixture(base.path(), spec);
|
||||
differential_on_tree(&tree);
|
||||
differential_on_tree(&tree, &SourceBuildOptions::default());
|
||||
}
|
||||
|
||||
/// Differential check of [`crate::debian::arch::arch_env`] against real
|
||||
@@ -1986,7 +2037,7 @@ Provides: virtual-thing (= 2.0), plain-virtual
|
||||
fn diff_binmu_binary_only() {
|
||||
let mut spec = FixtureSpec::new("pkh-diff-i", "1.0-1+b1", "unstable");
|
||||
spec.body = &["* Binary-only rebuild."];
|
||||
spec.with_previous_entry = true;
|
||||
spec.previous_version = Some("1.0-1");
|
||||
|
||||
// Binary-only metadata references the previous version's .dsc, which
|
||||
// must already exist next to the package tree.
|
||||
@@ -2001,7 +2052,7 @@ Provides: virtual-thing (= 2.0), plain-virtual
|
||||
prev_dsc,
|
||||
)
|
||||
.expect("write previous dsc");
|
||||
differential_on_tree(&tree);
|
||||
differential_on_tree(&tree, &SourceBuildOptions::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2029,6 +2080,74 @@ Provides: virtual-thing (= 2.0), plain-virtual
|
||||
differential_case(&spec);
|
||||
}
|
||||
|
||||
/// The default dpkg-genchanges source style (-si): a revision bump
|
||||
/// within the same upstream version must NOT redistribute the orig
|
||||
/// tarball in the `.changes`.
|
||||
#[test]
|
||||
fn diff_quilt_revision_bump_excludes_orig() {
|
||||
let mut spec = FixtureSpec::new("pkh-diff-n", "1.4-2", "unstable");
|
||||
spec.format = "3.0 (quilt)";
|
||||
spec.patches = &[
|
||||
"--- a/hello.txt\n+++ b/hello.txt\n@@ -1 +1 @@\n-upstream content v1\n+patched by debian\n",
|
||||
];
|
||||
spec.previous_version = Some("1.4-1");
|
||||
differential_case(&spec);
|
||||
}
|
||||
|
||||
/// Conversely, a new upstream version must redistribute the orig
|
||||
/// tarball, even though a previous entry exists.
|
||||
#[test]
|
||||
fn diff_quilt_new_upstream_includes_orig() {
|
||||
let mut spec = FixtureSpec::new("pkh-diff-o", "2.0-1", "unstable");
|
||||
spec.format = "3.0 (quilt)";
|
||||
spec.patches = &[
|
||||
"--- a/hello.txt\n+++ b/hello.txt\n@@ -1 +1 @@\n-upstream content v1\n+patched by debian\n",
|
||||
];
|
||||
spec.previous_version = Some("1.4-2");
|
||||
differential_case(&spec);
|
||||
}
|
||||
|
||||
/// `--orig always` (-sa) forces the tarball into a same-upstream
|
||||
/// revision bump upload.
|
||||
#[test]
|
||||
fn diff_orig_always_forces_inclusion() {
|
||||
let mut spec = FixtureSpec::new("pkh-diff-p", "1.4-2", "unstable");
|
||||
spec.format = "3.0 (quilt)";
|
||||
spec.patches = &[
|
||||
"--- a/hello.txt\n+++ b/hello.txt\n@@ -1 +1 @@\n-upstream content v1\n+patched by debian\n",
|
||||
];
|
||||
spec.previous_version = Some("1.4-1");
|
||||
let base = tempfile::tempdir().expect("tempdir");
|
||||
let tree = write_fixture(base.path(), &spec);
|
||||
differential_on_tree(
|
||||
&tree,
|
||||
&SourceBuildOptions {
|
||||
orig_source: OrigSourceMode::Always,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// `--orig never` (-sd) forces the tarball out of a new-upstream upload.
|
||||
#[test]
|
||||
fn diff_orig_never_forces_exclusion() {
|
||||
let mut spec = FixtureSpec::new("pkh-diff-q", "2.0-1", "unstable");
|
||||
spec.format = "3.0 (quilt)";
|
||||
spec.patches = &[
|
||||
"--- a/hello.txt\n+++ b/hello.txt\n@@ -1 +1 @@\n-upstream content v1\n+patched by debian\n",
|
||||
];
|
||||
spec.previous_version = Some("1.4-2");
|
||||
let base = tempfile::tempdir().expect("tempdir");
|
||||
let tree = write_fixture(base.path(), &spec);
|
||||
differential_on_tree(
|
||||
&tree,
|
||||
&SourceBuildOptions {
|
||||
orig_source: OrigSourceMode::Never,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Differential check of a single real archive package: pull it with
|
||||
/// pkh's own [`crate::pull`] (archive download mode) from `dist`
|
||||
/// (optionally `series`), then compare artifacts produced by real
|
||||
@@ -2072,7 +2191,7 @@ Provides: virtual-thing (= 2.0), plain-virtual
|
||||
dist,
|
||||
series.unwrap_or("latest")
|
||||
);
|
||||
differential_on_tree(&tree);
|
||||
differential_on_tree(&tree, &SourceBuildOptions::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user