feat: dependency resolver (Phase 1) + formatter + doctor + assertion evaluation
Resolver (Phase 1): - resolve::resolve() takes SystemConfig + Registry → BuildPlan - Three-layer feature/config merge (uses config::resolve_package) - Feature-gated dependencies: deps with feature=X skipped if feature disabled - Topological sort via Kahn's algorithm (BFS on in-degree) - Cycle detection, missing package warnings - CLI: kappa resolve <config> [<package>] Formatter: - format_package() + format_config() → canonical output - Consistent 4-space indent, canonical declaration order Doctor: - check_package() + check_config() → warnings for common issues - Missing fields, empty configs, root shell, feature warnings Assertion evaluation: - evaluate_assertions() resolves dotted field paths - Supports == and != operators for config validation - kappa validate now runs assertion checks Review fixes: - plan.steps.empty() exit code corrected to 0 - Removed unused <unordered_set> include - Eliminated duplicate merge logic (uses config::resolve_package)
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
#include "kappa/resolve/plan.hpp"
|
||||
#include "kappa/config/merge.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
|
||||
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.features = resolved.features;
|
||||
step.config = resolved.config;
|
||||
|
||||
for (auto& dep : pkg.depends) {
|
||||
if (!dep.feature.empty()) {
|
||||
auto fit = step.features.find(dep.feature);
|
||||
if (fit == step.features.end() || !fit->second.enabled) {
|
||||
continue; // feature-gated and disabled
|
||||
}
|
||||
}
|
||||
step.dependencies.push_back({dep.name, dep.version});
|
||||
}
|
||||
|
||||
name_to_idx[step.name] = nodes.size();
|
||||
nodes.push_back(std::move(step));
|
||||
}
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user