1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
|
//! Lintian data structures and utilities
/// The path to the Lintian data directory
pub const LINTIAN_DATA_PATH: &str = "/usr/share/lintian/data";
/// The path to the Lintian release dates file (old name)
pub const RELEASE_DATES_PATH_OLD: &str = "/usr/share/lintian/data/debian-policy/release-dates.json";
/// The path to the Lintian release dates file (new name)
pub const RELEASE_DATES_PATH_NEW: &str = "/usr/share/lintian/data/debian-policy/releases.json";
#[derive(Debug, Clone)]
/// A release of the Debian Policy
pub struct PolicyRelease {
/// The version of the release
pub version: StandardsVersion,
/// When the release was published
pub timestamp: chrono::DateTime<chrono::Utc>,
/// List of bug numbers closed by this release
pub closes: Vec<i32>,
/// The epoch of the release
pub epoch: Option<i32>,
/// The author of the release
pub author: Option<String>,
/// The changes made in this release
pub changes: Vec<String>,
}
#[derive(Debug, Clone, serde::Deserialize)]
#[allow(dead_code)]
struct Preamble {
pub cargo: String,
pub title: String,
}
// Internal struct for deserializing releases.json (new format with floats)
#[derive(Debug, Clone, serde::Deserialize)]
struct PolicyReleaseNewFormat {
pub version: StandardsVersion,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub closes: Vec<f64>,
pub epoch: Option<i32>,
pub author: Option<String>,
pub changes: Vec<String>,
}
// Internal struct for deserializing release-dates.json (old format with ints)
#[derive(Debug, Clone, serde::Deserialize)]
struct PolicyReleaseOldFormat {
pub version: StandardsVersion,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub closes: Vec<i32>,
pub epoch: Option<i32>,
pub author: Option<String>,
pub changes: Vec<String>,
}
impl From<PolicyReleaseNewFormat> for PolicyRelease {
fn from(r: PolicyReleaseNewFormat) -> Self {
PolicyRelease {
version: r.version,
timestamp: r.timestamp,
closes: r.closes.into_iter().map(|c| c as i32).collect(),
epoch: r.epoch,
author: r.author,
changes: r.changes,
}
}
}
impl From<PolicyReleaseOldFormat> for PolicyRelease {
fn from(r: PolicyReleaseOldFormat) -> Self {
PolicyRelease {
version: r.version,
timestamp: r.timestamp,
closes: r.closes,
epoch: r.epoch,
author: r.author,
changes: r.changes,
}
}
}
#[derive(Debug, Clone, serde::Deserialize)]
#[allow(dead_code)]
struct PolicyReleasesNewFormat {
pub preamble: Preamble,
pub releases: Vec<PolicyReleaseNewFormat>,
}
#[derive(Debug, Clone, serde::Deserialize)]
#[allow(dead_code)]
struct PolicyReleasesOldFormat {
pub preamble: Preamble,
pub releases: Vec<PolicyReleaseOldFormat>,
}
#[derive(Debug, Clone)]
/// A version of the Debian Policy
pub struct StandardsVersion(Vec<i32>);
impl StandardsVersion {
/// Create a new StandardsVersion from major, minor, and patch numbers
pub fn new(major: i32, minor: i32, patch: i32) -> Self {
Self(vec![major, minor, patch])
}
fn normalize(&self, n: usize) -> Self {
let mut version = self.0.clone();
version.resize(n, 0);
Self(version)
}
}
impl std::cmp::PartialEq for StandardsVersion {
fn eq(&self, other: &Self) -> bool {
// Normalize to the same length
let n = std::cmp::max(self.0.len(), other.0.len());
let self_normalized = self.normalize(n);
let other_normalized = other.normalize(n);
self_normalized.0 == other_normalized.0
}
}
impl std::cmp::Ord for StandardsVersion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// Normalize to the same length
let n = std::cmp::max(self.0.len(), other.0.len());
let self_normalized = self.normalize(n);
let other_normalized = other.normalize(n);
self_normalized.0.cmp(&other_normalized.0)
}
}
impl std::cmp::PartialOrd for StandardsVersion {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Eq for StandardsVersion {}
impl std::str::FromStr for StandardsVersion {
type Err = core::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split('.').map(|part| part.parse::<i32>());
let mut version = Vec::new();
for part in &mut parts {
version.push(part?);
}
Ok(StandardsVersion(version))
}
}
impl<'a> serde::Deserialize<'a> for StandardsVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
impl std::fmt::Display for StandardsVersion {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
self.0
.iter()
.map(|part| part.to_string())
.collect::<Vec<_>>()
.join(".")
)
}
}
/// Returns an iterator over all known standards versions
pub fn iter_standards_versions() -> impl Iterator<Item = PolicyRelease> {
iter_standards_versions_opt()
.expect("Failed to read release dates from either releases.json or release-dates.json")
}
/// Returns an iterator over all known standards versions
/// Returns None if neither releases.json nor release-dates.json can be found
pub fn iter_standards_versions_opt() -> Option<impl Iterator<Item = PolicyRelease>> {
// Try the new filename first, then fall back to the old one
if let Ok(data) = std::fs::read(RELEASE_DATES_PATH_NEW) {
// Try to parse as new format (releases.json)
if let Ok(parsed) = serde_json::from_slice::<PolicyReleasesNewFormat>(&data) {
return Some(
parsed
.releases
.into_iter()
.map(|r| r.into())
.collect::<Vec<_>>()
.into_iter(),
);
}
}
// Fall back to old format (release-dates.json)
if let Ok(data) = std::fs::read(RELEASE_DATES_PATH_OLD) {
if let Ok(parsed) = serde_json::from_slice::<PolicyReleasesOldFormat>(&data) {
return Some(
parsed
.releases
.into_iter()
.map(|r| r.into())
.collect::<Vec<_>>()
.into_iter(),
);
}
}
None
}
/// Returns the latest standards version
pub fn latest_standards_version() -> StandardsVersion {
iter_standards_versions()
.next()
.expect("No standards versions found")
.version
}
/// Returns the latest standards version
/// Returns None if release data files are not available
pub fn latest_standards_version_opt() -> Option<StandardsVersion> {
iter_standards_versions_opt()
.and_then(|mut iter| iter.next())
.map(|release| release.version)
}
#[cfg(test)]
mod tests {
use chrono::Datelike;
#[test]
fn test_standards_version() {
let version: super::StandardsVersion = "4.2.0".parse().unwrap();
assert_eq!(version.0, vec![4, 2, 0]);
assert_eq!(version.to_string(), "4.2.0");
assert_eq!(version, "4.2".parse().unwrap());
assert_eq!(version, "4.2.0".parse().unwrap());
}
#[test]
fn test_parse_releases() {
let input = r###"{
"preamble" : {
"cargo" : "releases",
"title" : "Debian Policy Releases"
},
"releases" : [
{
"author" : "Sean Whitton <spwhitton@spwhitton.name>",
"changes" : [
"",
"debian-policy (4.7.0.0) unstable; urgency=medium",
"",
" [ Sean Whitton ]",
" * Policy: Prefer native overriding mechanisms to diversions & alternatives",
" Wording: Luca Boccassi <bluca@debian.org>",
" Seconded: Sean Whitton <spwhitton@spwhitton.name>",
" Seconded: Russ Allbery <rra@debian.org>",
" Seconded: Holger Levsen <holger@layer-acht.org>",
" Closes: #1035733",
" * Policy: Improve alternative build dependency discussion",
" Wording: Russ Allbery <rra@debian.org>",
" Seconded: Wouter Verhelst <wouter@debian.org>",
" Seconded: Sean Whitton <spwhitton@spwhitton.name>",
" Closes: #968226",
" * Policy: No network access for required targets for contrib & non-free",
" Wording: Aurelien Jarno <aurel32@debian.org>",
" Seconded: Sam Hartman <hartmans@debian.org>",
" Seconded: Tobias Frost <tobi@debian.org>",
" Seconded: Holger Levsen <holger@layer-acht.org>",
" Closes: #1068192",
"",
" [ Russ Allbery ]",
" * Policy: Add mention of the new non-free-firmware archive area",
" Wording: Gunnar Wolf <gwolf@gwolf.org>",
" Seconded: Holger Levsen <holger@layer-acht.org>",
" Seconded: Russ Allbery <rra@debian.org>",
" Closes: #1029211",
" * Policy: Source packages in main may build binary packages in contrib",
" Wording: Simon McVittie <smcv@debian.org>",
" Seconded: Holger Levsen <holger@layer-acht.org>",
" Seconded: Russ Allbery <rra@debian.org>",
" Closes: #994008",
" * Policy: Allow hard links in source packages",
" Wording: Russ Allbery <rra@debian.org>",
" Seconded: Helmut Grohne <helmut@subdivi.de>",
" Seconded: Guillem Jover <guillem@debian.org>",
" Closes: #970234",
" * Policy: Binary and Description fields may be absent in .changes",
" Wording: Russ Allbery <rra@debian.org>",
" Seconded: Sam Hartman <hartmans@debian.org>",
" Seconded: Guillem Jover <guillem@debian.org>",
" Closes: #963524",
" * Policy: systemd units are required to start and stop system services",
" Wording: Luca Boccassi <bluca@debian.org>",
" Wording: Russ Allbery <rra@debian.org>",
" Seconded: Luca Boccassi <bluca@debian.org>",
" Seconded: Sam Hartman <hartmans@debian.org>",
" Closes: #1039102"
],
"closes" : [
963524,
968226,
970234,
994008,
1029211,
1035733,
1039102,
1068192
],
"epoch" : 1712466535,
"timestamp" : "2024-04-07T05:08:55Z",
"version" : "4.7.0.0"
}
]
}"###;
let data: super::PolicyReleasesOldFormat = serde_json::from_str(input).unwrap();
assert_eq!(data.releases.len(), 1);
}
#[test]
fn test_iter_standards_versions_opt() {
// This test verifies that we can read the policy release data
// In test environments, the files may not exist
let Some(iter) = super::iter_standards_versions_opt() else {
// Skip test if no files are available
return;
};
let versions: Vec<_> = iter.collect();
// Should have at least one version
assert!(!versions.is_empty());
// The latest version should be first
let latest = &versions[0];
// Verify the version has a proper format
assert!(!latest.version.to_string().is_empty());
assert!(latest.version.to_string().contains('.'));
// Verify other fields are populated
assert!(latest.timestamp.year() >= 2020);
assert!(!latest.changes.is_empty());
}
#[test]
fn test_latest_standards_version_opt() {
// Test that we can get the latest standards version
let Some(latest) = super::latest_standards_version_opt() else {
// Skip test if no files are available
return;
};
// Should have a valid version string
let version_str = latest.to_string();
assert!(!version_str.is_empty());
assert!(version_str.contains('.'));
// Should be at least 4.0.0 (Debian policy versions)
assert!(latest >= "4.0.0".parse::<super::StandardsVersion>().unwrap());
}
}
|