deb,quirks: scope deb entries per series with an entry list

One deb block per package cannot express two series needing different
rules. Make pull and deb lists of entries, each with its own series
scope: every matching entry applies, in file order, so a stonking
entry can carry its own dependency rules next to the resolute one.
Thread the series through find_package_directory for the
package_directory lookup, which keeps its deb-then-pull fallback.
This commit is contained in:
2026-09-20 19:40:19 +02:00
parent ff1f8c7ccd
commit 5b0cc08d8f
4 changed files with 94 additions and 64 deletions
+17 -13
View File
@@ -1,10 +1,12 @@
# Quirks configuration for package-specific workarounds # Quirks configuration for package-specific workarounds
# This file defines package-specific quirks that are applied during pull and deb operations # This file defines package-specific quirks that are applied during pull and deb operations
# #
# Entries can be scoped with `series`: an empty list applies to every # `pull` and `deb` hold one entry per scope: several entries can carry
# series, otherwise only the listed ones. Packaging workarounds should # different `series` lists, and every matching entry applies in file
# carry the series they were verified against so they can be dropped once # order. Entries can be scoped with `series`: an empty list applies to
# the upstream packaging catches up. # every series, otherwise only the listed ones. Packaging workarounds
# should carry the series they were verified against so they can be
# dropped once the upstream packaging catches up.
quirks: quirks:
@@ -15,13 +17,13 @@ quirks:
# until the control is fixed upstream. # until the control is fixed upstream.
linux: linux:
deb: deb:
series: [resolute] - series: [resolute]
dependencies: dependencies:
replace: replace:
llvm-21-dev: llvm-21-dev:native <!stage1> llvm-21-dev: llvm-21-dev:native <!stage1>
linux-riscv: linux-riscv:
deb: deb:
series: [resolute] - series: [resolute]
dependencies: dependencies:
replace: replace:
llvm-21-dev: llvm-21-dev:native <!stage1> llvm-21-dev: llvm-21-dev:native <!stage1>
@@ -29,15 +31,17 @@ quirks:
# Add more packages and their quirks as needed # Add more packages and their quirks as needed
# example-package: # example-package:
# pull: # pull:
# method: archive # - series: [noble]
# package_directory:
# - linux-main
# deb: # deb:
# series: [noble] # - series: [resolute]
# dependencies: # dependencies:
# replace: # replace:
# old-dep: new-dep (>= 2) [linux-any] # llvm-21-dev: llvm-21-dev:native <!stage1>
# inject: # - series: [stonking]
# - missing-dep # dependencies:
# drop: # replace:
# - broken-dep # llvm-22-dev: llvm-22-dev:native <!stage1>
# parameters: # parameters:
# key: value # key: value
+2 -1
View File
@@ -231,9 +231,10 @@ pub async fn build(
return Err("Could not install essential packages for the build".into()); return Err("Could not install essential packages for the build".into());
} }
// Find the actual package directory
// Find the actual package directory // Find the actual package directory
let package_dir = let package_dir =
crate::deb::find_package_directory(Path::new(build_root), package, version, &ctx)?; crate::deb::find_package_directory(Path::new(build_root), package, version, series, &ctx)?;
let package_dir_str = package_dir let package_dir_str = package_dir
.to_str() .to_str()
.ok_or("Invalid package directory path")?; .ok_or("Invalid package directory path")?;
+9 -3
View File
@@ -337,10 +337,11 @@ pub(crate) fn find_package_directory(
parent_dir: &Path, parent_dir: &Path,
package: &str, package: &str,
version: &str, version: &str,
series: &str,
ctx: &context::Context, ctx: &context::Context,
) -> Result<PathBuf, Box<dyn Error>> { ) -> Result<PathBuf, Box<dyn Error>> {
// Check quirks first for custom package directories // Check quirks first for custom package directories
let custom_dirs = crate::quirks::get_package_directories(package); let custom_dirs = crate::quirks::get_package_directories(package, series);
for custom_dir in custom_dirs { for custom_dir in custom_dirs {
let package_dir = parent_dir.join(&custom_dir); let package_dir = parent_dir.join(&custom_dir);
if ctx.exists(&package_dir)? && ctx.exists(&package_dir.join("debian"))? { if ctx.exists(&package_dir)? && ctx.exists(&package_dir.join("debian"))? {
@@ -533,8 +534,13 @@ mod tests {
let ctx = Arc::new(Context::new(crate::context::ContextConfig::Local).unwrap()); let ctx = Arc::new(Context::new(crate::context::ContextConfig::Local).unwrap());
// Change directory to the package directory // Change directory to the package directory
let cwd = let cwd = crate::deb::find_package_directory(
crate::deb::find_package_directory(cwd, package, &package_info.stanza.version, &ctx) cwd,
package,
&package_info.stanza.version,
series,
&ctx,
)
.expect("Cannot find package directory"); .expect("Cannot find package directory");
log::debug!("Package directory: {}", cwd.display()); log::debug!("Package directory: {}", cwd.display());
+57 -38
View File
@@ -57,15 +57,19 @@ pub struct OperationQuirks {
} }
/// Quirks for a specific package /// Quirks for a specific package
///
/// `pull` and `deb` hold one entry per scope: an operation can carry
/// several entries with different `series` lists; every matching entry
/// applies, in file order.
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PackageQuirks { pub struct PackageQuirks {
/// Quirks to apply during pull operation /// Quirks to apply during pull operation
#[serde(default)] #[serde(default)]
pub pull: Option<OperationQuirks>, pub pull: Vec<OperationQuirks>,
/// Quirks to apply during deb operation /// Quirks to apply during deb operation
#[serde(default)] #[serde(default)]
pub deb: Option<OperationQuirks>, pub deb: Vec<OperationQuirks>,
} }
/// Top-level quirks configuration /// Top-level quirks configuration
@@ -102,49 +106,64 @@ fn entry_applies_to_series(quirks: &OperationQuirks, series: &str) -> bool {
/// Get the build-dependency resolution rules of a package for a series /// Get the build-dependency resolution rules of a package for a series
/// ///
/// Every deb entry whose series list matches contributes its rules; the
/// returned rules apply in file order.
///
/// # Arguments /// # Arguments
/// * `package` - The package name /// * `package` - The package name
/// * `series` - The distribution series (e.g. "resolute") /// * `series` - The distribution series (e.g. "resolute")
/// ///
/// # Returns /// # Returns
/// * `Option<DependencyQuirks>` - The rules, or None when the package has /// * `Vec<DependencyQuirks>` - The matching rules, empty when the package
/// no deb entry or the entry does not apply to the series /// has no deb entry or none applies to the series
pub fn get_deb_dependency_quirks(package: &str, series: &str) -> Option<DependencyQuirks> { pub fn get_deb_dependency_quirks(package: &str, series: &str) -> Vec<DependencyQuirks> {
let quirks = get_package_quirks(&QUIRKS_DATA, package)?; let Some(quirks) = get_package_quirks(&QUIRKS_DATA, package) else {
let deb = quirks.deb.as_ref()?; return Vec::new();
if entry_applies_to_series(deb, series) { };
deb.dependencies.clone() quirks
} else { .deb
None .iter()
} .filter(|deb| entry_applies_to_series(deb, series))
.filter_map(|deb| deb.dependencies.clone())
.collect()
} }
/// Get package directories from quirks configuration /// Get package directories from quirks configuration
/// ///
/// This function returns the list of custom package directories to try /// This function returns the list of custom package directories to try
/// when looking for the package source directory. /// when looking for the package source directory: every matching deb
/// entry contributes its directories, falling back to the pull entries
/// when no deb entry carries any.
/// ///
/// # Arguments /// # Arguments
/// * `package` - The package name /// * `package` - The package name
/// * `series` - The distribution series (e.g. "resolute")
/// ///
/// # Returns /// # Returns
/// * `Vec<String>` - List of package directories to try, or empty vector if none /// * `Vec<String>` - List of package directories to try, or empty vector if none
pub fn get_package_directories(package: &str) -> Vec<String> { pub fn get_package_directories(package: &str, series: &str) -> Vec<String> {
if let Some(quirks) = get_package_quirks(&QUIRKS_DATA, package) { let Some(quirks) = get_package_quirks(&QUIRKS_DATA, package) else {
// Check deb quirks first, then pull quirks return Vec::new();
if let Some(deb_quirks) = &quirks.deb };
&& !deb_quirks.package_directory.is_empty()
{
return deb_quirks.package_directory.clone();
}
if let Some(pull_quirks) = &quirks.pull
&& !pull_quirks.package_directory.is_empty()
{
return pull_quirks.package_directory.clone();
}
}
Vec::new() let mut directories = Vec::new();
for deb in quirks
.deb
.iter()
.filter(|q| entry_applies_to_series(q, series))
{
directories.extend(deb.package_directory.iter().cloned());
}
if directories.is_empty() {
for pull in quirks
.pull
.iter()
.filter(|q| entry_applies_to_series(q, series))
{
directories.extend(pull.package_directory.iter().cloned());
}
}
directories
} }
/// Apply the dependency quirks of `package` in `series` to parsed /// Apply the dependency quirks of `package` in `series` to parsed
@@ -160,10 +179,10 @@ pub fn apply_dependency_quirks(
clauses: &mut Vec<Vec<PkgRelation>>, clauses: &mut Vec<Vec<PkgRelation>>,
opts: &ParseOpts, opts: &ParseOpts,
) -> Result<(), String> { ) -> Result<(), String> {
let Some(deps) = get_deb_dependency_quirks(package, series) else { for deps in get_deb_dependency_quirks(package, series) {
return Ok(()); apply_rules(clauses, &deps, opts)?;
}; }
apply_rules(clauses, &deps, opts) Ok(())
} }
/// Apply one set of dependency rules to parsed clauses. /// Apply one set of dependency rules to parsed clauses.
@@ -236,8 +255,8 @@ mod tests {
fn test_unknown_package_has_no_quirks() { fn test_unknown_package_has_no_quirks() {
// A package absent from quirks.yml has no dependency rules nor // A package absent from quirks.yml has no dependency rules nor
// custom directories, and must not panic // custom directories, and must not panic
assert!(get_deb_dependency_quirks("not-in-quirks", "resolute").is_none()); assert!(get_deb_dependency_quirks("not-in-quirks", "resolute").is_empty());
assert!(get_package_directories("not-in-quirks").is_empty()); assert!(get_package_directories("not-in-quirks", "resolute").is_empty());
} }
/// The linux dependency quirks are scoped to the series they were /// The linux dependency quirks are scoped to the series they were
@@ -245,13 +264,13 @@ mod tests {
#[test] #[test]
fn linux_dependency_quirks_are_series_scoped() { fn linux_dependency_quirks_are_series_scoped() {
for package in ["linux", "linux-riscv"] { for package in ["linux", "linux-riscv"] {
let deps = let rules = get_deb_dependency_quirks(package, "resolute");
get_deb_dependency_quirks(package, "resolute").expect("the resolute entry applies"); assert_eq!(rules.len(), 1, "the resolute entry applies");
assert_eq!( assert_eq!(
deps.replace.get("llvm-21-dev").map(String::as_str), rules[0].replace.get("llvm-21-dev").map(String::as_str),
Some("llvm-21-dev:native <!stage1>") Some("llvm-21-dev:native <!stage1>")
); );
assert!(get_deb_dependency_quirks(package, "noble").is_none()); assert!(get_deb_dependency_quirks(package, "noble").is_empty());
} }
} }