new: add upstream-aware orig tarball origins and the orig-vendor component

This commit is contained in:
2026-09-17 01:31:35 +02:00
parent 8e06b2074d
commit 77420e723a
19 changed files with 3270 additions and 184 deletions
+81 -22
View File
@@ -12,12 +12,13 @@ use chrono::Datelike;
use tar::Builder;
use xz2::write::XzEncoder;
use super::options::NewOptions;
use super::options::{NewOptions, SourceFormat};
use super::templates::{OutputFile, Template};
/// `3.0 (quilt)` source format, the pkh new default.
/// `3.0 (quilt)` source format, the default when packaging an existing
/// project.
pub const SOURCE_FORMAT_QUILT: &str = "3.0 (quilt)";
/// `3.0 (native)` source format, selected by `--native`.
/// `3.0 (native)` source format, the default for a fresh skeleton.
pub const SOURCE_FORMAT_NATIVE: &str = "3.0 (native)";
/// The three source formats pkh knows how to build.
@@ -59,7 +60,7 @@ pub fn files(opts: &NewOptions, template: &dyn Template) -> Vec<OutputFile> {
copyright(opts),
debian_gitignore(opts),
];
if !opts.native {
if opts.source_format == SourceFormat::Quilt {
files.push(local_options());
}
if opts.autopkgtest {
@@ -94,19 +95,13 @@ fn autopkgtest_smoke(opts: &NewOptions) -> OutputFile {
)
}
/// `debian/source/format`: `3.0 (quilt)` by default, `3.0 (native)` with
/// `--native`.
/// `debian/source/format`: `3.0 (native)` for a skeleton by default,
/// `3.0 (quilt)` for an existing project; either can be forced with
/// `--native` / `--quilt`.
fn source_format(opts: &NewOptions) -> OutputFile {
OutputFile::new(
"debian/source/format",
format!(
"{}\n",
if opts.native {
SOURCE_FORMAT_NATIVE
} else {
SOURCE_FORMAT_QUILT
}
),
format!("{}\n", opts.source_format.deb_string()),
)
}
@@ -369,6 +364,20 @@ pub fn create_orig_tarball(
tree: &Path,
name: &str,
upstream_version: &str,
) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
create_orig_tarball_excluding(tree, name, upstream_version, false)
}
/// [`create_orig_tarball`] with the generated `vendor/` directory of a
/// vendored rust package excluded from the snapshot: its contents travel in
/// the separate `<name>_<uver>.orig-vendor.tar.xz` component instead (see
/// [`super::orig`]), so they can be regenerated independently of the
/// upstream sources.
pub fn create_orig_tarball_excluding(
tree: &Path,
name: &str,
upstream_version: &str,
exclude_vendor: bool,
) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
let tarball_path = orig_tarball_path(tree, name, upstream_version).ok_or_else(|| {
format!(
@@ -393,7 +402,8 @@ pub fn create_orig_tarball(
let prefix = format!("{name}-{upstream_version}");
// The single top-level directory dpkg-source expects.
builder.append_dir(&prefix, tree)?;
append_tree(&mut builder, tree, &prefix, 0)?;
let top_excludes: &[&str] = if exclude_vendor { &["vendor"] } else { &[] };
append_tree(&mut builder, tree, &prefix, 0, ORIG_EXCLUDE, top_excludes)?;
builder
.finish()
@@ -407,12 +417,15 @@ pub fn create_orig_tarball(
}
/// Recursively append `dir` to the archive under `archive_path`, skipping
/// the [`ORIG_EXCLUDE`] names and non-regular files.
fn append_tree(
/// non-regular files, the names of `excludes` at any depth, the `debian/`
/// directory and the names of `top_excludes` at the top level (depth 0).
pub(crate) fn append_tree(
builder: &mut Builder<XzEncoder<std::fs::File>>,
dir: &Path,
archive_path: &str,
depth: usize,
excludes: &[&str],
top_excludes: &[&str],
) -> Result<(), Box<dyn std::error::Error>> {
let mut entries: Vec<std::fs::DirEntry> = std::fs::read_dir(dir)?.collect::<Result<_, _>>()?;
entries.sort_by_key(|entry| entry.file_name());
@@ -422,10 +435,10 @@ fn append_tree(
let file_name = entry.file_name();
let name = file_name.to_string_lossy().into_owned();
if depth == 0 && name == "debian" {
if depth == 0 && (name == "debian" || top_excludes.contains(&name.as_str())) {
continue;
}
if ORIG_EXCLUDE.contains(&name.as_str()) {
if excludes.contains(&name.as_str()) {
continue;
}
@@ -434,7 +447,14 @@ fn append_tree(
.map_err(|e| format!("cannot stat '{}': {}", path.display(), e))?;
if metadata.is_dir() {
builder.append_dir(&entry_archive_path, &path)?;
append_tree(builder, &path, &entry_archive_path, depth + 1)?;
append_tree(
builder,
&path,
&entry_archive_path,
depth + 1,
excludes,
top_excludes,
)?;
} else if metadata.is_file() {
// The mode (including the exec bit) travels through the header.
let mut header = tar::Header::new_gnu();
@@ -502,7 +522,8 @@ mod tests {
series: "resolute".into(),
release: false,
depends: Vec::new(),
native: false,
source_format: SourceFormat::Quilt,
orig: None,
git: true,
autopkgtest: false,
pkg_config: false,
@@ -528,7 +549,7 @@ mod tests {
);
let native = NewOptions {
native: true,
source_format: SourceFormat::Native,
..opts()
};
let files = super::files(
@@ -799,6 +820,44 @@ mod tests {
assert!(err.to_string().contains("already exists"));
}
/// The vendored-rust variant excludes the top-level `vendor/` (it
/// travels in the orig-vendor component) but keeps unrelated trees.
#[test]
fn orig_tarball_vendor_exclusion() {
let dir = tempfile::tempdir().unwrap();
let tree = dir.path().join("mytool");
std::fs::create_dir_all(tree.join("vendor/serde/src")).unwrap();
std::fs::create_dir_all(tree.join("src/vendor")).unwrap();
std::fs::write(tree.join("vendor/serde/src/lib.rs"), "code").unwrap();
std::fs::write(tree.join("src/vendor/mod.rs"), "code").unwrap();
std::fs::write(tree.join("Cargo.toml"), "[package]").unwrap();
let tarball = create_orig_tarball_excluding(&tree, "mytool", "0.1.0", true).unwrap();
let mut archive = tar::Archive::new(xz2::read::XzDecoder::new(
std::fs::File::open(&tarball).unwrap(),
));
let names: Vec<String> = archive
.entries()
.unwrap()
.map(|e| e.unwrap().path().unwrap().to_string_lossy().into_owned())
.collect();
// The generated vendored tree is out...
assert!(
!names.iter().any(|n| n.starts_with("mytool-0.1.0/vendor")),
"{names:?}"
);
// ...an unrelated nested vendor/ stays in...
assert!(
names.iter().any(|n| n == "mytool-0.1.0/src/vendor/mod.rs"),
"{names:?}"
);
// ...and normal files are unaffected.
assert!(
names.iter().any(|n| n == "mytool-0.1.0/Cargo.toml"),
"{names:?}"
);
}
#[test]
fn write_files_sets_exec_bit_and_parents() {
let dir = tempfile::tempdir().unwrap();