feat: transitive dependency resolution
CI / build-and-test (push) Successful in 39s

Resolver now pulls in transitive dependencies automatically.
Declaring 'packages { root {} }' where root→mid→leaf resolves
all three packages in correct topological order. Previously
every package had to be listed explicitly in the config.

build_registry pre-populates from $KAPPA_ROOT/cache/packages/
so transitive deps can be found without remote fetching.
This commit is contained in:
2026-08-05 07:39:36 -04:00
parent 33ff453cd1
commit 01fcb20a57
2 changed files with 70 additions and 1 deletions
+18
View File
@@ -111,6 +111,24 @@ static void handle_parse_error(const char* path,
static resolve::Registry build_registry(const dsl::SystemConfig& cfg) {
resolve::Registry registry;
// Pre-populate from cached packages so transitive deps can be resolved
auto pkg_dir = paths::packages_dir();
std::error_code ec;
if (std::filesystem::exists(pkg_dir)) {
for (auto& entry : std::filesystem::directory_iterator(pkg_dir, ec)) {
if (ec) break;
if (entry.path().extension() != ".kap") continue;
std::ifstream in(entry.path());
if (!in) continue;
std::ostringstream buf;
buf << in.rdbuf();
try {
auto pkg = dsl::parse(buf.str());
registry.try_emplace(pkg.name, std::move(pkg));
} catch (...) { continue; }
}
}
for (const auto& pref : cfg.packages) {
bool found = false;
+52 -1
View File
@@ -42,7 +42,58 @@ BuildPlan resolve(const dsl::SystemConfig& cfg, const Registry& registry) {
nodes.push_back(std::move(step));
}
// Detect conflicts between selected packages
// Transitive dependency discovery: pull in deps that are in the
// registry but not yet in the plan. Continue until no new deps
// are discovered — handles arbitrarily deep dependency chains.
bool changed = true;
while (changed) {
changed = false;
std::size_t current_size = nodes.size();
for (std::size_t i = 0; i < current_size; ++i) {
if (!nodes[i].package) continue;
for (auto& dep : nodes[i].package->depends) {
if (name_to_idx.contains(dep.name)) continue;
// Skip feature-conditional deps that aren't enabled
if (!dep.feature.empty()) {
auto fit = nodes[i].resolved.features.find(dep.feature);
if (fit == nodes[i].resolved.features.end() || !fit->second.enabled) {
continue;
}
}
auto rit = registry.find(dep.name);
if (rit == registry.end()) continue;
auto& pkg = rit->second;
auto resolved = config::resolve_package(pkg, cfg.system, {});
BuildStep step;
step.name = pkg.name;
step.package = &pkg;
step.resolved = std::move(resolved);
step.enabled_init = cfg.boot.init;
for (auto& ddep : pkg.depends) {
if (!ddep.feature.empty()) {
auto fit = step.resolved.features.find(ddep.feature);
if (fit == step.resolved.features.end() || !fit->second.enabled) {
continue;
}
}
step.dependencies.push_back({ddep.name, ddep.version});
}
name_to_idx[step.name] = nodes.size();
nodes.push_back(std::move(step));
changed = true;
}
}
}
// Detect conflicts between all selected packages (including transitive)
std::unordered_set<std::string> selected;
for (auto& node : nodes) { selected.insert(node.name); }