build/binary: native .buildinfo/.changes for binary builds (pkh deb)
Extend the metadata writers to binary-only uploads and wire them into the 'pkh deb' flow: - build/binary.rs generates <pkg>_<ver>_<arch>.buildinfo/.changes through any Context: debian/files consumption, encounter-order Architecture accumulation (sorted in .buildinfo like dpkg-genbuildinfo), sorted Binary lists, dpkg-formatted Description lines with udeb suffixes, Installed-Build-Depends closure over the context status DB, and binNMU handling (Source: pkg (prev), Binary-Only-Changes, previous .dsc redistribution); - artifact digests are computed inside the context via coreutils (md5sum/sha1sum/sha256sum/stat) so chrooted/remote trees work; - deb/local.rs runs the generation after 'rules binary', exports SOURCE_DATE_EPOCH from the changelog (reproducibility), and resolves vendor/profiles inside the context; deb/mod.rs retrieves the new artifacts alongside the debs; - reusable helpers added: FilesList::parse/render, parse_changelog_entry_from_str, parse_previous_version_from_str, installed_build_depends_from_content. Differential gate: same tree built with real 'dpkg-buildpackage -b' and with the pkh flow; .changes/.buildinfo compared field-by-field modulo machine-dependent fields, artifact checksums included.
This commit is contained in:
@@ -227,6 +227,20 @@ pub async fn build(
|
||||
.to_str()
|
||||
.ok_or("Invalid package directory path")?;
|
||||
|
||||
// Reproducibility: export SOURCE_DATE_EPOCH from the changelog entry,
|
||||
// like dpkg-buildpackage does.
|
||||
match ctx.read_file(&package_dir.join("debian/changelog")) {
|
||||
Ok(content) => {
|
||||
if let Ok(entry) = crate::debian::parse_changelog_entry_from_str(&content) {
|
||||
env.insert(
|
||||
"SOURCE_DATE_EPOCH".to_string(),
|
||||
entry.timestamp.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => log::debug!("cannot read changelog for SOURCE_DATE_EPOCH: {}", e),
|
||||
}
|
||||
|
||||
// Apply quilt patches if the package provides a patch series
|
||||
apply_quilt_patches(package_dir_str, &env, ctx.clone(), &ui, &sink)?;
|
||||
|
||||
@@ -318,6 +332,97 @@ pub async fn build(
|
||||
);
|
||||
}
|
||||
|
||||
// Generate the upload metadata (.buildinfo + .changes) natively, the
|
||||
// equivalent of dpkg-genbuildinfo -b + dpkg-genchanges -b, consuming
|
||||
// debian/files produced by the build. Failures are logged but do not
|
||||
// discard the produced binaries.
|
||||
if let Err(e) =
|
||||
generate_upload_metadata(package_dir_str, build_root, arch, cross, &env, &ctx)
|
||||
{
|
||||
warn!("failed to generate .buildinfo/.changes: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate `.buildinfo` and `.changes` for the finished binary build,
|
||||
/// inside the build context.
|
||||
fn generate_upload_metadata(
|
||||
package_dir: &str,
|
||||
build_root: &str,
|
||||
arch: &str,
|
||||
cross: bool,
|
||||
env: &HashMap<String, String>,
|
||||
ctx: &Arc<Context>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
use std::path::Path;
|
||||
|
||||
let changelog_path = Path::new(package_dir).join("debian/changelog");
|
||||
let changelog_content = ctx.read_file(&changelog_path)?;
|
||||
let entry =
|
||||
crate::debian::parse_changelog_entry_from_str(&changelog_content)?;
|
||||
|
||||
// Build architecture: the machine inside the build context.
|
||||
let build_arch = ctx
|
||||
.command("dpkg")
|
||||
.arg("--print-architecture")
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|o| o.status.success())
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_else(crate::get_current_arch);
|
||||
let host_arch = if cross {
|
||||
arch.to_string()
|
||||
} else {
|
||||
build_arch.clone()
|
||||
};
|
||||
|
||||
// Vendor resolution inside the context (falls back to the host view).
|
||||
let vendor = ctx
|
||||
.read_file(Path::new("/etc/dpkg/origins/default"))
|
||||
.ok()
|
||||
.and_then(|content| {
|
||||
for line in content.lines() {
|
||||
if let Some(v) = line.strip_prefix("Vendor:") {
|
||||
let v = v.trim();
|
||||
if !v.is_empty() {
|
||||
return Some(v.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.unwrap_or_else(crate::build::env::current_vendor);
|
||||
|
||||
let profiles = crate::build::env::resolve_build_profiles(
|
||||
&[],
|
||||
&vendor,
|
||||
);
|
||||
let source_date_epoch = env
|
||||
.get("SOURCE_DATE_EPOCH")
|
||||
.and_then(|v| v.parse::<i64>().ok())
|
||||
.unwrap_or(entry.timestamp);
|
||||
|
||||
let opts = crate::build::binary::BinaryMetadataOptions {
|
||||
profiles,
|
||||
vendor,
|
||||
parallel: crate::build::env::num_parallel(),
|
||||
source_date_epoch,
|
||||
build_arch,
|
||||
host_arch,
|
||||
};
|
||||
let (buildinfo, changes) = crate::build::binary::generate_binary_metadata(
|
||||
ctx,
|
||||
Path::new(package_dir),
|
||||
Path::new(build_root),
|
||||
&opts,
|
||||
)?;
|
||||
log::info!(
|
||||
"generated upload metadata: {} and {}",
|
||||
buildinfo.display(),
|
||||
changes.display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -165,14 +165,18 @@ async fn build_binary_package_impl(
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve produced .deb files
|
||||
// Retrieve produced artifacts (.deb files plus the upload metadata
|
||||
// (.buildinfo/.changes) generated natively after the build)
|
||||
if let Some(u) = ui {
|
||||
u.phase(Phase::RetrievingArtifacts);
|
||||
}
|
||||
let remote_files = build_ctx.list_files(Path::new(&build_root))?;
|
||||
let deb_files: Vec<PathBuf> = remote_files
|
||||
.into_iter()
|
||||
.filter(|f| f.extension().is_some_and(|ext| ext == "deb"))
|
||||
.filter(|f| {
|
||||
f.extension()
|
||||
.is_some_and(|ext| matches!(ext.to_str(), Some("deb") | Some("buildinfo") | Some("changes")))
|
||||
})
|
||||
.collect();
|
||||
let total_debs = deb_files.len();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user