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:
+24
-19
@@ -46,7 +46,15 @@ pub fn parse_changelog_entry(path: &Path) -> Result<ChangelogEntry, Box<dyn std:
|
||||
e
|
||||
)
|
||||
})?;
|
||||
parse_changelog_entry_from_str(&content)
|
||||
}
|
||||
|
||||
/// Parse the most recent changelog entry from its textual content. `origin`
|
||||
/// is used in error messages only.
|
||||
pub fn parse_changelog_entry_from_str(
|
||||
content: &str,
|
||||
) -> Result<ChangelogEntry, Box<dyn std::error::Error>> {
|
||||
let origin = "changelog";
|
||||
let mut lines = content.lines().peekable();
|
||||
|
||||
// --- Header line: `package (version) distributions; urgency=medium[, key=value]`
|
||||
@@ -55,17 +63,13 @@ pub fn parse_changelog_entry(path: &Path) -> Result<ChangelogEntry, Box<dyn std:
|
||||
Some(l) if l.trim().is_empty() => continue,
|
||||
Some(l) => break l.trim_end(),
|
||||
None => {
|
||||
return Err(format!("changelog '{}' is empty", path.display()).into());
|
||||
return Err(format!("changelog '{origin}' is empty").into());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let open = header.find('(').ok_or_else(|| {
|
||||
format!(
|
||||
"invalid changelog header in '{}': {}",
|
||||
path.display(),
|
||||
header
|
||||
)
|
||||
format!("invalid changelog header in '{origin}': {header}")
|
||||
})?;
|
||||
let close = header[open..]
|
||||
.find(')')
|
||||
@@ -123,9 +127,8 @@ pub fn parse_changelog_entry(path: &Path) -> Result<ChangelogEntry, Box<dyn std:
|
||||
|
||||
let trailer_line = trailer.ok_or_else(|| {
|
||||
format!(
|
||||
"no maintainer trailer found in '{}': expected a line of the form \
|
||||
' -- Name <email> Date'",
|
||||
path.display()
|
||||
"no maintainer trailer found in '{origin}': expected a line of the form \
|
||||
' -- Name <email> Date'"
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -148,12 +151,7 @@ pub fn parse_changelog_entry(path: &Path) -> Result<ChangelogEntry, Box<dyn std:
|
||||
|
||||
let timestamp = DateTime::parse_from_rfc2822(&date_raw)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"cannot parse changelog date '{}' in '{}': {}",
|
||||
date_raw,
|
||||
path.display(),
|
||||
e
|
||||
)
|
||||
format!("cannot parse changelog date '{date_raw}' in '{origin}': {e}")
|
||||
})?
|
||||
.timestamp();
|
||||
|
||||
@@ -220,7 +218,14 @@ fn find_closes(body_lines: &[String]) -> Option<String> {
|
||||
pub fn parse_previous_version(path: &Path) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
let content = std::fs::read_to_string(path)
|
||||
.map_err(|e| format!("failed to read changelog '{}': {}", path.display(), e))?;
|
||||
parse_previous_version_from_str(&content)
|
||||
}
|
||||
|
||||
/// Return the version of the *previous* changelog entry from the textual
|
||||
/// content of a changelog file.
|
||||
pub fn parse_previous_version_from_str(
|
||||
content: &str,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
let mut seen_first = false;
|
||||
for line in content.lines() {
|
||||
let line = line.trim_end();
|
||||
@@ -229,12 +234,12 @@ pub fn parse_previous_version(path: &Path) -> Result<Option<String>, Box<dyn std
|
||||
seen_first = true;
|
||||
continue;
|
||||
}
|
||||
let open = line.find('(').ok_or_else(|| {
|
||||
format!("invalid changelog header in '{}': {}", path.display(), line)
|
||||
})?;
|
||||
let open = line
|
||||
.find('(')
|
||||
.ok_or_else(|| format!("invalid changelog header: {line}"))?;
|
||||
let close = line[open..]
|
||||
.find(')')
|
||||
.ok_or_else(|| format!("unbalanced parenthesis in changelog header '{}'", line))?;
|
||||
.ok_or_else(|| format!("unbalanced parenthesis in changelog header '{line}'"))?;
|
||||
return Ok(Some(line[open + 1..open + close].to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
+29
-10
@@ -114,29 +114,31 @@ impl FilesList {
|
||||
|
||||
/// Load `debian/files`. A missing file yields an empty registry.
|
||||
pub fn load(path: &Path) -> Result<FilesList, Box<dyn std::error::Error>> {
|
||||
let mut list = FilesList::new();
|
||||
let content = match std::fs::read_to_string(path) {
|
||||
Ok(c) => c,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(list),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(FilesList::new()),
|
||||
Err(e) => {
|
||||
return Err(format!("cannot read '{}': {}", path.display(), e).into());
|
||||
}
|
||||
};
|
||||
FilesList::parse(&content)
|
||||
.map_err(|e| format!("in '{}': {}", path.display(), e).into())
|
||||
}
|
||||
|
||||
/// Parse a `debian/files` registry from its textual content
|
||||
/// (`filename section priority [key=value...]` lines).
|
||||
pub fn parse(content: &str) -> Result<FilesList, String> {
|
||||
let mut list = FilesList::new();
|
||||
for line in content.lines() {
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let tokens: Vec<&str> = line.split_whitespace().collect();
|
||||
if tokens.len() < 3 {
|
||||
return Err(format!("badly formed line in '{}': {}", path.display(), line).into());
|
||||
return Err(format!("badly formed line: {line}"));
|
||||
}
|
||||
let mut entry = parse_filename(tokens[0]).ok_or_else(|| {
|
||||
format!(
|
||||
"badly formed file name in '{}': {}",
|
||||
path.display(),
|
||||
tokens[0]
|
||||
)
|
||||
})?;
|
||||
let mut entry = parse_filename(tokens[0])
|
||||
.ok_or_else(|| format!("badly formed file name: {}", tokens[0]))?;
|
||||
entry.section = tokens[1].to_string();
|
||||
entry.priority = tokens[2].to_string();
|
||||
for attr in &tokens[3..] {
|
||||
@@ -181,6 +183,23 @@ impl FilesList {
|
||||
self.files.is_empty()
|
||||
}
|
||||
|
||||
/// Render the registry to its textual `debian/files` representation.
|
||||
pub fn render(&self) -> String {
|
||||
let mut out = String::new();
|
||||
for entry in self.iter() {
|
||||
out.push_str(&entry.filename);
|
||||
out.push(' ');
|
||||
out.push_str(&entry.section);
|
||||
out.push(' ');
|
||||
out.push_str(&entry.priority);
|
||||
for (k, v) in &entry.attrs {
|
||||
out.push_str(&format!(" {k}={v}"));
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Save atomically: write `<path>.new` then rename over `path`, like
|
||||
/// dpkg does.
|
||||
pub fn save_atomic(&self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ pub mod deps;
|
||||
pub mod files;
|
||||
pub mod version;
|
||||
|
||||
pub use changelog::{ChangelogEntry, parse_changelog_entry};
|
||||
pub use changelog::{
|
||||
ChangelogEntry, parse_changelog_entry, parse_changelog_entry_from_str,
|
||||
parse_previous_version_from_str,
|
||||
};
|
||||
pub use checksums::{Entry as ChecksumEntry, FileChecksums};
|
||||
pub use control::{ControlInfo, Paragraph, parse_paragraphs, write_paragraph};
|
||||
pub use files::{FilesEntry, FilesList};
|
||||
|
||||
Reference in New Issue
Block a user