build: record the actual build environment in .buildinfo

The binary build exported DEB_BUILD_OPTIONS='parallel=<context nproc>
nocheck' (or the -j override) but the generated .buildinfo recomputed
the environment from host state: host core count, no nocheck, and
vendor profiles that ignored DEB_BUILD_PROFILES (a cross build recorded
no 'cross' profile). generate_binary_metadata now records the exact env
map that was exported to the build steps, and the recorded profiles
come from the exported DEB_BUILD_PROFILES when set.

Also unifies vendor parsing on one helper (the context-side copy lacked
the Origin: fallback of the source-build path).
This commit is contained in:
2026-09-16 04:22:43 +02:00
parent 50ae12cafe
commit 3a454b0811
4 changed files with 98 additions and 54 deletions
+44 -24
View File
@@ -36,10 +36,13 @@ pub struct BinaryMetadataOptions {
pub profiles: Vec<String>,
/// Vendor name (`Build-Origin`).
pub vendor: String,
/// Parallel job count advertised in `DEB_BUILD_OPTIONS`.
pub parallel: usize,
/// Reproducible-builds epoch exported to the build.
pub source_date_epoch: i64,
/// Environment variables pkh exported to the build steps (e.g. `LANG`,
/// `DEB_BUILD_OPTIONS` with the real parallel count and `nocheck`,
/// `SOURCE_DATE_EPOCH`, cross `DEB_*` variables). Recorded — filtered to
/// dpkg's allow-list — in the `.buildinfo` `Environment` field, taking
/// precedence over whatever the host process inherited, so the metadata
/// describes the environment the build actually ran in.
pub exported_env: BTreeMap<String, String>,
/// Build architecture (the machine inside the build context).
pub build_arch: String,
/// Host architecture (the packages' target); equals the build
@@ -210,8 +213,10 @@ pub fn generate_binary_metadata(
// ------------------------------------------------------------------
// .buildinfo generation, then registration in debian/files
// ------------------------------------------------------------------
let pipeline_env = pipeline_environment(opts);
let environment = crate::build::env::buildinfo_environment(&pipeline_env);
// Record exactly the environment that was exported to the build steps,
// overriding any host-inherited value (dpkg-style allowed-variable
// filtering, export precedence).
let environment = crate::build::env::buildinfo_environment(&opts.exported_env);
// dpkg-genbuildinfo sorts the accumulated architecture values, while
// dpkg-genchanges keeps encounter order.
@@ -294,24 +299,6 @@ pub fn generate_binary_metadata(
Ok((buildinfo_path, changes_path))
}
/// Environment exported to the build steps; recorded (filtered) in the
/// `.buildinfo` `Environment` field.
fn pipeline_environment(opts: &BinaryMetadataOptions) -> BTreeMap<String, String> {
let mut env = BTreeMap::new();
env.insert(
"SOURCE_DATE_EPOCH".to_string(),
opts.source_date_epoch.to_string(),
);
env.insert(
"DEB_BUILD_OPTIONS".to_string(),
format!("parallel={}", opts.parallel),
);
if !opts.profiles.is_empty() {
env.insert("DEB_BUILD_PROFILES".to_string(), opts.profiles.join(","));
}
env
}
/// Compute md5/sha1/sha256 digests and sizes for the named files inside the
/// context directory `dir`, using coreutils.
fn hashes_in_context(
@@ -463,6 +450,39 @@ fn include_dsc_artifacts(
mod tests {
use super::*;
/// The recorded `.buildinfo` `Environment` must carry the environment
/// actually exported to the build steps (`parallel=N nocheck`, `LANG=C`,
/// ...), taking precedence over any host-inherited value, instead of
/// values recomputed from host state at generation time.
#[test]
fn environment_records_exported_env_not_host_defaults() {
let mut exported_env = BTreeMap::new();
exported_env.insert("LANG".to_string(), "C".to_string());
exported_env.insert(
"DEB_BUILD_OPTIONS".to_string(),
"parallel=7 nocheck".to_string(),
);
let opts = BinaryMetadataOptions {
profiles: Vec::new(),
vendor: "debian".to_string(),
exported_env,
build_arch: "amd64".to_string(),
host_arch: "amd64".to_string(),
};
let environment = crate::build::env::buildinfo_environment(&opts.exported_env);
assert!(
environment.contains("DEB_BUILD_OPTIONS=\"parallel=7 nocheck\""),
"recorded Environment must carry the exported DEB_BUILD_OPTIONS: {environment}"
);
assert!(
environment.contains("LANG=\"C\""),
"recorded Environment must carry the exported LANG: {environment}"
);
// Not in dpkg's allowed-variable list: never recorded.
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