Files
kappa/src/resolve/plan.cpp
T
huntedbytheirs dd984f96d4 feat: system-agnostic package manager — 5 inits, 2 bootloaders, parallel scheduler
Complete rewrite of kappa from a sequential build tool into a
system-agnostic package manager with runtime init switching.

Core additions:
- 5 init system backends: systemd, openrc, s6, runit, dinit
  (service file generation, enable/disable, init_paths)
- 2 bootloader backends: grub, limine (config generation, fallback entries)
- Parallel scheduler with worker pool, depth-based priority (Beta/Alpha/Zeta),
  atomic claiming, dependency tracking, deduplication, and failure propagation
- Init-switch impact analysis: only rebuild packages using ${enabledinit}
- Init-agnostic service definitions: flat NamedService blocks replace
  per-init nesting
- Package conflicts: mutual incompatibility detection in resolver
- System groups: init-agnostic group creation in DSL
- Init-agnostic hostname/timezone: direct /etc/hostname and /etc/localtime writes
- Source tarball caching at /kappa/cache/ with atomic write-then-rename
- Package recipe caching with remote fetching and version comparison
- remotes = [...] block in system config for package repositories
- Auto-fetch: rebuild resolves missing packages from remotes
- uninstall phase in package definitions
- ${enabledinit} eval variable for init-conditional builds
- Shared util module (to_lower, shell_escape)
- 54 integration tests across two shell test suites
- Comprehensive README and CONTRIBUTING guide

Bug fixes from review:
- CRITICAL: Replace std::system() with fork+execvp (command injection)
- CRITICAL: Fix scheduler deadlock on successful completion
- CRITICAL: Fix rebuild init/kernel/bootloader change detection
- HIGH: Fix path traversal via unsanitized package names in cache
- HIGH: Fix TOCTOU race in cache write with atomic rename
- HIGH: Fix formatter dropping remotes/imports blocks
- HIGH: Fix formatter stripping empty-string assert values
- HIGH: Fix formatter non-idempotent output (sorted key iteration)
- HIGH: Populate ${enabledinit} from boot.init in BuildStep
- MEDIUM: Fix data race on non-atomic scheduler stop flag
- MEDIUM: Fix compute_depths() traversal direction
- MEDIUM: Add runit to doctor supported-init warning
- MEDIUM: Extract to_lower/shell_escape to shared kappa::util
- MEDIUM: Consolidate generator declarations in headers
2026-07-31 05:32:57 -04:00

97 lines
2.8 KiB
C++

#include "kappa/resolve/plan.hpp"
#include "kappa/config/merge.hpp"
#include <format>
#include <queue>
#include <unordered_set>
namespace kappa::resolve {
BuildPlan resolve(const dsl::SystemConfig& cfg, const Registry& registry) {
BuildPlan plan;
std::unordered_map<std::string, std::size_t> name_to_idx;
std::vector<BuildStep> nodes;
for (auto& pref : cfg.packages) {
auto rit = registry.find(pref.name);
if (rit == registry.end()) {
plan.missing.push_back(pref.name);
continue;
}
auto& pkg = rit->second;
auto resolved = config::resolve_package(pkg, cfg.system, pref);
BuildStep step;
step.name = pkg.name;
step.package = &pkg;
step.resolved = std::move(resolved);
step.enabled_init = cfg.boot.init;
for (auto& dep : pkg.depends) {
if (!dep.feature.empty()) {
auto fit = step.resolved.features.find(dep.feature);
if (fit == step.resolved.features.end() || !fit->second.enabled) {
continue;
}
}
step.dependencies.push_back({dep.name, dep.version});
}
name_to_idx[step.name] = nodes.size();
nodes.push_back(std::move(step));
}
// Detect conflicts between selected packages
std::unordered_set<std::string> selected;
for (auto& node : nodes) { selected.insert(node.name); }
for (auto& node : nodes) {
if (!node.package) continue;
for (auto& conflict_name : node.package->conflicts) {
if (selected.contains(conflict_name)) {
plan.conflicts.push_back(
std::format("{} conflicts with {}", node.name, conflict_name));
}
}
}
std::vector<int> in_degree(nodes.size(), 0);
std::vector<std::vector<std::size_t>> adj(nodes.size());
for (std::size_t i = 0; i < nodes.size(); ++i) {
for (auto& dep : nodes[i].dependencies) {
auto it = name_to_idx.find(dep.name);
if (it != name_to_idx.end()) {
adj[it->second].push_back(i);
in_degree[i]++;
}
}
}
std::queue<std::size_t> q;
for (std::size_t i = 0; i < nodes.size(); ++i) {
if (in_degree[i] == 0) { q.push(i); }
}
std::vector<bool> visited(nodes.size(), false);
while (!q.empty()) {
auto u = q.front(); q.pop();
visited[u] = true;
plan.steps.push_back(nodes[u]);
for (auto v : adj[u]) {
if (--in_degree[v] == 0) { q.push(v); }
}
}
for (std::size_t i = 0; i < nodes.size(); ++i) {
if (!visited[i]) {
plan.cycles.push_back(nodes[i].name);
}
}
return plan;
}
} // namespace kappa::resolve