build: re-implement source builds natively, drop dpkg-buildpackage shell-out
Replace the 'dpkg-buildpackage -S' wrapper with a native pipeline in src/build/: - deb822 control parser/writer with dpkg-compatible multiline rendering (control.rs) - md5/sha1/sha256 checksum registry, insertion-ordered like dpkg's artifact accumulation (checksums.rs) - Debian version splitting/validation and full changelog entry parsing, including binNMU binary-only entries (metadata.rs) - build-type bitflags and rules-target/artifact-suffix mapping (buildtype.rs) - environment setup: SOURCE_DATE_EPOCH, DEB_BUILD_OPTIONS, dpkg-architecture env dump, vendor default profiles and the sanitized Environment field recorded in .buildinfo (env.rs) - debian/files registry with atomic saves (files.rs) - native .buildinfo writer, including the Installed-Build-Depends closure computed over the dpkg status database (buildinfo.rs) - native .changes writer emitting dpkg's canonical field order with legacy Files + Checksums-Sha1/Sha256 (changes.rs) - gpgme clearsigning with the transitive checksum cascade (dsc -> buildinfo -> changes), key discovery from the changelog maintainer and UNRELEASED no-sign handling (sign.rs) dpkg-source (-b/--before-build/--after-build) intentionally remains a subprocess; debian/rules execution is unchanged. Validated differentially against real dpkg-buildpackage -S -I -i -nc -d on native and 3.0 (quilt) fixture packages: .dsc byte-identical, .changes payload matches modulo machine-dependent Installed-Build-Depends and Environment content, all signatures verify with gpg, artifact ordering and UNRELEASED no-sign behavior match dpkg.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
//! Debian build types (`dpkg-buildpackage -b/-B/-A/-S/-g/-G/--build=...`)
|
||||
//! and their mapping to `debian/rules` targets.
|
||||
|
||||
/// Build type bit flags, mirroring `Dpkg::BuildTypes`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BuildType(u8);
|
||||
|
||||
/// Source build component (`-S`, `--build=source`).
|
||||
pub const SOURCE: BuildType = BuildType(0x1);
|
||||
/// Arch-dependent binary build component (`-B`, `--build=any`).
|
||||
pub const ARCH_DEP: BuildType = BuildType(0x2);
|
||||
/// Arch-independent binary build component (`-A`, `--build=all`).
|
||||
pub const ARCH_INDEP: BuildType = BuildType(0x4);
|
||||
|
||||
/// Any binary component.
|
||||
pub const BINARY: BuildType = BuildType(ARCH_DEP.0 | ARCH_INDEP.0);
|
||||
/// Normal full build: source + binaries (`-F`, default).
|
||||
pub const FULL: BuildType = BuildType(SOURCE.0 | BINARY.0);
|
||||
/// Source + arch-dependent (`-G`).
|
||||
pub const SOURCE_ARCH_DEP: BuildType = BuildType(SOURCE.0 | ARCH_DEP.0);
|
||||
/// Source + arch-indep (`-g`).
|
||||
pub const SOURCE_ARCH_INDEP: BuildType = BuildType(SOURCE.0 | ARCH_INDEP.0);
|
||||
|
||||
impl BuildType {
|
||||
/// Construct from raw bits.
|
||||
pub const fn from_bits(bits: u8) -> Self {
|
||||
BuildType(bits)
|
||||
}
|
||||
|
||||
/// Raw bits.
|
||||
pub const fn bits(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// True if any of `other`'s components are set.
|
||||
pub fn has_any(self, other: BuildType) -> bool {
|
||||
self.0 & other.0 != 0
|
||||
}
|
||||
|
||||
/// True if all of `other`'s components are set.
|
||||
pub fn has_all(self, other: BuildType) -> bool {
|
||||
self.0 & other.0 == other.0
|
||||
}
|
||||
|
||||
/// True if none of `other`'s components are set.
|
||||
pub fn has_none(self, other: BuildType) -> bool {
|
||||
self.0 & other.0 == 0
|
||||
}
|
||||
|
||||
/// Parse a comma-separated `--build=<type>[,...]` option value.
|
||||
///
|
||||
/// Valid components: `full`, `source`, `binary`, `any`, `all`.
|
||||
pub fn from_options(value: &str) -> Result<BuildType, String> {
|
||||
let mut result = BuildType(0);
|
||||
for part in value.split(',') {
|
||||
match part.trim() {
|
||||
"full" => result = FULL,
|
||||
"source" => result = BuildType(result.0 | SOURCE.0),
|
||||
"binary" => result = BuildType(result.0 | BINARY.0),
|
||||
"any" => result = BuildType(result.0 | ARCH_DEP.0),
|
||||
"all" => result = BuildType(result.0 | ARCH_INDEP.0),
|
||||
other => return Err(format!("unknown build type component '{}'", other)),
|
||||
}
|
||||
}
|
||||
if result.0 == 0 {
|
||||
return Err("empty build type".to_string());
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Canonical comma-separated representation (as passed to
|
||||
/// `dpkg-genchanges --build=` / `dpkg-genbuildinfo --build=`).
|
||||
pub fn to_options(self) -> String {
|
||||
let mut parts = Vec::new();
|
||||
if self.has_any(SOURCE) {
|
||||
parts.push("source");
|
||||
}
|
||||
if self.has_all(BINARY) {
|
||||
parts.push("binary");
|
||||
} else {
|
||||
if self.has_any(ARCH_DEP) {
|
||||
parts.push("any");
|
||||
}
|
||||
if self.has_any(ARCH_INDEP) {
|
||||
parts.push("all");
|
||||
}
|
||||
}
|
||||
parts.join(",")
|
||||
}
|
||||
|
||||
/// The `debian/rules` build target for this type:
|
||||
/// `build`, `build-arch` or `build-indep`.
|
||||
pub fn build_target(self) -> &'static str {
|
||||
if self.has_all(BINARY) || self.has_none(BINARY) {
|
||||
"build"
|
||||
} else if self.has_any(ARCH_DEP) {
|
||||
"build-arch"
|
||||
} else {
|
||||
"build-indep"
|
||||
}
|
||||
}
|
||||
|
||||
/// The `debian/rules` binary target for this type:
|
||||
/// `binary`, `binary-arch` or `binary-indep`.
|
||||
pub fn binary_target(self) -> &'static str {
|
||||
if self.has_all(BINARY) || self.has_none(BINARY) {
|
||||
"binary"
|
||||
} else if self.has_any(ARCH_DEP) {
|
||||
"binary-arch"
|
||||
} else {
|
||||
"binary-indep"
|
||||
}
|
||||
}
|
||||
|
||||
/// The architecture suffix used in artifact file names:
|
||||
/// host arch, `all` or `source`.
|
||||
pub fn arch_suffix<'a>(self, host_arch: &'a str) -> &'a str {
|
||||
if self.has_any(ARCH_DEP) {
|
||||
host_arch
|
||||
} else if self.has_any(ARCH_INDEP) {
|
||||
"all"
|
||||
} else {
|
||||
"source"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_options() {
|
||||
assert_eq!(BuildType::from_options("full").unwrap(), FULL);
|
||||
assert_eq!(BuildType::from_options("source").unwrap(), SOURCE);
|
||||
assert_eq!(BuildType::from_options("source,any").unwrap(), SOURCE_ARCH_DEP);
|
||||
assert_eq!(BuildType::from_options("any,all").unwrap(), BINARY);
|
||||
assert!(BuildType::from_options("bogus").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_options() {
|
||||
for t in [FULL, SOURCE, BINARY, SOURCE_ARCH_DEP, SOURCE_ARCH_INDEP] {
|
||||
assert_eq!(BuildType::from_options(&t.to_options()).unwrap(), t);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn targets() {
|
||||
assert_eq!(FULL.build_target(), "build");
|
||||
assert_eq!(FULL.binary_target(), "binary");
|
||||
assert_eq!(ARCH_DEP.build_target(), "build-arch");
|
||||
assert_eq!(ARCH_DEP.binary_target(), "binary-arch");
|
||||
assert_eq!(ARCH_INDEP.build_target(), "build-indep");
|
||||
assert_eq!(ARCH_INDEP.binary_target(), "binary-indep");
|
||||
assert_eq!(SOURCE.arch_suffix("amd64"), "source");
|
||||
assert_eq!(ARCH_DEP.arch_suffix("amd64"), "amd64");
|
||||
assert_eq!(ARCH_INDEP.arch_suffix("amd64"), "all");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user