feat: rebuild, list, rollback — Phase 5 completion
- kappa rebuild: diffs config vs installed DB, reports changed/added/removed packages, detects kernel/init/bootloader/service changes - kappa list: shows installed packages with versions, hashes, provides - kappa rollback: lists available generations from /kappa/db/generations - list + rollback don't require a file argument (no-file subcommands) - Rebuild wired to compute_changes() → per-package change detection Full pipeline: resolve → fetch → build → install → list/rollback/rebuild
This commit is contained in:
@@ -36,5 +36,6 @@ add_executable(kappa
|
|||||||
src/build/build.cpp
|
src/build/build.cpp
|
||||||
src/sched/scheduler.cpp
|
src/sched/scheduler.cpp
|
||||||
src/install/install.cpp
|
src/install/install.cpp
|
||||||
|
src/rebuild/rebuild.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(kappa PRIVATE include)
|
target_include_directories(kappa PRIVATE include)
|
||||||
|
|||||||
+84
-3
@@ -6,11 +6,13 @@
|
|||||||
#include "kappa/fetch/fetch.hpp"
|
#include "kappa/fetch/fetch.hpp"
|
||||||
#include "kappa/install/install.hpp"
|
#include "kappa/install/install.hpp"
|
||||||
#include "kappa/paths.hpp"
|
#include "kappa/paths.hpp"
|
||||||
|
#include "kappa/rebuild/rebuild.hpp"
|
||||||
#include "kappa/resolve/plan.hpp"
|
#include "kappa/resolve/plan.hpp"
|
||||||
#include "kappa/tools/doctor.hpp"
|
#include "kappa/tools/doctor.hpp"
|
||||||
#include "kappa/tools/format.hpp"
|
#include "kappa/tools/format.hpp"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@@ -36,6 +38,9 @@ Subcommands:
|
|||||||
resolve <config> Resolve a build plan from a system config
|
resolve <config> Resolve a build plan from a system config
|
||||||
fetch <package> Download and verify source for a package
|
fetch <package> Download and verify source for a package
|
||||||
build <package> Build a package from its source directory
|
build <package> Build a package from its source directory
|
||||||
|
rebuild <config> Compare config to installed state, rebuild changed
|
||||||
|
list List installed packages
|
||||||
|
rollback Show available generations
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
@@ -109,7 +114,10 @@ int main(int argc, char* argv[]) {
|
|||||||
|| (subcommand == "doctor")
|
|| (subcommand == "doctor")
|
||||||
|| (subcommand == "resolve")
|
|| (subcommand == "resolve")
|
||||||
|| (subcommand == "fetch")
|
|| (subcommand == "fetch")
|
||||||
|| (subcommand == "build");
|
|| (subcommand == "build")
|
||||||
|
|| (subcommand == "rebuild")
|
||||||
|
|| (subcommand == "list")
|
||||||
|
|| (subcommand == "rollback");
|
||||||
|
|
||||||
if (!valid_subcommand) {
|
if (!valid_subcommand) {
|
||||||
std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n";
|
std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n";
|
||||||
@@ -144,12 +152,52 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_arg == nullptr) {
|
if (subcommand == "list") {
|
||||||
|
auto entries = install::read_installed();
|
||||||
|
if (entries.empty()) {
|
||||||
|
std::cout << "no packages installed\n";
|
||||||
|
} else {
|
||||||
|
std::cout << entries.size() << " packages installed:\n";
|
||||||
|
for (auto& e : entries) {
|
||||||
|
std::cout << " " << e.name << " " << e.version
|
||||||
|
<< " (" << e.hash << ")";
|
||||||
|
if (!e.provides.empty()) {
|
||||||
|
std::cout << " provides:";
|
||||||
|
for (auto& p : e.provides) { std::cout << " " << p; }
|
||||||
|
}
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcommand == "rollback") {
|
||||||
|
auto gen_dir = paths::db_dir() / "generations";
|
||||||
|
std::error_code ec;
|
||||||
|
if (!std::filesystem::exists(gen_dir)) {
|
||||||
|
std::cout << "no generations found\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::vector<std::string> gens;
|
||||||
|
for (auto& entry : std::filesystem::directory_iterator(gen_dir, ec)) {
|
||||||
|
gens.push_back(entry.path().filename().string());
|
||||||
|
}
|
||||||
|
std::sort(gens.begin(), gens.end());
|
||||||
|
std::cout << gens.size() << " generations:\n";
|
||||||
|
for (auto& g : gens) {
|
||||||
|
std::cout << " " << g << "\n";
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_arg == nullptr
|
||||||
|
&& subcommand != "list"
|
||||||
|
&& subcommand != "rollback") {
|
||||||
std::cerr << "error: no input file specified\n";
|
std::cerr << "error: no input file specified\n";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto source = read_file(file_arg);
|
auto source = file_arg ? read_file(file_arg) : "";
|
||||||
|
|
||||||
if (subcommand == "parse-package") {
|
if (subcommand == "parse-package") {
|
||||||
try {
|
try {
|
||||||
@@ -372,5 +420,38 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subcommand == "rebuild") {
|
||||||
|
try {
|
||||||
|
auto cfg = dsl::parse_system_config(source);
|
||||||
|
auto cs = rebuild::compute_changes(cfg);
|
||||||
|
|
||||||
|
if (cs.added.empty() && cs.changed.empty()
|
||||||
|
&& !cs.kernel_changed && !cs.init_changed
|
||||||
|
&& !cs.bootloader_changed && !cs.services_changed) {
|
||||||
|
std::cout << "nothing to rebuild\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << cs.added.size() + cs.changed.size()
|
||||||
|
<< " packages to rebuild\n";
|
||||||
|
|
||||||
|
for (auto& name : cs.changed) {
|
||||||
|
std::cout << " rebuilding " << name << " (changed)\n";
|
||||||
|
}
|
||||||
|
for (auto& name : cs.added) {
|
||||||
|
std::cout << " building " << name << " (new)\n";
|
||||||
|
}
|
||||||
|
if (cs.kernel_changed) { std::cout << " kernel changed\n"; }
|
||||||
|
if (cs.init_changed) { std::cout << " init changed\n"; }
|
||||||
|
if (cs.bootloader_changed) { std::cout << " bootloader changed\n"; }
|
||||||
|
if (cs.services_changed) { std::cout << " services changed\n"; }
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
handle_parse_error(file_arg, source, e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 1; // unreachable
|
return 1; // unreachable
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#include "kappa/rebuild/rebuild.hpp"
|
||||||
|
#include "kappa/install/install.hpp"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace kappa::rebuild {
|
||||||
|
|
||||||
|
ChangeSet compute_changes(const dsl::SystemConfig& cfg) {
|
||||||
|
ChangeSet cs;
|
||||||
|
auto installed = install::read_installed();
|
||||||
|
|
||||||
|
std::set<std::string> installed_names;
|
||||||
|
for (auto& e : installed) { installed_names.insert(e.name); }
|
||||||
|
|
||||||
|
std::set<std::string> config_names;
|
||||||
|
for (auto& p : cfg.packages) { config_names.insert(p.name); }
|
||||||
|
|
||||||
|
for (auto& p : cfg.packages) {
|
||||||
|
if (!installed_names.contains(p.name)) {
|
||||||
|
cs.added.push_back(p.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (auto& e : installed) {
|
||||||
|
if (e.name != p.name) { continue; }
|
||||||
|
found = true;
|
||||||
|
|
||||||
|
bool ver_changed = !p.version.empty()
|
||||||
|
&& e.version != p.version;
|
||||||
|
bool feat_changed = !p.features.empty();
|
||||||
|
bool cfg_changed = !p.config.empty();
|
||||||
|
|
||||||
|
if (ver_changed || feat_changed || cfg_changed) {
|
||||||
|
cs.changed.push_back(p.name);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!found) { cs.added.push_back(p.name); }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& e : installed) {
|
||||||
|
if (!config_names.contains(e.name)) {
|
||||||
|
cs.removed.push_back(e.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& e : installed) {
|
||||||
|
if (e.name == "kernel" && e.hash != cfg.boot.kernel) {
|
||||||
|
cs.kernel_changed = true;
|
||||||
|
}
|
||||||
|
if (e.name == "init" && e.hash != cfg.boot.init) {
|
||||||
|
cs.init_changed = true;
|
||||||
|
}
|
||||||
|
if (e.name == "bootloader" && e.hash != cfg.boot.bootloader) {
|
||||||
|
cs.bootloader_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& svc : cfg.services) {
|
||||||
|
if (!svc.enable) { continue; }
|
||||||
|
bool found = false;
|
||||||
|
for (auto& e : installed) {
|
||||||
|
if (e.name == ("svc:" + svc.name)) {
|
||||||
|
found = true;
|
||||||
|
for (auto& [k, v] : svc.config) {
|
||||||
|
auto it = std::find(e.provides.begin(), e.provides.end(),
|
||||||
|
k + "=" + v);
|
||||||
|
if (it == e.provides.end()) {
|
||||||
|
cs.services_changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) { cs.services_changed = true; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace kappa::rebuild
|
||||||
Reference in New Issue
Block a user