Move the generic components out of src/build/ into a new src/debian/ module so they can be reused independently of the build pipeline: deb822 control parsing (plus the debian/control model), file checksum registry, debian/files registry, Debian version handling and changelog entry parsing. Merge OpenPGP clearsigning into utils/gpg.rs next to the existing key discovery helper, making signing available outside of builds. Delegate changelog.rs header/footer parsing to the new debian::changelog parser, removing the duplicate regex implementation. src/build/ keeps only build-specific logic: the pipeline driver, build types, environment setup and the .buildinfo/.changes writers.
121 lines
4.1 KiB
Rust
121 lines
4.1 KiB
Rust
//! Debian version handling: splitting and validation of
|
|
//! `[epoch:]upstream[-revision]` version strings.
|
|
|
|
/// A Debian version, split into its `[epoch:]upstream[-revision]` parts.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct DebianVersion {
|
|
/// Optional numeric epoch (part before the first `:`).
|
|
pub epoch: Option<u32>,
|
|
/// Upstream version (may itself contain `-` when there is no revision).
|
|
pub upstream: String,
|
|
/// Optional Debian revision (part after the last `-`).
|
|
pub debian_revision: Option<String>,
|
|
}
|
|
|
|
impl DebianVersion {
|
|
/// Parse and validate a Debian version string.
|
|
pub fn parse(raw: &str) -> Result<DebianVersion, String> {
|
|
let raw = raw.trim();
|
|
if raw.is_empty() {
|
|
return Err("empty version string".to_string());
|
|
}
|
|
|
|
let (epoch, rest) = match raw.split_once(':') {
|
|
Some((e, r)) => {
|
|
let epoch: u32 = e
|
|
.parse()
|
|
.map_err(|_| format!("invalid epoch '{}' in version '{}'", e, raw))?;
|
|
(Some(epoch), r)
|
|
}
|
|
None => (None, raw),
|
|
};
|
|
|
|
// The revision is everything after the last hyphen.
|
|
let (upstream, debian_revision) = match rest.rsplit_once('-') {
|
|
Some((u, r)) => (u.to_string(), Some(r.to_string())),
|
|
None => (rest.to_string(), None),
|
|
};
|
|
|
|
if upstream.is_empty() {
|
|
return Err(format!("missing upstream version in '{}'", raw));
|
|
}
|
|
for c in upstream.chars() {
|
|
if !(c.is_ascii_alphanumeric()
|
|
|| matches!(c, '.' | '+' | '-' | '~' | ':')
|
|
|| !c.is_ascii())
|
|
{
|
|
return Err(format!("invalid character '{}' in version '{}'", c, raw));
|
|
}
|
|
}
|
|
if let Some(rev) = &debian_revision {
|
|
for c in rev.chars() {
|
|
if !(c.is_ascii_alphanumeric() || matches!(c, '.' | '+' | '~') || !c.is_ascii()) {
|
|
return Err(format!(
|
|
"invalid character '{}' in revision of version '{}'",
|
|
c, raw
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(DebianVersion {
|
|
epoch,
|
|
upstream,
|
|
debian_revision,
|
|
})
|
|
}
|
|
|
|
/// Full version string, including the epoch (`[epoch:]upstream[-rev]`).
|
|
pub fn full(&self) -> String {
|
|
match (&self.epoch, &self.debian_revision) {
|
|
(Some(e), Some(r)) => format!("{}:{}-{}", e, self.upstream, r),
|
|
(Some(e), None) => format!("{}:{}", e, self.upstream),
|
|
(None, Some(r)) => format!("{}-{}", self.upstream, r),
|
|
(None, None) => self.upstream.clone(),
|
|
}
|
|
}
|
|
|
|
/// Version string without the epoch (`upstream[-rev]`), used in artifact
|
|
/// file names.
|
|
pub fn no_epoch(&self) -> String {
|
|
match &self.debian_revision {
|
|
Some(r) => format!("{}-{}", self.upstream, r),
|
|
None => self.upstream.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn version_splitting() {
|
|
let v = DebianVersion::parse("1.2.3-4ubuntu5").unwrap();
|
|
assert_eq!(v.epoch, None);
|
|
assert_eq!(v.upstream, "1.2.3");
|
|
assert_eq!(v.debian_revision.as_deref(), Some("4ubuntu5"));
|
|
assert_eq!(v.full(), "1.2.3-4ubuntu5");
|
|
assert_eq!(v.no_epoch(), "1.2.3-4ubuntu5");
|
|
|
|
let v = DebianVersion::parse("3:2.10-3").unwrap();
|
|
assert_eq!(v.epoch, Some(3));
|
|
assert_eq!(v.upstream, "2.10");
|
|
assert_eq!(v.no_epoch(), "2.10-3");
|
|
assert_eq!(v.full(), "3:2.10-3");
|
|
|
|
let v = DebianVersion::parse("1.0").unwrap();
|
|
assert_eq!(v.debian_revision, None);
|
|
assert_eq!(v.no_epoch(), "1.0");
|
|
}
|
|
|
|
#[test]
|
|
fn version_validation() {
|
|
assert!(DebianVersion::parse("").is_err());
|
|
assert!(DebianVersion::parse(":1.0").is_err());
|
|
assert!(DebianVersion::parse("a:_b").is_err());
|
|
assert!(DebianVersion::parse("1.0").is_ok());
|
|
assert!(DebianVersion::parse("1.0~rc1-2").is_ok());
|
|
}
|
|
}
|