From 4165623d89dcfdb85a036f0a5407e067fbab4075 Mon Sep 17 00:00:00 2001 From: Vasilito Date: Thu, 30 Apr 2026 01:14:36 +0100 Subject: [PATCH] fix: topological sort cookbook dependency resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- src/recipe.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/recipe.rs b/src/recipe.rs index d9a2d789..13f50d97 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -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) -> Result, PackageError> { + if recipes.len() <= 1 { + return Ok(recipes); + } + + let name_to_idx: BTreeMap = recipes + .iter() + .enumerate() + .map(|(i, r)| (r.name.clone(), i)) + .collect(); + + let mut dep_counts: Vec = vec![0; recipes.len()]; + let mut reverse_deps: Vec> = 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 = 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) {