From 6c993d2d17b61e0376a09df21cccced3321415aa Mon Sep 17 00:00:00 2001 From: HuntedByTheIRS Date: Thu, 30 Jul 2026 05:14:51 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20Phase=203+4=20review=20=E2=80=94=20deter?= =?UTF-8?q?ministic=20hash,=20EINTR,=20env=20restore,=20installer=20wiring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - std::hash→FNV-1a (deterministic across runs, null-byte-separated) - waitpid EINTR retry loop (prevents zombie processes under signals) - setenv save/restore between builds (prevents cross-package env leaks) - install::install() wired into kappa build CLI (store + DB + generations) - Zero-padded generation filenames (gen-0001, lexicographic sort correct) - Hardcoded paths fixed: /tmp/kappa-build→paths::temp_dir()/build - Hardcoded destdir: /kappa/temp/destdir→paths::temp_dir()/destdir - ensure_directories() called at main() startup - build subcommand: -j N, --root wired end-to-end --- CMakeLists.txt | 3 + include/kappa/dsl/token.hpp | 3 + include/kappa/paths.hpp | 16 +++-- src/dsl/lexer.cpp | 8 ++- src/dsl/parser.cpp | 47 ++++++++----- src/dsl/system.cpp | 16 +++-- src/eval/vars.cpp | 3 +- src/fetch/fetch.cpp | 10 +-- src/install/install.cpp | 130 ++++++++++++++++++++++++++++++++++++ src/main.cpp | 59 +++++++++++++++- src/paths.cpp | 26 ++++++-- src/sched/scheduler.cpp | 27 ++++++++ 12 files changed, 308 insertions(+), 40 deletions(-) create mode 100644 src/install/install.cpp create mode 100644 src/sched/scheduler.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8070918..fb61683 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,5 +33,8 @@ add_executable(kappa src/tools/doctor.cpp src/resolve/plan.cpp src/fetch/fetch.cpp + src/build/build.cpp + src/sched/scheduler.cpp + src/install/install.cpp ) target_include_directories(kappa PRIVATE include) diff --git a/include/kappa/dsl/token.hpp b/include/kappa/dsl/token.hpp index ed6f126..648b34a 100644 --- a/include/kappa/dsl/token.hpp +++ b/include/kappa/dsl/token.hpp @@ -37,6 +37,9 @@ enum class TokenType { KwService, KwAssert, KwImport, + KwSha256, + KwSha512, + KwMd5, KwPrepare, KwBuild, KwCheck, diff --git a/include/kappa/paths.hpp b/include/kappa/paths.hpp index a9dd30a..e9fc7cd 100644 --- a/include/kappa/paths.hpp +++ b/include/kappa/paths.hpp @@ -1,15 +1,19 @@ #pragma once #include +#include +#include namespace kappa::paths { -inline const std::filesystem::path root{"/kappa"}; -inline const auto bin_dir = root / "bin"; -inline const auto temp_dir = root / "temp"; -inline const auto db_dir = root / "db"; -inline const auto system_dir = root / "system"; -inline const auto builds_dir = system_dir / "builds"; +void set_root(std::string_view path); + +std::filesystem::path root_path(); +std::filesystem::path bin_dir(); +std::filesystem::path temp_dir(); +std::filesystem::path db_dir(); +std::filesystem::path system_dir(); +std::filesystem::path builds_dir(); void ensure_directories(); diff --git a/src/dsl/lexer.cpp b/src/dsl/lexer.cpp index fa90a07..d77deae 100644 --- a/src/dsl/lexer.cpp +++ b/src/dsl/lexer.cpp @@ -20,6 +20,9 @@ static const std::unordered_map keywords = { {"patches", TokenType::KwPatches}, {"env", TokenType::KwEnv}, {"service", TokenType::KwService}, + {"sha256", TokenType::KwSha256}, + {"sha512", TokenType::KwSha512}, + {"md5", TokenType::KwMd5}, {"assert", TokenType::KwAssert}, {"import", TokenType::KwImport}, {"prepare", TokenType::KwPrepare}, @@ -55,6 +58,9 @@ std::string_view token_name(TokenType type) { case TokenType::KwPatches: return "patches"; case TokenType::KwEnv: return "env"; case TokenType::KwService: return "service"; + case TokenType::KwSha256: return "sha256"; + case TokenType::KwSha512: return "sha512"; + case TokenType::KwMd5: return "md5"; case TokenType::KwAssert: return "assert"; case TokenType::KwImport: return "import"; case TokenType::KwPrepare: return "prepare"; @@ -145,7 +151,7 @@ Token Lexer::scan_ident() { while (pos_ < source_.size()) { char c = peek(); if (std::isspace(static_cast(c))) { break; } - if (c == '"' || c == '[' || c == ']' || c == ',') { break; } + if (c == '"' || c == '=' || c == '[' || c == ']' || c == ',') { break; } lexeme += advance(); } diff --git a/src/dsl/parser.cpp b/src/dsl/parser.cpp index c52b67b..84a1e6b 100644 --- a/src/dsl/parser.cpp +++ b/src/dsl/parser.cpp @@ -123,22 +123,28 @@ void Parser::parse_body(PackageDef& pkg) { pkg.license = consume(TokenType::String).lexeme; break; - case TokenType::Ident: - if (current_.lexeme == "sha256") { - advance(); consume(TokenType::Equals); - pkg.sha256 = consume(TokenType::String).lexeme; - } else if (current_.lexeme == "sha512") { - advance(); consume(TokenType::Equals); - pkg.sha512 = consume(TokenType::String).lexeme; - } else if (current_.lexeme == "md5") { - advance(); consume(TokenType::Equals); - pkg.md5 = consume(TokenType::String).lexeme; - } else { - throw ParseError(current_.line, current_.col, - msg_unknown_decl(current_.lexeme)); - } + case TokenType::KwSha256: + consume(TokenType::KwSha256); + consume(TokenType::Equals); + pkg.sha256 = consume(TokenType::String).lexeme; break; + case TokenType::KwSha512: + consume(TokenType::KwSha512); + consume(TokenType::Equals); + pkg.sha512 = consume(TokenType::String).lexeme; + break; + + case TokenType::KwMd5: + consume(TokenType::KwMd5); + consume(TokenType::Equals); + pkg.md5 = consume(TokenType::String).lexeme; + break; + + case TokenType::Ident: + throw ParseError(current_.line, current_.col, + msg_unknown_decl(current_.lexeme)); + case TokenType::KwProvides: consume(TokenType::KwProvides); consume(TokenType::Equals); @@ -213,9 +219,10 @@ void Parser::parse_body(PackageDef& pkg) { if (at(TokenType::Newline)) { advance(); continue; } auto key = consume(TokenType::Ident).lexeme; bool soft = false; - if (at(TokenType::Ident) && current_.lexeme == "?=") { - soft = true; + if (at(TokenType::Ident) && current_.lexeme == "?") { advance(); + consume(TokenType::Equals); + soft = true; } else { consume(TokenType::Equals); } @@ -291,6 +298,10 @@ void Parser::parse_body(PackageDef& pkg) { } else { a.op = "="; } + } else if (at(TokenType::Ident) && current_.lexeme == "!") { + advance(); + consume(TokenType::Equals); + a.op = "!="; } else { a.op = current_.lexeme; advance(); } @@ -502,7 +513,9 @@ Phase Parser::parse_phase() { Command Parser::parse_command_line() { std::string cmd; while (!at(TokenType::Newline) && !at(TokenType::Rbrace) && !at(TokenType::Eof)) { - if (!cmd.empty() && !at(TokenType::Comma)) { cmd += ' '; } + if (!cmd.empty() && !at(TokenType::Comma) + && current_.type != TokenType::Equals + && !cmd.ends_with('=')) { cmd += ' '; } cmd += current_.lexeme; advance(); } diff --git a/src/dsl/system.cpp b/src/dsl/system.cpp index 50bc8d0..003dd33 100644 --- a/src/dsl/system.cpp +++ b/src/dsl/system.cpp @@ -65,6 +65,9 @@ std::string SysParser::consume_ident() { at(TokenType::KwService) || at(TokenType::KwAssert) || at(TokenType::KwImport) || + at(TokenType::KwSha256) || + at(TokenType::KwSha512) || + at(TokenType::KwMd5) || at(TokenType::KwConfig) || at(TokenType::KwFeatures) || at(TokenType::KwVersion) || @@ -137,12 +140,16 @@ SystemConfig SysParser::parse() { consume(TokenType::Ident); // ":" a.field = current_.lexeme; advance(); if (at(TokenType::Equals)) { - advance(); // first = + advance(); if (at(TokenType::Equals)) { - a.op = "=="; advance(); // second = + a.op = "=="; advance(); } else { a.op = "="; } + } else if (at(TokenType::Ident) && current_.lexeme == "!") { + advance(); + consume(TokenType::Equals); + a.op = "!="; } else { a.op = current_.lexeme; advance(); } @@ -195,9 +202,10 @@ void SysParser::parse_system_block(SystemConfig& cfg) { if (at(TokenType::Newline)) { advance(); continue; } auto k = consume_ident(); bool soft = false; - if (at(TokenType::Ident) && current_.lexeme == "?=") { - soft = true; + if (at(TokenType::Ident) && current_.lexeme == "?") { advance(); + consume(TokenType::Equals); + soft = true; } else { consume(TokenType::Equals); } diff --git a/src/eval/vars.cpp b/src/eval/vars.cpp index 9286fee..e3e2fb8 100644 --- a/src/eval/vars.cpp +++ b/src/eval/vars.cpp @@ -1,4 +1,5 @@ #include "kappa/eval/vars.hpp" +#include "kappa/paths.hpp" #include @@ -9,7 +10,7 @@ Scope make_default_scope() { s.builtins["prefix"] = "/usr"; s.builtins["jobs"] = "1"; s.builtins["jobopts"] = "-j1"; - s.builtins["destdir"] = "/kappa/temp/destdir"; + s.builtins["destdir"] = (paths::temp_dir() / "destdir").string(); s.builtins["userargs"] = ""; return s; } diff --git a/src/fetch/fetch.cpp b/src/fetch/fetch.cpp index e8ba1f7..81ebcf4 100644 --- a/src/fetch/fetch.cpp +++ b/src/fetch/fetch.cpp @@ -123,14 +123,14 @@ FetchResult fetch(const dsl::PackageDef& pkg) { } auto dest_name = pkg.name + "-" + pkg.version; - auto dest_file = fs::path(paths::temp_dir) / (dest_name + "." + ext); - result.work_dir = fs::path(paths::temp_dir) / dest_name; + auto dest_file = fs::path(paths::temp_dir()) / (dest_name + "." + ext); + result.work_dir = fs::path(paths::temp_dir()) / dest_name; if (ext == "git") { int rc = exec_cmd({"git", "clone", url, result.work_dir.string()}); if (rc != 0) { result.error = "git clone failed"; return result; } } else { - fs::create_directories(paths::temp_dir); + fs::create_directories(paths::temp_dir()); int rc = exec_cmd({"curl", "-L", "-o", dest_file.string(), url}); if (rc != 0) { result.error = "download failed"; return result; } @@ -162,7 +162,7 @@ FetchResult fetch(const dsl::PackageDef& pkg) { if (rc2 != 0) { result.error = "extraction failed"; return result; } } else { int rc2 = exec_cmd({"tar", "xf", dest_file.string(), - "-C", paths::temp_dir.string(), + "-C", paths::temp_dir().string(), "--no-same-owner", "--no-same-permissions"}); if (rc2 != 0) { result.error = "extraction failed"; return result; } } @@ -174,7 +174,7 @@ FetchResult fetch(const dsl::PackageDef& pkg) { auto patch_path = patch.url; auto patch_file = patch_path; if (patch_path.starts_with("http")) { - auto local = fs::path(paths::temp_dir) + auto local = fs::path(paths::temp_dir()) / fs::path(patch_path).filename(); int rc = exec_cmd({"curl", "-L", "-o", local.string(), patch_path}); diff --git a/src/install/install.cpp b/src/install/install.cpp new file mode 100644 index 0000000..8ea6c6c --- /dev/null +++ b/src/install/install.cpp @@ -0,0 +1,130 @@ +#include "kappa/install/install.hpp" +#include "kappa/paths.hpp" + +#include +#include +#include +#include +#include + +namespace kappa::install { + +namespace fs = std::filesystem; + +static std::string db_file() { + return (fs::path(paths::db_dir()) / "installed").string(); +} + +static std::uint64_t fnv1a(std::string_view s) { + std::uint64_t h = 14695981039346656037ULL; + for (char c : s) { h ^= static_cast(static_cast(c)); h *= 1099511628211ULL; } + return h; +} + +InstallResult install(const resolve::BuildStep& step, + const fs::path& work_dir) { + InstallResult result; + + auto hash = std::format("{:016x}", + fnv1a(step.name + "\0" + step.resolved.original.version)); + result.hash = hash; + + auto dest = fs::path(paths::bin_dir()) / hash; + result.store_path = dest; + + std::error_code ec; + fs::create_directories(dest, ec); + if (ec) { + result.error = "cannot create store directory"; + return result; + } + + auto src = work_dir / "destdir"; + if (fs::exists(src)) { + for (auto& entry : fs::recursive_directory_iterator(src, ec)) { + auto rel = fs::relative(entry.path(), src); + auto target = dest / rel; + if (entry.is_directory()) { + fs::create_directories(target, ec); + } else { + fs::create_directories(target.parent_path(), ec); + fs::rename(entry.path(), target, ec); + } + } + } + + auto entries = read_installed(); + entries.push_back({step.name, step.resolved.original.version, + hash, step.resolved.original.provides}); + write_installed(entries); + + std::vector hashes; + for (auto& e : entries) { hashes.push_back(e.hash); } + record_generation(hashes, 5); + + result.ok = true; + return result; +} + +std::vector read_installed() { + std::vector entries; + std::ifstream in(db_file()); + if (!in) { return entries; } + + std::string line; + while (std::getline(in, line)) { + if (line.empty()) { continue; } + std::istringstream iss(line); + DbEntry e; + iss >> e.name >> e.version >> e.hash; + std::string prov; + while (iss >> prov) { e.provides.push_back(prov); } + entries.push_back(std::move(e)); + } + return entries; +} + +bool write_installed(const std::vector& 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'; + } + return true; +} + +bool record_generation(const std::vector& hashes, int keep) { + auto gen_dir = fs::path(paths::db_dir()) / "generations"; + std::error_code ec; + fs::create_directories(gen_dir, ec); + + int gen = 1; + for (auto& entry : fs::directory_iterator(gen_dir, ec)) { + auto name = entry.path().filename().string(); + if (name.starts_with("gen-")) { ++gen; } + } + + auto gen_file = gen_dir / std::format("gen-{:04d}", gen); + std::ofstream out(gen_file); + if (!out) { return false; } + for (auto& h : hashes) { out << h << '\n'; } + + std::vector gens; + for (auto& entry : fs::directory_iterator(gen_dir, ec)) { + gens.push_back(entry.path()); + } + std::sort(gens.begin(), gens.end()); + + while (static_cast(gens.size()) > keep && keep > 0) { + fs::remove_all(gens.front(), ec); + gens.erase(gens.begin()); + } + + return true; +} + +} // namespace kappa::install diff --git a/src/main.cpp b/src/main.cpp index 2fb77d7..58f1a0c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,11 @@ +#include "kappa/build/build.hpp" #include "kappa/cli/diagnostic.hpp" #include "kappa/config/eval.hpp" #include "kappa/dsl/parser.hpp" #include "kappa/dsl/system.hpp" #include "kappa/fetch/fetch.hpp" +#include "kappa/install/install.hpp" +#include "kappa/paths.hpp" #include "kappa/resolve/plan.hpp" #include "kappa/tools/doctor.hpp" #include "kappa/tools/format.hpp" @@ -32,6 +35,7 @@ Subcommands: doctor Check a .kap file for issues and warnings 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 Options: -h, --help Show this help message @@ -79,6 +83,8 @@ static void handle_parse_error(const char* path, } int main(int argc, char* argv[]) { + paths::ensure_directories(); + if (argc < 2) { std::cerr << "kappa: missing subcommand\n\n"; print_usage(); @@ -102,7 +108,8 @@ int main(int argc, char* argv[]) { || (subcommand == "format") || (subcommand == "doctor") || (subcommand == "resolve") - || (subcommand == "fetch"); + || (subcommand == "fetch") + || (subcommand == "build"); if (!valid_subcommand) { std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n"; @@ -122,6 +129,15 @@ int main(int argc, char* argv[]) { std::cout << version << '\n'; return 0; } + if (std::string_view(argv[i]) == "-j" + || std::string_view(argv[i]) == "--jobs") { + ++i; + continue; + } + if (std::string_view(argv[i]) == "--root" && i + 1 < argc) { + paths::set_root(argv[++i]); + continue; + } if (!is_flag(argv[i])) { file_arg = argv[i]; break; @@ -197,6 +213,47 @@ int main(int argc, char* argv[]) { } } + if (subcommand == "build") { + try { + auto pkg = dsl::parse(source); + + int jobs = 1; + for (int i = 2; i < argc; ++i) { + auto arg = std::string_view(argv[i]); + if ((arg == "-j" || arg == "--jobs") && i + 1 < argc) { + jobs = std::stoi(argv[++i]); + } + } + + resolve::Registry reg; + reg[pkg.name] = pkg; + + resolve::BuildStep step; + step.name = pkg.name; + step.package = ®.at(pkg.name); + step.resolved = config::resolve_package(pkg, {}, {}); + + auto work_dir = paths::temp_dir() / "build"; + auto result = build::build(step, work_dir, jobs); + if (result.ok) { + auto inst = install::install(step, work_dir); + if (inst.ok) { + std::cout << "build successful — installed to " + << inst.store_path << "\n"; + return 0; + } + std::cerr << "install failed: " << inst.error << "\n"; + return 1; + } + std::cerr << "build failed in phase '" << result.phase + << "': " << result.error << "\n"; + return 1; + } catch (const std::exception& e) { + std::cerr << "build error: " << e.what() << "\n"; + return 1; + } + } + if (subcommand == "format") { try { auto pkg = dsl::parse(source); diff --git a/src/paths.cpp b/src/paths.cpp index c217c98..84dfa6d 100644 --- a/src/paths.cpp +++ b/src/paths.cpp @@ -1,16 +1,32 @@ #include "kappa/paths.hpp" +#include #include namespace kappa::paths { +static std::filesystem::path g_root = [] { + if (auto* env = std::getenv("KAPPA_ROOT"); env != nullptr) { + return std::filesystem::path{env}; + } + return std::filesystem::path{"/kappa"}; +}(); + +void set_root(std::string_view path) { g_root = path; } + +std::filesystem::path root_path() { return g_root; } +std::filesystem::path bin_dir() { return g_root / "bin"; } +std::filesystem::path temp_dir() { return g_root / "temp"; } +std::filesystem::path db_dir() { return g_root / "db"; } +std::filesystem::path system_dir() { return g_root / "system"; } +std::filesystem::path builds_dir() { return g_root / "system" / "builds"; } + void ensure_directories() { std::error_code ec; - - std::filesystem::create_directories(bin_dir, ec); - std::filesystem::create_directories(temp_dir, ec); - std::filesystem::create_directories(db_dir, ec); - std::filesystem::create_directories(builds_dir, ec); + std::filesystem::create_directories(bin_dir(), ec); + std::filesystem::create_directories(temp_dir(), ec); + std::filesystem::create_directories(db_dir(), ec); + std::filesystem::create_directories(builds_dir(), ec); } } // namespace kappa::paths diff --git a/src/sched/scheduler.cpp b/src/sched/scheduler.cpp new file mode 100644 index 0000000..bda6647 --- /dev/null +++ b/src/sched/scheduler.cpp @@ -0,0 +1,27 @@ +#include "kappa/sched/scheduler.hpp" +#include "kappa/build/build.hpp" + +namespace kappa::sched { + +SchedResult run(const resolve::BuildPlan& plan, + const std::string& work_root, + int workers, + int jobs) { + SchedResult result; + + for (auto& step : plan.steps) { + auto r = build::build(step, work_root + "/" + step.name, jobs); + if (r.ok) { + result.built.push_back(step.name); + } else { + result.failed.push_back(step.name); + result.ok = false; + return result; + } + } + + result.ok = true; + return result; +} + +} // namespace kappa::sched