fix: Phase 3+4 review — deterministic hash, EINTR, env restore, installer wiring
- 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
This commit is contained in:
@@ -33,5 +33,8 @@ add_executable(kappa
|
|||||||
src/tools/doctor.cpp
|
src/tools/doctor.cpp
|
||||||
src/resolve/plan.cpp
|
src/resolve/plan.cpp
|
||||||
src/fetch/fetch.cpp
|
src/fetch/fetch.cpp
|
||||||
|
src/build/build.cpp
|
||||||
|
src/sched/scheduler.cpp
|
||||||
|
src/install/install.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(kappa PRIVATE include)
|
target_include_directories(kappa PRIVATE include)
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ enum class TokenType {
|
|||||||
KwService,
|
KwService,
|
||||||
KwAssert,
|
KwAssert,
|
||||||
KwImport,
|
KwImport,
|
||||||
|
KwSha256,
|
||||||
|
KwSha512,
|
||||||
|
KwMd5,
|
||||||
KwPrepare,
|
KwPrepare,
|
||||||
KwBuild,
|
KwBuild,
|
||||||
KwCheck,
|
KwCheck,
|
||||||
|
|||||||
+10
-6
@@ -1,15 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace kappa::paths {
|
namespace kappa::paths {
|
||||||
|
|
||||||
inline const std::filesystem::path root{"/kappa"};
|
void set_root(std::string_view path);
|
||||||
inline const auto bin_dir = root / "bin";
|
|
||||||
inline const auto temp_dir = root / "temp";
|
std::filesystem::path root_path();
|
||||||
inline const auto db_dir = root / "db";
|
std::filesystem::path bin_dir();
|
||||||
inline const auto system_dir = root / "system";
|
std::filesystem::path temp_dir();
|
||||||
inline const auto builds_dir = system_dir / "builds";
|
std::filesystem::path db_dir();
|
||||||
|
std::filesystem::path system_dir();
|
||||||
|
std::filesystem::path builds_dir();
|
||||||
|
|
||||||
void ensure_directories();
|
void ensure_directories();
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -20,6 +20,9 @@ static const std::unordered_map<std::string_view, TokenType> keywords = {
|
|||||||
{"patches", TokenType::KwPatches},
|
{"patches", TokenType::KwPatches},
|
||||||
{"env", TokenType::KwEnv},
|
{"env", TokenType::KwEnv},
|
||||||
{"service", TokenType::KwService},
|
{"service", TokenType::KwService},
|
||||||
|
{"sha256", TokenType::KwSha256},
|
||||||
|
{"sha512", TokenType::KwSha512},
|
||||||
|
{"md5", TokenType::KwMd5},
|
||||||
{"assert", TokenType::KwAssert},
|
{"assert", TokenType::KwAssert},
|
||||||
{"import", TokenType::KwImport},
|
{"import", TokenType::KwImport},
|
||||||
{"prepare", TokenType::KwPrepare},
|
{"prepare", TokenType::KwPrepare},
|
||||||
@@ -55,6 +58,9 @@ std::string_view token_name(TokenType type) {
|
|||||||
case TokenType::KwPatches: return "patches";
|
case TokenType::KwPatches: return "patches";
|
||||||
case TokenType::KwEnv: return "env";
|
case TokenType::KwEnv: return "env";
|
||||||
case TokenType::KwService: return "service";
|
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::KwAssert: return "assert";
|
||||||
case TokenType::KwImport: return "import";
|
case TokenType::KwImport: return "import";
|
||||||
case TokenType::KwPrepare: return "prepare";
|
case TokenType::KwPrepare: return "prepare";
|
||||||
@@ -145,7 +151,7 @@ Token Lexer::scan_ident() {
|
|||||||
while (pos_ < source_.size()) {
|
while (pos_ < source_.size()) {
|
||||||
char c = peek();
|
char c = peek();
|
||||||
if (std::isspace(static_cast<unsigned char>(c))) { break; }
|
if (std::isspace(static_cast<unsigned char>(c))) { break; }
|
||||||
if (c == '"' || c == '[' || c == ']' || c == ',') { break; }
|
if (c == '"' || c == '=' || c == '[' || c == ']' || c == ',') { break; }
|
||||||
lexeme += advance();
|
lexeme += advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+30
-17
@@ -123,22 +123,28 @@ void Parser::parse_body(PackageDef& pkg) {
|
|||||||
pkg.license = consume(TokenType::String).lexeme;
|
pkg.license = consume(TokenType::String).lexeme;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TokenType::Ident:
|
case TokenType::KwSha256:
|
||||||
if (current_.lexeme == "sha256") {
|
consume(TokenType::KwSha256);
|
||||||
advance(); consume(TokenType::Equals);
|
consume(TokenType::Equals);
|
||||||
pkg.sha256 = consume(TokenType::String).lexeme;
|
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));
|
|
||||||
}
|
|
||||||
break;
|
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:
|
case TokenType::KwProvides:
|
||||||
consume(TokenType::KwProvides);
|
consume(TokenType::KwProvides);
|
||||||
consume(TokenType::Equals);
|
consume(TokenType::Equals);
|
||||||
@@ -213,9 +219,10 @@ void Parser::parse_body(PackageDef& pkg) {
|
|||||||
if (at(TokenType::Newline)) { advance(); continue; }
|
if (at(TokenType::Newline)) { advance(); continue; }
|
||||||
auto key = consume(TokenType::Ident).lexeme;
|
auto key = consume(TokenType::Ident).lexeme;
|
||||||
bool soft = false;
|
bool soft = false;
|
||||||
if (at(TokenType::Ident) && current_.lexeme == "?=") {
|
if (at(TokenType::Ident) && current_.lexeme == "?") {
|
||||||
soft = true;
|
|
||||||
advance();
|
advance();
|
||||||
|
consume(TokenType::Equals);
|
||||||
|
soft = true;
|
||||||
} else {
|
} else {
|
||||||
consume(TokenType::Equals);
|
consume(TokenType::Equals);
|
||||||
}
|
}
|
||||||
@@ -291,6 +298,10 @@ void Parser::parse_body(PackageDef& pkg) {
|
|||||||
} else {
|
} else {
|
||||||
a.op = "=";
|
a.op = "=";
|
||||||
}
|
}
|
||||||
|
} else if (at(TokenType::Ident) && current_.lexeme == "!") {
|
||||||
|
advance();
|
||||||
|
consume(TokenType::Equals);
|
||||||
|
a.op = "!=";
|
||||||
} else {
|
} else {
|
||||||
a.op = current_.lexeme; advance();
|
a.op = current_.lexeme; advance();
|
||||||
}
|
}
|
||||||
@@ -502,7 +513,9 @@ Phase Parser::parse_phase() {
|
|||||||
Command Parser::parse_command_line() {
|
Command Parser::parse_command_line() {
|
||||||
std::string cmd;
|
std::string cmd;
|
||||||
while (!at(TokenType::Newline) && !at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
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;
|
cmd += current_.lexeme;
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-4
@@ -65,6 +65,9 @@ std::string SysParser::consume_ident() {
|
|||||||
at(TokenType::KwService) ||
|
at(TokenType::KwService) ||
|
||||||
at(TokenType::KwAssert) ||
|
at(TokenType::KwAssert) ||
|
||||||
at(TokenType::KwImport) ||
|
at(TokenType::KwImport) ||
|
||||||
|
at(TokenType::KwSha256) ||
|
||||||
|
at(TokenType::KwSha512) ||
|
||||||
|
at(TokenType::KwMd5) ||
|
||||||
at(TokenType::KwConfig) ||
|
at(TokenType::KwConfig) ||
|
||||||
at(TokenType::KwFeatures) ||
|
at(TokenType::KwFeatures) ||
|
||||||
at(TokenType::KwVersion) ||
|
at(TokenType::KwVersion) ||
|
||||||
@@ -137,12 +140,16 @@ SystemConfig SysParser::parse() {
|
|||||||
consume(TokenType::Ident); // ":"
|
consume(TokenType::Ident); // ":"
|
||||||
a.field = current_.lexeme; advance();
|
a.field = current_.lexeme; advance();
|
||||||
if (at(TokenType::Equals)) {
|
if (at(TokenType::Equals)) {
|
||||||
advance(); // first =
|
advance();
|
||||||
if (at(TokenType::Equals)) {
|
if (at(TokenType::Equals)) {
|
||||||
a.op = "=="; advance(); // second =
|
a.op = "=="; advance();
|
||||||
} else {
|
} else {
|
||||||
a.op = "=";
|
a.op = "=";
|
||||||
}
|
}
|
||||||
|
} else if (at(TokenType::Ident) && current_.lexeme == "!") {
|
||||||
|
advance();
|
||||||
|
consume(TokenType::Equals);
|
||||||
|
a.op = "!=";
|
||||||
} else {
|
} else {
|
||||||
a.op = current_.lexeme; advance();
|
a.op = current_.lexeme; advance();
|
||||||
}
|
}
|
||||||
@@ -195,9 +202,10 @@ void SysParser::parse_system_block(SystemConfig& cfg) {
|
|||||||
if (at(TokenType::Newline)) { advance(); continue; }
|
if (at(TokenType::Newline)) { advance(); continue; }
|
||||||
auto k = consume_ident();
|
auto k = consume_ident();
|
||||||
bool soft = false;
|
bool soft = false;
|
||||||
if (at(TokenType::Ident) && current_.lexeme == "?=") {
|
if (at(TokenType::Ident) && current_.lexeme == "?") {
|
||||||
soft = true;
|
|
||||||
advance();
|
advance();
|
||||||
|
consume(TokenType::Equals);
|
||||||
|
soft = true;
|
||||||
} else {
|
} else {
|
||||||
consume(TokenType::Equals);
|
consume(TokenType::Equals);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
#include "kappa/eval/vars.hpp"
|
#include "kappa/eval/vars.hpp"
|
||||||
|
#include "kappa/paths.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ Scope make_default_scope() {
|
|||||||
s.builtins["prefix"] = "/usr";
|
s.builtins["prefix"] = "/usr";
|
||||||
s.builtins["jobs"] = "1";
|
s.builtins["jobs"] = "1";
|
||||||
s.builtins["jobopts"] = "-j1";
|
s.builtins["jobopts"] = "-j1";
|
||||||
s.builtins["destdir"] = "/kappa/temp/destdir";
|
s.builtins["destdir"] = (paths::temp_dir() / "destdir").string();
|
||||||
s.builtins["userargs"] = "";
|
s.builtins["userargs"] = "";
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -123,14 +123,14 @@ FetchResult fetch(const dsl::PackageDef& pkg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto dest_name = pkg.name + "-" + pkg.version;
|
auto dest_name = pkg.name + "-" + pkg.version;
|
||||||
auto dest_file = fs::path(paths::temp_dir) / (dest_name + "." + ext);
|
auto dest_file = fs::path(paths::temp_dir()) / (dest_name + "." + ext);
|
||||||
result.work_dir = fs::path(paths::temp_dir) / dest_name;
|
result.work_dir = fs::path(paths::temp_dir()) / dest_name;
|
||||||
|
|
||||||
if (ext == "git") {
|
if (ext == "git") {
|
||||||
int rc = exec_cmd({"git", "clone", url, result.work_dir.string()});
|
int rc = exec_cmd({"git", "clone", url, result.work_dir.string()});
|
||||||
if (rc != 0) { result.error = "git clone failed"; return result; }
|
if (rc != 0) { result.error = "git clone failed"; return result; }
|
||||||
} else {
|
} else {
|
||||||
fs::create_directories(paths::temp_dir);
|
fs::create_directories(paths::temp_dir());
|
||||||
int rc = exec_cmd({"curl", "-L", "-o", dest_file.string(), url});
|
int rc = exec_cmd({"curl", "-L", "-o", dest_file.string(), url});
|
||||||
if (rc != 0) { result.error = "download failed"; return result; }
|
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; }
|
if (rc2 != 0) { result.error = "extraction failed"; return result; }
|
||||||
} else {
|
} else {
|
||||||
int rc2 = exec_cmd({"tar", "xf", dest_file.string(),
|
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"});
|
"--no-same-owner", "--no-same-permissions"});
|
||||||
if (rc2 != 0) { result.error = "extraction failed"; return result; }
|
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_path = patch.url;
|
||||||
auto patch_file = patch_path;
|
auto patch_file = patch_path;
|
||||||
if (patch_path.starts_with("http")) {
|
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();
|
/ fs::path(patch_path).filename();
|
||||||
int rc = exec_cmd({"curl", "-L", "-o", local.string(),
|
int rc = exec_cmd({"curl", "-L", "-o", local.string(),
|
||||||
patch_path});
|
patch_path});
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
#include "kappa/install/install.hpp"
|
||||||
|
#include "kappa/paths.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <format>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
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<std::uint64_t>(static_cast<unsigned char>(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<std::string> hashes;
|
||||||
|
for (auto& e : entries) { hashes.push_back(e.hash); }
|
||||||
|
record_generation(hashes, 5);
|
||||||
|
|
||||||
|
result.ok = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DbEntry> read_installed() {
|
||||||
|
std::vector<DbEntry> 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<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';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool record_generation(const std::vector<std::string>& 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<fs::path> gens;
|
||||||
|
for (auto& entry : fs::directory_iterator(gen_dir, ec)) {
|
||||||
|
gens.push_back(entry.path());
|
||||||
|
}
|
||||||
|
std::sort(gens.begin(), gens.end());
|
||||||
|
|
||||||
|
while (static_cast<int>(gens.size()) > keep && keep > 0) {
|
||||||
|
fs::remove_all(gens.front(), ec);
|
||||||
|
gens.erase(gens.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace kappa::install
|
||||||
+58
-1
@@ -1,8 +1,11 @@
|
|||||||
|
#include "kappa/build/build.hpp"
|
||||||
#include "kappa/cli/diagnostic.hpp"
|
#include "kappa/cli/diagnostic.hpp"
|
||||||
#include "kappa/config/eval.hpp"
|
#include "kappa/config/eval.hpp"
|
||||||
#include "kappa/dsl/parser.hpp"
|
#include "kappa/dsl/parser.hpp"
|
||||||
#include "kappa/dsl/system.hpp"
|
#include "kappa/dsl/system.hpp"
|
||||||
#include "kappa/fetch/fetch.hpp"
|
#include "kappa/fetch/fetch.hpp"
|
||||||
|
#include "kappa/install/install.hpp"
|
||||||
|
#include "kappa/paths.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"
|
||||||
@@ -32,6 +35,7 @@ Subcommands:
|
|||||||
doctor <file> Check a .kap file for issues and warnings
|
doctor <file> Check a .kap file for issues and warnings
|
||||||
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
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
@@ -79,6 +83,8 @@ static void handle_parse_error(const char* path,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
paths::ensure_directories();
|
||||||
|
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
std::cerr << "kappa: missing subcommand\n\n";
|
std::cerr << "kappa: missing subcommand\n\n";
|
||||||
print_usage();
|
print_usage();
|
||||||
@@ -102,7 +108,8 @@ int main(int argc, char* argv[]) {
|
|||||||
|| (subcommand == "format")
|
|| (subcommand == "format")
|
||||||
|| (subcommand == "doctor")
|
|| (subcommand == "doctor")
|
||||||
|| (subcommand == "resolve")
|
|| (subcommand == "resolve")
|
||||||
|| (subcommand == "fetch");
|
|| (subcommand == "fetch")
|
||||||
|
|| (subcommand == "build");
|
||||||
|
|
||||||
if (!valid_subcommand) {
|
if (!valid_subcommand) {
|
||||||
std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n";
|
std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n";
|
||||||
@@ -122,6 +129,15 @@ int main(int argc, char* argv[]) {
|
|||||||
std::cout << version << '\n';
|
std::cout << version << '\n';
|
||||||
return 0;
|
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])) {
|
if (!is_flag(argv[i])) {
|
||||||
file_arg = argv[i];
|
file_arg = argv[i];
|
||||||
break;
|
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") {
|
if (subcommand == "format") {
|
||||||
try {
|
try {
|
||||||
auto pkg = dsl::parse(source);
|
auto pkg = dsl::parse(source);
|
||||||
|
|||||||
+21
-5
@@ -1,16 +1,32 @@
|
|||||||
#include "kappa/paths.hpp"
|
#include "kappa/paths.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
namespace kappa::paths {
|
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() {
|
void ensure_directories() {
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
|
std::filesystem::create_directories(bin_dir(), ec);
|
||||||
std::filesystem::create_directories(bin_dir, ec);
|
std::filesystem::create_directories(temp_dir(), ec);
|
||||||
std::filesystem::create_directories(temp_dir, ec);
|
std::filesystem::create_directories(db_dir(), ec);
|
||||||
std::filesystem::create_directories(db_dir, ec);
|
std::filesystem::create_directories(builds_dir(), ec);
|
||||||
std::filesystem::create_directories(builds_dir, ec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace kappa::paths
|
} // namespace kappa::paths
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user