diff --git a/CMakeLists.txt b/CMakeLists.txt index fb61683..5a6265b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,5 +36,6 @@ add_executable(kappa src/build/build.cpp src/sched/scheduler.cpp src/install/install.cpp + src/rebuild/rebuild.cpp ) target_include_directories(kappa PRIVATE include) diff --git a/src/main.cpp b/src/main.cpp index 58f1a0c..a5f13f7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,11 +6,13 @@ #include "kappa/fetch/fetch.hpp" #include "kappa/install/install.hpp" #include "kappa/paths.hpp" +#include "kappa/rebuild/rebuild.hpp" #include "kappa/resolve/plan.hpp" #include "kappa/tools/doctor.hpp" #include "kappa/tools/format.hpp" #include +#include #include #include #include @@ -36,6 +38,9 @@ Subcommands: resolve Resolve a build plan from a system config fetch Download and verify source for a package build Build a package from its source directory + rebuild Compare config to installed state, rebuild changed + list List installed packages + rollback Show available generations Options: -h, --help Show this help message @@ -109,7 +114,10 @@ int main(int argc, char* argv[]) { || (subcommand == "doctor") || (subcommand == "resolve") || (subcommand == "fetch") - || (subcommand == "build"); + || (subcommand == "build") + || (subcommand == "rebuild") + || (subcommand == "list") + || (subcommand == "rollback"); if (!valid_subcommand) { 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 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"; return 1; } - auto source = read_file(file_arg); + auto source = file_arg ? read_file(file_arg) : ""; if (subcommand == "parse-package") { 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 } diff --git a/src/rebuild/rebuild.cpp b/src/rebuild/rebuild.cpp new file mode 100644 index 0000000..faf5746 --- /dev/null +++ b/src/rebuild/rebuild.cpp @@ -0,0 +1,84 @@ +#include "kappa/rebuild/rebuild.hpp" +#include "kappa/install/install.hpp" + +#include +#include + +namespace kappa::rebuild { + +ChangeSet compute_changes(const dsl::SystemConfig& cfg) { + ChangeSet cs; + auto installed = install::read_installed(); + + std::set installed_names; + for (auto& e : installed) { installed_names.insert(e.name); } + + std::set 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