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/<category>/<pkg>/, NOT in recipes/<category>/<pkg>/. 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/<pkg>/recipe.toml is discovered directly, no symlink needed. 'repo find <pkg>' returns the local path. 'repo cook <pkg>' 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.
This commit is contained in:
+47
-30
@@ -13,39 +13,52 @@ use pkg::{Package, PackageError, PackageName};
|
||||
|
||||
static RECIPE_PATHS: LazyLock<HashMap<PackageName, PathBuf>> = LazyLock::new(|| {
|
||||
let mut recipe_paths = HashMap::<PackageName, PathBuf>::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<PackageName> = 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<PackageName> = 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<HashMap<PackageName, PathBuf>> = LazyLock::new(||
|
||||
recipe_paths
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
pub fn find(recipe: &str) -> Option<&'static Path> {
|
||||
RECIPE_PATHS.get(recipe).map(PathBuf::as_path)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user