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:
2026-04-30 01:14:36 +01:00
parent 5c48f631f1
commit 4165623d89
+57 -2
View File
@@ -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>) {