feat: wire rebuild pipeline (scheduler \u2192 build \u2192 install \u2192 services \u2192 bootloader)

The rebuild subcommand now executes the full pipeline:
- build_registry() -> resolve() -> sched::run()
- install::install() for each built package
- service::install_service() for enabled services
- boot::install_bootloader_config() for bootloader config
- system::write_hostname() / write_timezone() for system activation

Added --dry-run flag for report-only behavior (backward compat with
test suite). Added -w flag for worker parallelism and --dry-run to help.
Fixed .gitignore: 'kappa' -> '/kappa' (was matching include/kappa/,
preventing header files from being tracked since initial commit).
This commit also adds all previously-gitignored header files.
This commit is contained in:
2026-07-31 08:38:53 -04:00
parent 7e93db2d07
commit 486a476b90
17 changed files with 518 additions and 43 deletions
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include <string>
#include <string_view>
#include "kappa/boot/types.hpp"
namespace kappa::boot {
struct BootSpec {
std::string kernel_path; // e.g. "/kappa/boot/kernel"
std::string init_path; // e.g. "/kappa/boot/init" (the symlink, stable across init swaps)
std::string root; // e.g. "/dev/sda1" or "PARTUUID=xxx"
std::string kernel_params; // extra kernel cmdline params
std::string init_prev; // previous init symlink for fallback entry; empty = no fallback
};
std::string generate_bootloader_config(Bootloader bl, const BootSpec& spec);
std::string generate_limine_config(const BootSpec& spec);
std::string generate_grub_config(const BootSpec& spec);
struct BootloaderInstallResult {
bool ok;
std::string path;
std::string error;
};
BootloaderInstallResult install_bootloader_config(Bootloader bl,
const BootSpec& spec,
std::string_view prefix = "/");
} // namespace kappa::boot
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace kappa::boot {
enum class Bootloader : std::uint8_t {
Grub,
Limine,
Unknown
};
Bootloader parse_bootloader(std::string_view name);
std::string_view to_string(Bootloader bl);
bool is_supported(std::string_view name);
std::vector<Bootloader> all_bootloaders();
std::string_view bootloader_description(Bootloader bl);
struct BootloaderPaths {
std::string config_path;
std::string install_cmd;
};
BootloaderPaths bootloader_paths(Bootloader bl, std::string_view prefix = "/");
} // namespace kappa::boot
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "kappa/dsl/ast.hpp"
#include "kappa/dsl/system.hpp"
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace kappa::config {
struct AssertFailure {
std::string message;
std::string field;
std::string expected;
std::string actual;
};
std::string resolve_field(const dsl::SystemConfig& cfg, std::string_view path);
std::vector<AssertFailure> evaluate_assertions(const dsl::SystemConfig& cfg);
std::unordered_map<std::string, dsl::NamedService> resolve_services(
const dsl::SystemConfig& cfg,
const std::unordered_map<std::string, dsl::PackageDef>& packages);
} // namespace kappa::config
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "kappa/dsl/ast.hpp"
#include <filesystem>
#include <string>
namespace kappa::fetch {
struct FetchResult {
std::filesystem::path work_dir;
std::string error;
bool ok() const { return error.empty(); }
};
FetchResult fetch(const dsl::PackageDef& pkg);
} // namespace kappa::fetch
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <string>
#include <vector>
namespace kappa::fetch {
struct RecipeResult {
bool ok = false;
std::string path; // path to the cached .kap file
std::string version; // version of the fetched package
bool updated = false; // true if the cache was updated (remote was newer)
std::string error;
};
RecipeResult fetch_recipe(const std::string& name,
const std::vector<std::string>& remotes);
} // namespace kappa::fetch
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include "kappa/resolve/plan.hpp"
#include <filesystem>
#include <string>
#include <unordered_map>
#include <vector>
namespace kappa::install {
struct InstallResult {
bool ok = false;
std::string hash;
std::filesystem::path store_path;
std::string error;
};
InstallResult install(const resolve::BuildStep& step,
const std::filesystem::path& work_dir);
struct DbEntry {
std::string name;
std::string version;
std::string hash;
std::string config_hash; // FNV-1a hash of features+config, empty if unknown
std::vector<std::string> provides;
};
std::vector<DbEntry> read_installed();
bool write_installed(const std::vector<DbEntry>& entries);
bool record_generation(const std::vector<std::string>& hashes, int keep);
std::string compute_config_hash(
const std::unordered_map<std::string, dsl::FeatureDef>& features,
const std::unordered_map<std::string, std::string>& config);
} // namespace kappa::install
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "kappa/dsl/system.hpp"
#include "kappa/resolve/plan.hpp"
#include <string>
#include <vector>
namespace kappa::rebuild {
struct ChangeSet {
std::vector<std::string> added;
std::vector<std::string> changed;
std::vector<std::string> removed;
bool kernel_changed = false;
bool init_changed = false;
bool services_changed = false;
bool bootloader_changed = false;
};
struct InitImpact {
std::vector<std::string> service_rebuild; // packages needing full rebuild (use ${enabledinit})
std::vector<std::string> service_only; // packages needing only service file regeneration
std::vector<std::string> skipped; // packages with no services — no action needed
};
ChangeSet compute_changes(const dsl::SystemConfig& cfg);
InitImpact compute_init_impact(const dsl::SystemConfig& cfg,
const resolve::Registry& registry);
} // namespace kappa::rebuild
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include "kappa/config/merge.hpp"
#include "kappa/dsl/ast.hpp"
#include "kappa/dsl/system.hpp"
#include <string>
#include <unordered_map>
#include <vector>
namespace kappa::resolve {
struct ResolvedDep {
std::string name;
std::string version_constraint;
};
struct BuildStep {
std::string name;
const dsl::PackageDef* package = nullptr;
config::ResolvedPackage resolved;
std::vector<ResolvedDep> dependencies;
std::string enabled_init; // boot.init value for ${enabledinit}
};
struct BuildPlan {
std::vector<BuildStep> steps;
std::vector<std::string> cycles;
std::vector<std::string> conflicts;
std::vector<std::string> missing;
};
using Registry = std::unordered_map<std::string, dsl::PackageDef>;
BuildPlan resolve(const dsl::SystemConfig& cfg, const Registry& registry);
} // namespace kappa::resolve
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "kappa/resolve/plan.hpp"
#include <string>
#include <vector>
namespace kappa::sched {
struct SchedResult {
bool ok = false;
std::vector<std::string> built;
std::vector<std::string> failed;
};
// Run the build plan with worker-level (-w) and job-level (-j) parallelism.
// Workers atomically claim ready packages, build them, and notify dependents.
// The same package is never built twice — deduplicated in the dependency graph.
//
// -w N package-level parallelism (concurrent package builds)
// -j N job-level parallelism (make -jN per package)
//
// The scheduler uses depth-based priority groups (Beta → Alpha → Zeta)
// so that deep dependencies unblock as many packages as possible first.
SchedResult run(const resolve::BuildPlan& plan,
const std::string& work_root,
int workers = 1,
int jobs = 1);
} // namespace kappa::sched
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include "kappa/dsl/ast.hpp"
#include "kappa/service/types.hpp"
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace kappa::service {
struct ServiceSpec {
std::string name;
std::string description;
std::string exec;
std::string type;
std::string user;
std::vector<int> ports;
std::unordered_map<std::string, std::string> env;
std::string after;
std::string restart_policy;
std::string working_dir;
static ServiceSpec from_service_init(const dsl::NamedService& ns);
};
std::string generate_systemd_service(const ServiceSpec& spec);
std::string generate_s6_service(const ServiceSpec& spec);
std::string generate_openrc_service(const ServiceSpec& spec);
std::string generate_dinit_service(const ServiceSpec& spec);
std::string generate_runit_service(const ServiceSpec& spec);
std::string generate_service_file(InitSystem is, const ServiceSpec& spec);
struct ServiceInstallResult {
bool ok;
std::string path;
std::string error;
};
ServiceInstallResult install_service(
InitSystem is,
const ServiceSpec& spec,
std::string_view prefix = "/");
} // namespace kappa::service
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace kappa::service {
enum class InitSystem : std::uint8_t {
Systemd,
OpenRC,
S6,
Runit,
Dinit,
Unknown
};
InitSystem parse_init_system(std::string_view name);
std::string_view to_string(InitSystem is);
bool is_supported(std::string_view name);
std::vector<InitSystem> all_systems();
std::string_view init_description(InitSystem is);
struct InitPaths {
std::string service_dir;
std::string enable_cmd;
std::string disable_cmd;
};
InitPaths init_paths(InitSystem is, std::string_view prefix = "/");
// Shebangs for s6 execline-based run scripts.
// Extracted here so backends share a single definition.
inline constexpr std::string_view s6_execline_shebang = "#!/bin/execlineb -P\n";
} // namespace kappa::service
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "kappa/dsl/system.hpp"
#include <string>
namespace kappa::system {
struct ActivateResult {
bool ok;
std::string error;
};
ActivateResult write_hostname(const std::string& hostname,
std::string_view prefix = "/");
ActivateResult write_timezone(const std::string& timezone,
std::string_view prefix = "/");
} // namespace kappa::system
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "kappa/dsl/ast.hpp"
#include "kappa/dsl/system.hpp"
#include <string>
#include <vector>
namespace kappa::tools {
enum class DiagSeverity { Warning, Error };
struct Diagnostic {
DiagSeverity severity;
std::string message;
};
std::vector<Diagnostic> check_package(const dsl::PackageDef& pkg);
std::vector<Diagnostic> check_config(const dsl::SystemConfig& cfg);
} // namespace kappa::tools
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "kappa/dsl/ast.hpp"
#include "kappa/dsl/system.hpp"
#include <ostream>
namespace kappa::tools {
void format_package(std::ostream& os, const dsl::PackageDef& pkg);
void format_config(std::ostream& os, const dsl::SystemConfig& cfg);
} // namespace kappa::tools
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <string>
#include <string_view>
namespace kappa::util {
std::string to_lower(std::string_view sv);
std::string shell_escape(std::string_view s);
}