feat: portability, correctness, and quality improvements for v0.2
Portability (Linux distro-agnostic): - Remove hardcoded Clang compiler enforcement; GCC now builds - Add find_package(Threads REQUIRED) for older glibc - Add cmake install() target - FHS 3.0 default root: /kappa -> /usr/local/kappa - fs::path operator/ for all init/bootloader paths (fixes prefix fragility) - Multi-distro zoneinfo search (FHS, NixOS, Guix, alt) - Portable tar extraction (drop GNU-only --no-same-permissions) - Runit enable/disable commands now prefix-aware - --root CLI flag before/after subcommand, lazy directory creation - Shebang constants de-duplicated to types.hpp Correctness (race conditions, UB, corruption): - Fix CWD race in scheduler: per-child chdir() instead of process-global - Fix UB const_cast in exec_cmd/exec_capture: mutable argv buffers - Fix non-atomic installed DB writes: tmp+rename pattern - Fix read_file() no longer calls exit(1), throws instead - Fix silent catch(...) parse errors now print diagnostics - Fix rebuild false positives with config_hash change detection - Fix s6 disable_cmd copy-paste bug (was identical to enable) - Fix runit enable_cmd incomplete, disable_cmd wrong target - Fix dinit env vars: functional env-file + companion .env Quality: - Add -Wall -Wextra -Wpedantic to CMake, fix 2 pre-existing warnings - Move parse_int from error.hpp to parse_util.hpp - Fix hash verification guard checks all three hash types - Check patch return code in fetch.cpp - Add explicit system_dir creation in ensure_directories() - Add resolve to needs_dirs for build_registry() consistency - Update stale /kappa path references in examples - Remove inaccurate -Werror claim in CONTRIBUTING.md - Add build-gcc/ and agent dirs to .gitignore - Suppress clang-tidy portability-avoid-pragma-once - Fix .gitignore /kappa pattern (was matching include/kappa/) - Delete stale vcpkg_installed/ directory 54/54 tests pass. Builds on Clang and GCC with 0 warnings.
This commit is contained in:
+74
-8
@@ -1,6 +1,7 @@
|
||||
#include "kappa/install/install.hpp"
|
||||
#include "kappa/paths.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
@@ -11,6 +12,42 @@ namespace kappa::install {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::string compute_config_hash(
|
||||
const std::unordered_map<std::string, dsl::FeatureDef>& features,
|
||||
const std::unordered_map<std::string, std::string>& config) {
|
||||
// Serialize features (sorted by key): "key1=enabled/flag|key2=..."
|
||||
std::vector<std::pair<std::string_view, const dsl::FeatureDef*>> feat_sorted;
|
||||
for (auto& [k, v] : features) feat_sorted.emplace_back(k, &v);
|
||||
std::sort(feat_sorted.begin(), feat_sorted.end());
|
||||
std::string serialized;
|
||||
for (auto& [k, f] : feat_sorted) {
|
||||
if (!serialized.empty()) serialized += '|';
|
||||
serialized += k;
|
||||
serialized += '=';
|
||||
if (f->force) serialized += "force:";
|
||||
serialized += f->enabled ? "1:" : "0:";
|
||||
serialized += f->flag;
|
||||
}
|
||||
serialized += '\n';
|
||||
// Serialize config (sorted by key): "key1=val1|key2=val2"
|
||||
std::vector<std::pair<std::string_view, std::string_view>> cfg_sorted;
|
||||
for (auto& [k, v] : config) cfg_sorted.emplace_back(k, v);
|
||||
std::sort(cfg_sorted.begin(), cfg_sorted.end());
|
||||
for (auto& [k, v] : cfg_sorted) {
|
||||
serialized += k;
|
||||
serialized += '=';
|
||||
serialized += v;
|
||||
serialized += '|';
|
||||
}
|
||||
// FNV-1a 64-bit hash
|
||||
std::uint64_t h = 14695981039346656037ULL;
|
||||
for (char c : serialized) {
|
||||
h ^= static_cast<std::uint64_t>(static_cast<unsigned char>(c));
|
||||
h *= 1099511628211ULL;
|
||||
}
|
||||
return std::format("{:016x}", h);
|
||||
}
|
||||
|
||||
static std::string db_file() {
|
||||
return (fs::path(paths::db_dir()) / "installed").string();
|
||||
}
|
||||
@@ -56,8 +93,12 @@ InstallResult install(const resolve::BuildStep& step,
|
||||
}
|
||||
|
||||
auto entries = read_installed();
|
||||
std::string ch = "0000000000000000";
|
||||
if (!step.resolved.features.empty() || !step.resolved.config.empty()) {
|
||||
ch = compute_config_hash(step.resolved.features, step.resolved.config);
|
||||
}
|
||||
entries.push_back({step.name, step.resolved.original.version,
|
||||
hash, step.resolved.original.provides});
|
||||
hash, ch, step.resolved.original.provides});
|
||||
if (!write_installed(entries)) {
|
||||
result.error = "failed to write installed DB";
|
||||
return result;
|
||||
@@ -82,6 +123,19 @@ std::vector<DbEntry> read_installed() {
|
||||
std::istringstream iss(line);
|
||||
DbEntry e;
|
||||
iss >> e.name >> e.version >> e.hash;
|
||||
std::string token;
|
||||
auto pos = iss.tellg();
|
||||
if (iss >> token && token.size() == 16
|
||||
&& std::all_of(token.begin(), token.end(), [](char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
|
||||
})) {
|
||||
e.config_hash = token;
|
||||
} else {
|
||||
if (pos != std::streampos(-1)) {
|
||||
iss.clear();
|
||||
iss.seekg(pos);
|
||||
}
|
||||
}
|
||||
std::string prov;
|
||||
while (iss >> prov) { e.provides.push_back(prov); }
|
||||
entries.push_back(std::move(e));
|
||||
@@ -92,14 +146,26 @@ std::vector<DbEntry> read_installed() {
|
||||
bool write_installed(const std::vector<DbEntry>& entries) {
|
||||
std::error_code ec;
|
||||
fs::create_directories(paths::db_dir(), ec);
|
||||
std::ofstream out(db_file());
|
||||
if (!out) { return false; }
|
||||
for (auto& e : entries) {
|
||||
out << e.name << ' ' << e.version << ' ' << e.hash;
|
||||
for (auto& p : e.provides) { out << ' ' << p; }
|
||||
out << '\n';
|
||||
auto tmp = db_file() + ".tmp";
|
||||
{
|
||||
std::ofstream out(tmp);
|
||||
if (!out) return false;
|
||||
for (auto& e : entries) {
|
||||
out << e.name << ' ' << e.version << ' ' << e.hash;
|
||||
if (!e.config_hash.empty() && e.config_hash != "0000000000000000") {
|
||||
out << ' ' << e.config_hash;
|
||||
}
|
||||
for (auto& p : e.provides) { out << ' ' << p; }
|
||||
out << '\n';
|
||||
}
|
||||
out.close();
|
||||
if (out.fail()) {
|
||||
std::filesystem::remove(tmp, ec);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
std::filesystem::rename(tmp, db_file(), ec);
|
||||
return !ec;
|
||||
}
|
||||
|
||||
bool record_generation(const std::vector<std::string>& hashes, int keep) {
|
||||
|
||||
Reference in New Issue
Block a user