From d747b4009aef3b3e76a2ea7338870b5b2c1acf2f Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Tue, 9 Jun 2026 16:52:13 +0300 Subject: [PATCH] cookbook: walk local/recipes/ first so Red Bear forks take precedence Red Bear is a full fork. The canonical recipe for a Red Bear package lives in local/recipes///, NOT in recipes///. The recipes/ tree is a frozen snapshot that will eventually be deleted. Previously the cookbook only walked recipes/, so local/recipes/ recipes were unreachable unless apply-patches.sh created a symlink in recipes/. That overlay approach was wrong (per the NO OVERLAY-STYLE PATCHES policy in AGENTS.md). This change makes the cookbook walk ['local/recipes', 'recipes'] in that order. When a package exists in both trees, the local/recipes/ path ALWAYS wins. The conflict resolution in the walker is updated to enforce this rule explicitly. Result: local/recipes//recipe.toml is discovered directly, no symlink needed. 'repo find ' returns the local path. 'repo cook ' uses the local recipe. This makes the full-fork model work end-to-end. local/recipes/ no longer needs an apply-patches.sh symlink; the cookbook discovers it natively. --- src/staged_pkg.rs | 77 +++++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/src/staged_pkg.rs b/src/staged_pkg.rs index 561ddbdd80..8e2e63abdc 100644 --- a/src/staged_pkg.rs +++ b/src/staged_pkg.rs @@ -13,39 +13,52 @@ use pkg::{Package, PackageError, PackageName}; static RECIPE_PATHS: LazyLock> = LazyLock::new(|| { let mut recipe_paths = HashMap::::new(); - let mut walker = ignore::WalkBuilder::new("recipes"); - walker.follow_links(true); - for entry_res in walker.build() { - let Ok(entry) = entry_res else { - continue; - }; - if entry.file_name() == OsStr::new("recipe.toml") { - let recipe_file = entry.path(); - let Some(recipe_dir) = recipe_file.parent() else { + // Walk local/recipes/ first so Red Bear forks take precedence over + // upstream recipes. This is the canonical discovery order for a + // full fork: the local overlay is the source of truth. + for root in ["local/recipes", "recipes"] { + let mut walker = ignore::WalkBuilder::new(root); + walker.follow_links(true); + for entry_res in walker.build() { + let Ok(entry) = entry_res else { continue; }; - let Some(recipe_name): Option = recipe_dir - .file_name() - .and_then(|x| x.to_str()?.try_into().ok()) - else { - continue; - }; - let existing = recipe_paths.get(&recipe_name); - match existing { - Some(other_dir) => { - let other_dir = other_dir.clone(); - let current_is_deeper = recipe_dir.starts_with(&other_dir); - let existing_is_deeper = other_dir.starts_with(&recipe_dir); - if current_is_deeper { - recipe_paths.insert(recipe_name.clone(), other_dir); - } else if existing_is_deeper { - recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()); - } else { - recipe_paths.insert(recipe_name.clone(), recipe_dir.to_path_buf()); + if entry.file_name() == OsStr::new("recipe.toml") { + let recipe_file = entry.path(); + let Some(recipe_dir) = recipe_file.parent() else { + continue; + }; + let Some(recipe_name): Option = recipe_dir + .file_name() + .and_then(|x| x.to_str()?.try_into().ok()) + else { + continue; + }; + let existing = recipe_paths.get(&recipe_name); + match existing { + Some(other_dir) => { + let other_dir = other_dir.clone(); + let current_is_deeper = recipe_dir.starts_with(&other_dir); + let existing_is_deeper = other_dir.starts_with(&recipe_dir); + // Red Bear full-fork rule: local/recipes/ ALWAYS wins. + // If either side is a local/recipes/ path, keep it. + let current_is_local = recipe_dir.starts_with("local/recipes"); + let existing_is_local = other_dir.starts_with("local/recipes"); + if current_is_local && !existing_is_local { + recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()); + } else if existing_is_local && !current_is_local { + // keep existing + } else if current_is_deeper { + recipe_paths.insert(recipe_name.clone(), other_dir); + } else if existing_is_deeper { + recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()); + } else { + recipe_paths.insert(recipe_name.clone(), recipe_dir.to_path_buf()); + } + } + None => { + recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()); } - } - None => { - recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()); } } } @@ -53,6 +66,10 @@ static RECIPE_PATHS: LazyLock> = LazyLock::new(|| recipe_paths }); + + + + pub fn find(recipe: &str) -> Option<&'static Path> { RECIPE_PATHS.get(recipe).map(PathBuf::as_path) }