fix: topological sort cookbook dependency resolution
- Added Kahn's algorithm topological sort to new_recursive() in src/recipe.rs - BFS previously returned flat dependency list with dependents before deps, causing stage.pkgar 'Not Found' when cooking in list order - Now deps always cook before dependents (kdecoration before breeze, kf6-extra-cmake-modules before kde-cli-tools, etc.) - Falls back to original order on dependency cycles - Verified: kdecoration, kwin, plasma-wayland-protocols, KF6 packages all cook successfully in correct dependency order - kirigami still fails (needs Qt6 QML headers — known QML gate)
This commit is contained in:
+57
-2
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::{BTreeSet, VecDeque},
|
||||
collections::{BTreeSet, BTreeMap, VecDeque},
|
||||
convert::TryInto,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
@@ -363,7 +363,11 @@ impl CookRecipe {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(recipes)
|
||||
// Topological sort: ensure dependencies come before dependents.
|
||||
// Without this, the flat BFS list has dependents before their deps,
|
||||
// causing stage.pkgar "Not Found" failures when cooking in list order.
|
||||
let sorted = topological_sort(recipes)?;
|
||||
Ok(sorted)
|
||||
}
|
||||
|
||||
pub fn get_build_deps_recursive(
|
||||
@@ -521,6 +525,57 @@ impl CookRecipe {
|
||||
}
|
||||
}
|
||||
|
||||
/// Topologically sort a list of CookRecipes so that dependencies always
|
||||
/// appear before the recipes that depend on them. Uses Kahn's algorithm.
|
||||
/// Falls back to original order on cycles.
|
||||
fn topological_sort(recipes: Vec<CookRecipe>) -> Result<Vec<CookRecipe>, PackageError> {
|
||||
if recipes.len() <= 1 {
|
||||
return Ok(recipes);
|
||||
}
|
||||
|
||||
let name_to_idx: BTreeMap<PackageName, usize> = recipes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, r)| (r.name.clone(), i))
|
||||
.collect();
|
||||
|
||||
let mut dep_counts: Vec<usize> = vec![0; recipes.len()];
|
||||
let mut reverse_deps: Vec<Vec<usize>> = vec![Vec::new(); recipes.len()];
|
||||
|
||||
for (i, recipe) in recipes.iter().enumerate() {
|
||||
for dep in &recipe.recipe.build.dependencies {
|
||||
if let Some(&dep_idx) = name_to_idx.get(dep) {
|
||||
dep_counts[i] = dep_counts[i].saturating_add(1);
|
||||
reverse_deps[dep_idx].push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut queue: VecDeque<usize> = VecDeque::new();
|
||||
for (i, &count) in dep_counts.iter().enumerate() {
|
||||
if count == 0 {
|
||||
queue.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
let mut sorted = Vec::with_capacity(recipes.len());
|
||||
while let Some(idx) = queue.pop_front() {
|
||||
sorted.push(recipes[idx].clone());
|
||||
for &dep_idx in &reverse_deps[idx] {
|
||||
dep_counts[dep_idx] = dep_counts[dep_idx].saturating_sub(1);
|
||||
if dep_counts[dep_idx] == 0 {
|
||||
queue.push_back(dep_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sorted.len() != recipes.len() {
|
||||
return Ok(recipes);
|
||||
}
|
||||
|
||||
Ok(sorted)
|
||||
}
|
||||
|
||||
// TODO: Wrap these vectors in a struct
|
||||
|
||||
pub fn recipes_mark_as_deps(names: &[PackageName], packages: &mut Vec<CookRecipe>) {
|
||||
|
||||
Reference in New Issue
Block a user