From f06403c5d5d8366b95d63d73b8d5aafc50a6d50e Mon Sep 17 00:00:00 2001 From: Mohammed Naser Date: Mon, 21 Sep 2026 22:58:36 +0300 Subject: [PATCH 1/3] Validate ignored build dependencies --- src/bin/repo/main.rs | 8 +++- src/recipe.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/bin/repo/main.rs b/src/bin/repo/main.rs index 8b95e1065..17797e682 100644 --- a/src/bin/repo/main.rs +++ b/src/bin/repo/main.rs @@ -11,7 +11,11 @@ use cookbook::cook::tree::{self, DisplayOptions, TreeData, TreeItem, TreeOptions use cookbook::cook::tui::join_logs; use cookbook::cook::{fetch_repo, ident}; use cookbook::recipe::{ - CookRecipe, SourceRecipe, recipes_flatten_package_names, recipes_mark_as_deps, + CookRecipe, + SourceRecipe, + recipes_flatten_package_names, + recipes_mark_as_deps, + validate_filesystem_rules, }; use cookbook::{Error, Result, staged_pkg}; use pkg::{PackageName, PackageState}; @@ -764,6 +768,8 @@ fn parse_args(args: Vec) -> Result<(CliConfig, CliCommand, Vec) -> Vec crate::Result<()> { + let ignored: BTreeSet = recipes + .iter() + .filter(|r| r.rule == "ignore") + .map(|r| r.canon_recipe_name().without_prefix().to_string()) + .collect(); + if ignored.is_empty() { + return Ok(()); + } + for recipe in recipes { + if recipe.rule == "ignore" { + continue; + } + if recipe.recipe.build.kind == BuildKind::None + || recipe.recipe.build.kind == BuildKind::Remote + { + // Not built from source, so build dependencies are irrelevant. + continue; + } + for dep in recipe + .recipe + .build + .dependencies + .iter() + .chain(recipe.recipe.build.dev_dependencies.iter()) + { + if ignored.contains(&dep.without_prefix().to_string()) { + bail_other_err!( + "Invalid filesystem config: {:?} is configured as \"ignore\" but is required as a build dependency by {:?}", + dep.without_prefix().to_string(), + recipe.name.as_str(), + ); + } + } + } + Ok(()) +} #[derive(Serialize, Deserialize)] pub struct AutoDeps { @@ -799,4 +838,58 @@ mod tests { } ); } + + #[test] + fn ignore_required_by_build_dep_is_rejected() { + use crate::recipe::{BuildKind, BuildRecipe, CookRecipe, validate_filesystem_rules}; + + let mut ignored = CookRecipe::dummy(&PackageName::new("libogg").unwrap()); + ignored.rule = "ignore".into(); + + let mut dependent = CookRecipe::dummy(&PackageName::new("libvorbis").unwrap()); + dependent.recipe.build = BuildRecipe::new(BuildKind::Custom { + script: "make".to_string(), + }); + dependent.recipe.build.dependencies = vec![PackageName::new("libogg").unwrap()]; + + let err = validate_filesystem_rules(&[ignored, dependent]).unwrap_err(); + let msg = format!("{}", err); + assert!( + msg.contains("libogg") && msg.contains("libvorbis"), + "unexpected error: {msg}" + ); + } + + #[test] + fn ignore_required_by_dev_dep_is_rejected() { + use crate::recipe::{BuildKind, BuildRecipe, CookRecipe, validate_filesystem_rules}; + + let mut ignored = CookRecipe::dummy(&PackageName::new("libogg").unwrap()); + ignored.rule = "ignore".into(); + + let mut dependent = CookRecipe::dummy(&PackageName::new("libvorbis").unwrap()); + dependent.recipe.build = BuildRecipe::new(BuildKind::Custom { + script: "make".to_string(), + }); + dependent.recipe.build.dev_dependencies = vec![PackageName::new("libogg").unwrap()]; + + let err = validate_filesystem_rules(&[ignored, dependent]).unwrap_err(); + assert!(format!("{}", err).contains("libogg")); + } + + #[test] + fn ignore_leaf_is_allowed() { + use crate::recipe::{BuildKind, BuildRecipe, CookRecipe, validate_filesystem_rules}; + + // e.g. pruning `orbterm` from a metapackage: nothing references it. + let mut ignored = CookRecipe::dummy(&PackageName::new("orbterm").unwrap()); + ignored.rule = "ignore".into(); + + let mut meta = CookRecipe::dummy(&PackageName::new("desktop-minimal").unwrap()); + meta.recipe.build = BuildRecipe::new(BuildKind::None); + meta.recipe.package.dependencies = vec![PackageName::new("orbital").unwrap()]; + + assert!(validate_filesystem_rules(&[ignored, meta]).is_ok()); + assert!(validate_filesystem_rules(&[]).is_ok()); + } } From 9b229ca5b7c67e6d02b57f4b1c30a3794e4469b6 Mon Sep 17 00:00:00 2001 From: Mohammed Naser Date: Mon, 21 Sep 2026 23:10:23 +0300 Subject: [PATCH 2/3] run cargo fmt --- src/bin/repo/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/bin/repo/main.rs b/src/bin/repo/main.rs index 17797e682..98b2bfa4e 100644 --- a/src/bin/repo/main.rs +++ b/src/bin/repo/main.rs @@ -11,10 +11,7 @@ use cookbook::cook::tree::{self, DisplayOptions, TreeData, TreeItem, TreeOptions use cookbook::cook::tui::join_logs; use cookbook::cook::{fetch_repo, ident}; use cookbook::recipe::{ - CookRecipe, - SourceRecipe, - recipes_flatten_package_names, - recipes_mark_as_deps, + CookRecipe, SourceRecipe, recipes_flatten_package_names, recipes_mark_as_deps, validate_filesystem_rules, }; use cookbook::{Error, Result, staged_pkg}; From d8e5f45a392f6830f248a691cbe2b764ae58a995 Mon Sep 17 00:00:00 2001 From: Mohammed Naser Date: Wed, 23 Sep 2026 14:49:14 +0300 Subject: [PATCH 3/3] Report all violation in recipe --- src/recipe.rs | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/recipe.rs b/src/recipe.rs index 3073d21ad..a66e45249 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -657,6 +657,7 @@ pub fn validate_filesystem_rules(recipes: &[CookRecipe]) -> crate::Result<()> { // Not built from source, so build dependencies are irrelevant. continue; } + let mut ignored_dependencies: Vec<&str> = Vec::new(); for dep in recipe .recipe .build @@ -664,14 +665,19 @@ pub fn validate_filesystem_rules(recipes: &[CookRecipe]) -> crate::Result<()> { .iter() .chain(recipe.recipe.build.dev_dependencies.iter()) { - if ignored.contains(&dep.without_prefix().to_string()) { - bail_other_err!( - "Invalid filesystem config: {:?} is configured as \"ignore\" but is required as a build dependency by {:?}", - dep.without_prefix().to_string(), - recipe.name.as_str(), - ); + let dep = dep.without_prefix(); + + if ignored.contains(dep) { + ignored_dependencies.push(dep); } } + if !ignored_dependencies.is_empty() { + bail_other_err!( + "Invalid filesystem config: the following packages are configured as \"ignore\" but are required as build dependencies by {:?}: {:?}", + ignored_dependencies, + recipe.name.as_str(), + ); + } } Ok(()) } @@ -859,7 +865,35 @@ mod tests { "unexpected error: {msg}" ); } + #[test] + fn ignore_multiple_required_by_build_dep_are_rejected() { + use crate::recipe::{BuildKind, BuildRecipe, CookRecipe, validate_filesystem_rules}; + let mut ignored_ogg = CookRecipe::dummy(&PackageName::new("libogg").unwrap()); + ignored_ogg.rule = "ignore".into(); + + let mut ignored_foo = CookRecipe::dummy(&PackageName::new("libfoo").unwrap()); + ignored_foo.rule = "ignore".into(); + + let mut dependent = CookRecipe::dummy(&PackageName::new("libvorbis").unwrap()); + + dependent.recipe.build = BuildRecipe::new(BuildKind::Custom { + script: "make".to_string(), + }); + + dependent.recipe.build.dependencies = vec![ + PackageName::new("libogg").unwrap(), + PackageName::new("libfoo").unwrap(), + ]; + + let err = validate_filesystem_rules(&[ignored_ogg, ignored_foo, dependent]).unwrap_err(); + + let msg = format!("{err}"); + + assert!(msg.contains("libogg"), "unexpected error: {msg}"); + assert!(msg.contains("libfoo"), "unexpected error: {msg}"); + assert!(msg.contains("libvorbis"), "unexpected error: {msg}"); + } #[test] fn ignore_required_by_dev_dep_is_rejected() { use crate::recipe::{BuildKind, BuildRecipe, CookRecipe, validate_filesystem_rules};