feat: DSL parser, eval system, and project structure
- Hand-written lexer + recursive-descent parser for .kap package definitions
- Package constructs: const, license, provides, outputs, patches, depends
(version-constrained, feature-gated, output-targeted), features (enabled/
forced/flag), config files (default/replace/merge with cfg interpolation),
env (?= soft-set), prepare/build/check/install phases
- Evaluator: recursive resolver for prefix, jobopts, destdir, userargs,
cfg.*, feature.*, package vars
- Comments: // single-line, /* */ multi-line
- /kappa/{bin,temp,db,system,system/builds} path layout
- Clang 22, C++23, CMake + vcpkg (tomlplusplus)
- .clangd + .clang-tidy configured
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
Checks: >
|
||||
-*,
|
||||
bugprone-*,
|
||||
modernize-*,
|
||||
performance-*,
|
||||
portability-*,
|
||||
readability-*,
|
||||
-modernize-use-trailing-return-type,
|
||||
-readability-identifier-length,
|
||||
-readability-magic-numbers,
|
||||
-readability-identifier-naming
|
||||
|
||||
CheckOptions:
|
||||
- key: modernize-use-nullptr.NullptrMacros
|
||||
value: 'NULL'
|
||||
@@ -0,0 +1,5 @@
|
||||
CompileFlags:
|
||||
CompilationDatabase: build
|
||||
Diagnostics:
|
||||
UnusedIncludes: Strict
|
||||
MissingIncludes: Strict
|
||||
@@ -32,3 +32,7 @@
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Build
|
||||
build/
|
||||
compile_commands.json
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
# Enforce Clang
|
||||
set(CMAKE_C_COMPILER clang)
|
||||
set(CMAKE_CXX_COMPILER clang++)
|
||||
|
||||
project(kappa VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# Generate compile_commands.json for clangd
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# --- vcpkg integration ---
|
||||
if(DEFINED ENV{VCPKG_ROOT})
|
||||
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
|
||||
CACHE STRING "vcpkg toolchain")
|
||||
endif()
|
||||
|
||||
find_package(tomlplusplus CONFIG REQUIRED)
|
||||
|
||||
add_executable(kappa
|
||||
src/main.cpp
|
||||
src/package.cpp
|
||||
src/paths.cpp
|
||||
src/dsl/lexer.cpp
|
||||
src/dsl/parser.cpp
|
||||
src/eval/vars.cpp
|
||||
)
|
||||
target_include_directories(kappa PRIVATE include)
|
||||
target_link_libraries(kappa PRIVATE tomlplusplus::tomlplusplus)
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* foo — a web server with optional SSL and GUI support.
|
||||
* Demonstrates the full kappa DSL surface.
|
||||
*/
|
||||
package "foo" {
|
||||
const version = "1.2.3"
|
||||
const source = "https://example.com/foo-${version}.tar.gz"
|
||||
license = "MIT"
|
||||
|
||||
provides = ["libfoo.so.1", "foo"]
|
||||
|
||||
patches = [
|
||||
{
|
||||
url = "https://example.com/fix-build.patch"
|
||||
sha256 = "abc123def456"
|
||||
level = 1
|
||||
},
|
||||
"local-fix.patch" // local shorthand, no hash
|
||||
]
|
||||
outputs = ["bin", "lib", "dev"]
|
||||
|
||||
depends = [
|
||||
{ name = "zlib", version = ">=1.2,<2.0" },
|
||||
{ name = "openssl", feature = "ssl" },
|
||||
{ name = "gtk", feature = "gui", version = ">=3" },
|
||||
"gettext:lib" // name:output shorthand
|
||||
]
|
||||
|
||||
features {
|
||||
ssl = { enabled = true, flag = "--with-ssl-dir=${cfg.ssl_dir}" }
|
||||
gui = { enabled = false, flag = "--enable-gui" }
|
||||
drivers = { enabled = true, force = true }
|
||||
debug = false
|
||||
}
|
||||
|
||||
config {
|
||||
// Written once, left alone on rebuild if the user edits it.
|
||||
file "etc/foo.conf" mode = "default" {
|
||||
hostname = ${cfg.hostname !} // required — user must set
|
||||
listen_port = ${cfg.port ? 8080} // optional, default 8080
|
||||
ssl_enabled = ${cfg.ssl ? true} // optional, default true
|
||||
}
|
||||
|
||||
// Always overwritten on rebuild.
|
||||
file "etc/log.conf" mode = "replace" {
|
||||
log_level = ${cfg.log_level ? info}
|
||||
log_path = ${cfg.log_path ? /var/log/foo}
|
||||
}
|
||||
|
||||
// Three-way diff on rebuild.
|
||||
file "etc/limits.conf" mode = "merge" {
|
||||
max_connections = ${cfg.max_conn ? 1024}
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
CFLAGS = "-O2 -march=native"
|
||||
LDFLAGS = "-Wl,--as-needed"
|
||||
CFLAGS ?= "-g"
|
||||
}
|
||||
|
||||
prepare {
|
||||
patch "fix-build.patch"
|
||||
}
|
||||
|
||||
build {
|
||||
./configure --prefix=${prefix} ${feature.ssl} ${feature.gui}
|
||||
make -j${jobs}
|
||||
}
|
||||
|
||||
check {
|
||||
make check
|
||||
}
|
||||
|
||||
install {
|
||||
make DESTDIR=${destdir} install
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace kappa::dsl {
|
||||
|
||||
using Command = std::string;
|
||||
|
||||
struct Phase {
|
||||
std::vector<Command> commands;
|
||||
};
|
||||
|
||||
struct Dependency {
|
||||
std::string name;
|
||||
std::string version;
|
||||
std::string output;
|
||||
std::string feature; // empty = unconditional
|
||||
};
|
||||
|
||||
struct ConfigFile {
|
||||
std::string path;
|
||||
std::string mode;
|
||||
std::unordered_map<std::string, std::string> entries;
|
||||
};
|
||||
|
||||
struct FeatureDef {
|
||||
bool enabled = false;
|
||||
bool force = false;
|
||||
std::string flag;
|
||||
};
|
||||
|
||||
struct Patch {
|
||||
std::string url;
|
||||
std::string sha256;
|
||||
int level = 1;
|
||||
};
|
||||
|
||||
struct EnvEntry {
|
||||
std::string key;
|
||||
std::string value;
|
||||
bool soft = false;
|
||||
};
|
||||
|
||||
struct PackageDef {
|
||||
std::string name;
|
||||
std::string version;
|
||||
std::string source;
|
||||
std::string license;
|
||||
std::vector<Dependency> depends;
|
||||
std::vector<std::string> provides;
|
||||
std::vector<std::string> outputs;
|
||||
std::unordered_map<std::string, FeatureDef> features;
|
||||
std::vector<ConfigFile> config_files;
|
||||
std::vector<Patch> patches;
|
||||
std::vector<EnvEntry> env_entries;
|
||||
std::unordered_set<std::string> const_keys;
|
||||
Phase prepare;
|
||||
Phase build;
|
||||
Phase check;
|
||||
Phase install;
|
||||
};
|
||||
|
||||
} // namespace kappa::dsl
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "kappa/dsl/token.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace kappa::dsl {
|
||||
|
||||
class Lexer {
|
||||
public:
|
||||
explicit Lexer(std::string_view source);
|
||||
|
||||
Token next();
|
||||
|
||||
private:
|
||||
Token scan_ident();
|
||||
Token scan_string();
|
||||
Token scan_symbol();
|
||||
void skip_whitespace();
|
||||
bool skip_comment();
|
||||
|
||||
char peek() const;
|
||||
char advance();
|
||||
bool match(char c);
|
||||
|
||||
std::string_view source_;
|
||||
std::size_t pos_ = 0;
|
||||
int line_ = 1;
|
||||
int col_ = 1;
|
||||
int token_start_line_ = 0;
|
||||
int token_start_col_ = 0;
|
||||
};
|
||||
|
||||
} // namespace kappa::dsl
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "kappa/dsl/ast.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace kappa::dsl {
|
||||
|
||||
PackageDef parse(std::string_view source);
|
||||
|
||||
} // namespace kappa::dsl
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace kappa::dsl {
|
||||
|
||||
enum class TokenType {
|
||||
Eof,
|
||||
Newline,
|
||||
|
||||
// Symbols
|
||||
Lbrace, // {
|
||||
Rbrace, // }
|
||||
Equals, // =
|
||||
Lbracket, // [
|
||||
Rbracket, // ]
|
||||
Comma, // ,
|
||||
|
||||
// Literals
|
||||
String, // "..."
|
||||
Ident, // bare word (keyword, command, variable ref)
|
||||
|
||||
// Keywords (tokenised as Ident, then classified)
|
||||
KwPackage,
|
||||
KwVersion,
|
||||
KwSource,
|
||||
KwDepends,
|
||||
KwProvides,
|
||||
KwOutputs,
|
||||
KwFeatures,
|
||||
KwConfig,
|
||||
KwConst,
|
||||
KwLicense,
|
||||
KwPatches,
|
||||
KwEnv,
|
||||
KwPrepare,
|
||||
KwBuild,
|
||||
KwCheck,
|
||||
KwInstall,
|
||||
KwTrue,
|
||||
KwFalse,
|
||||
};
|
||||
|
||||
struct Token {
|
||||
TokenType type = TokenType::Eof;
|
||||
std::string lexeme;
|
||||
int line = 0;
|
||||
int col = 0;
|
||||
};
|
||||
|
||||
std::string_view token_name(TokenType type);
|
||||
|
||||
} // namespace kappa::dsl
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "kappa/dsl/ast.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace kappa::eval {
|
||||
|
||||
enum class VarKind {
|
||||
Unknown,
|
||||
Builtin, // ${prefix}, ${jobs}, ${jobopts}, ${userargs}
|
||||
Package, // ${version}, ${name}, ${source}
|
||||
Config, // ${cfg.key}
|
||||
Feature, // ${feature.key} → expanded flag or ""
|
||||
};
|
||||
|
||||
struct ResolvedVar {
|
||||
std::string value;
|
||||
VarKind kind = VarKind::Unknown;
|
||||
};
|
||||
|
||||
struct Scope {
|
||||
std::unordered_map<std::string, std::string> builtins;
|
||||
std::unordered_map<std::string, std::string> config;
|
||||
std::unordered_map<std::string, dsl::FeatureDef> features;
|
||||
std::unordered_map<std::string, std::string> package;
|
||||
};
|
||||
|
||||
Scope make_default_scope();
|
||||
|
||||
ResolvedVar resolve(std::string_view name, const Scope& scope);
|
||||
|
||||
std::string interpolate(std::string_view input, const Scope& scope);
|
||||
|
||||
} // namespace kappa::eval
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace kappa {
|
||||
|
||||
struct PackageConfig {
|
||||
std::string name;
|
||||
std::string version;
|
||||
std::string description;
|
||||
std::string source_url;
|
||||
std::string source_extension;
|
||||
std::string source_hash;
|
||||
std::vector<std::string> dependencies;
|
||||
std::string build_system;
|
||||
};
|
||||
|
||||
PackageConfig parse_package(const std::string& toml_path);
|
||||
void print_package(std::ostream& os, const PackageConfig& pkg);
|
||||
|
||||
} // namespace kappa
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
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 ensure_directories();
|
||||
|
||||
} // namespace kappa::paths
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "binutils"
|
||||
version = "2.46.1"
|
||||
description = "GNU Binary Utilities (ld, as, objdump, readelf)"
|
||||
|
||||
[source]
|
||||
url = "https://ftp.gnu.org/gnu/${name}/${name}-${version}.tar.xz" # ${} is string substitution
|
||||
sha256 = "e127a709cba24c76de8936cb7083dd768f28cd37eb010492e2f19b71eb1294e4" # or sha512, or md5
|
||||
|
||||
[dependencies]
|
||||
deps = ["zlib", "gettext"]
|
||||
|
||||
[build]
|
||||
system = "autotools" # or cargo, make, cmake, or meson
|
||||
@@ -0,0 +1,191 @@
|
||||
#include "kappa/dsl/lexer.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace kappa::dsl {
|
||||
|
||||
static const std::unordered_map<std::string_view, TokenType> keywords = {
|
||||
{"package", TokenType::KwPackage},
|
||||
{"version", TokenType::KwVersion},
|
||||
{"source", TokenType::KwSource},
|
||||
{"depends", TokenType::KwDepends},
|
||||
{"provides", TokenType::KwProvides},
|
||||
{"outputs", TokenType::KwOutputs},
|
||||
{"features", TokenType::KwFeatures},
|
||||
{"config", TokenType::KwConfig},
|
||||
{"const", TokenType::KwConst},
|
||||
{"license", TokenType::KwLicense},
|
||||
{"patches", TokenType::KwPatches},
|
||||
{"env", TokenType::KwEnv},
|
||||
{"prepare", TokenType::KwPrepare},
|
||||
{"build", TokenType::KwBuild},
|
||||
{"check", TokenType::KwCheck},
|
||||
{"install", TokenType::KwInstall},
|
||||
{"true", TokenType::KwTrue},
|
||||
{"false", TokenType::KwFalse},
|
||||
};
|
||||
|
||||
std::string_view token_name(TokenType type) {
|
||||
switch (type) {
|
||||
case TokenType::Eof: return "EOF";
|
||||
case TokenType::Newline: return "newline";
|
||||
case TokenType::Lbrace: return "{";
|
||||
case TokenType::Rbrace: return "}";
|
||||
case TokenType::Equals: return "=";
|
||||
case TokenType::Lbracket: return "[";
|
||||
case TokenType::Rbracket: return "]";
|
||||
case TokenType::Comma: return ",";
|
||||
case TokenType::String: return "string";
|
||||
case TokenType::Ident: return "ident";
|
||||
case TokenType::KwPackage: return "package";
|
||||
case TokenType::KwVersion: return "version";
|
||||
case TokenType::KwSource: return "source";
|
||||
case TokenType::KwDepends: return "depends";
|
||||
case TokenType::KwProvides: return "provides";
|
||||
case TokenType::KwOutputs: return "outputs";
|
||||
case TokenType::KwFeatures: return "features";
|
||||
case TokenType::KwConfig: return "config";
|
||||
case TokenType::KwConst: return "const";
|
||||
case TokenType::KwLicense: return "license";
|
||||
case TokenType::KwPatches: return "patches";
|
||||
case TokenType::KwEnv: return "env";
|
||||
case TokenType::KwPrepare: return "prepare";
|
||||
case TokenType::KwBuild: return "build";
|
||||
case TokenType::KwCheck: return "check";
|
||||
case TokenType::KwInstall: return "install";
|
||||
case TokenType::KwTrue: return "true";
|
||||
case TokenType::KwFalse: return "false";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
Lexer::Lexer(std::string_view source) : source_(source) {}
|
||||
|
||||
char Lexer::peek() const {
|
||||
return pos_ < source_.size() ? source_[pos_] : '\0';
|
||||
}
|
||||
|
||||
char Lexer::advance() {
|
||||
if (pos_ >= source_.size()) { return '\0'; }
|
||||
char c = source_[pos_++];
|
||||
if (c == '\n') { ++line_; col_ = 1; }
|
||||
else { ++col_; }
|
||||
return c;
|
||||
}
|
||||
|
||||
bool Lexer::match(char c) {
|
||||
if (peek() == c) { advance(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
void Lexer::skip_whitespace() {
|
||||
while (std::isspace(static_cast<unsigned char>(peek())) && peek() != '\n') {
|
||||
advance();
|
||||
}
|
||||
}
|
||||
|
||||
bool Lexer::skip_comment() {
|
||||
if (peek() != '/') { return false; }
|
||||
if (pos_ + 1 >= source_.size()) { return false; }
|
||||
|
||||
char next = source_[pos_ + 1];
|
||||
if (next == '/') {
|
||||
advance(); advance(); // skip //
|
||||
while (pos_ < source_.size() && peek() != '\n') { advance(); }
|
||||
return true;
|
||||
}
|
||||
if (next == '*') {
|
||||
advance(); advance(); // skip /*
|
||||
while (pos_ + 1 < source_.size()) {
|
||||
if (peek() == '*' && source_[pos_ + 1] == '/') {
|
||||
advance(); advance(); // skip */
|
||||
return true;
|
||||
}
|
||||
advance();
|
||||
}
|
||||
return true; // unterminated — consume to EOF
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Token Lexer::next() {
|
||||
while (true) {
|
||||
skip_whitespace();
|
||||
if (skip_comment()) { continue; }
|
||||
|
||||
token_start_line_ = line_;
|
||||
token_start_col_ = col_;
|
||||
|
||||
if (pos_ >= source_.size()) { return {TokenType::Eof, "", line_, col_}; }
|
||||
|
||||
char c = peek();
|
||||
|
||||
if (c == '\n') {
|
||||
advance();
|
||||
return {TokenType::Newline, "\n", token_start_line_, token_start_col_};
|
||||
}
|
||||
if (c == '"') { return scan_string(); }
|
||||
if (c == '{' || c == '}' || c == '=' || c == '[' || c == ']' || c == ',') {
|
||||
return scan_symbol();
|
||||
}
|
||||
return scan_ident();
|
||||
}
|
||||
}
|
||||
|
||||
Token Lexer::scan_ident() {
|
||||
std::string lexeme;
|
||||
while (pos_ < source_.size()) {
|
||||
char c = peek();
|
||||
if (std::isspace(static_cast<unsigned char>(c))) { break; }
|
||||
if (c == '"' || c == '[' || c == ']' || c == ',') { break; }
|
||||
lexeme += advance();
|
||||
}
|
||||
|
||||
auto it = keywords.find(lexeme);
|
||||
if (it != keywords.end()) {
|
||||
return {it->second, lexeme, token_start_line_, token_start_col_};
|
||||
}
|
||||
return {TokenType::Ident, lexeme, token_start_line_, token_start_col_};
|
||||
}
|
||||
|
||||
Token Lexer::scan_string() {
|
||||
advance(); // opening "
|
||||
std::string lexeme;
|
||||
while (pos_ < source_.size()) {
|
||||
char c = peek();
|
||||
if (c == '"') { advance(); break; }
|
||||
if (c == '\\') {
|
||||
advance();
|
||||
if (pos_ < source_.size()) {
|
||||
char esc = advance();
|
||||
switch (esc) {
|
||||
case 'n': lexeme += '\n'; break;
|
||||
case 't': lexeme += '\t'; break;
|
||||
case '\\': lexeme += '\\'; break;
|
||||
case '"': lexeme += '"'; break;
|
||||
default: lexeme += esc; break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lexeme += advance();
|
||||
}
|
||||
}
|
||||
return {TokenType::String, lexeme, token_start_line_, token_start_col_};
|
||||
}
|
||||
|
||||
Token Lexer::scan_symbol() {
|
||||
char c = advance();
|
||||
switch (c) {
|
||||
case '{': return {TokenType::Lbrace, "{", token_start_line_, token_start_col_};
|
||||
case '}': return {TokenType::Rbrace, "}", token_start_line_, token_start_col_};
|
||||
case '=': return {TokenType::Equals, "=", token_start_line_, token_start_col_};
|
||||
case '[': return {TokenType::Lbracket, "[", token_start_line_, token_start_col_};
|
||||
case ']': return {TokenType::Rbracket, "]", token_start_line_, token_start_col_};
|
||||
case ',': return {TokenType::Comma, ",", token_start_line_, token_start_col_};
|
||||
default: return {TokenType::Eof, "", token_start_line_, token_start_col_};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace kappa::dsl
|
||||
@@ -0,0 +1,416 @@
|
||||
#include "kappa/dsl/parser.hpp"
|
||||
#include "kappa/dsl/lexer.hpp"
|
||||
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace kappa::dsl {
|
||||
|
||||
class ParseError : public std::runtime_error {
|
||||
public:
|
||||
ParseError(int line, int col, const std::string& msg)
|
||||
: std::runtime_error(std::format("{}:{}: {}", line, col, msg)) {}
|
||||
};
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
explicit Parser(std::string_view source) : lexer_(source) { advance(); }
|
||||
|
||||
PackageDef parse();
|
||||
|
||||
private:
|
||||
void advance();
|
||||
Token consume(TokenType type);
|
||||
void skip_newlines();
|
||||
bool at(TokenType type) const { return current_.type == type; }
|
||||
|
||||
PackageDef parse_package_def();
|
||||
void parse_body(PackageDef& pkg);
|
||||
Phase parse_phase();
|
||||
Command parse_command_line();
|
||||
bool parse_bool();
|
||||
|
||||
std::vector<std::string> parse_string_list();
|
||||
Dependency parse_dependency_item();
|
||||
ConfigFile parse_config_file();
|
||||
FeatureDef parse_feature_def();
|
||||
Patch parse_patch_item();
|
||||
|
||||
Token current_;
|
||||
Lexer lexer_;
|
||||
};
|
||||
|
||||
void Parser::advance() { current_ = lexer_.next(); }
|
||||
|
||||
void Parser::skip_newlines() {
|
||||
while (at(TokenType::Newline)) { advance(); }
|
||||
}
|
||||
|
||||
Token Parser::consume(TokenType type) {
|
||||
if (!at(type)) {
|
||||
throw ParseError(current_.line, current_.col,
|
||||
std::format("expected '{}', got '{}'",
|
||||
token_name(type), token_name(current_.type)));
|
||||
}
|
||||
Token t = std::move(current_);
|
||||
advance();
|
||||
return t;
|
||||
}
|
||||
|
||||
PackageDef Parser::parse() {
|
||||
skip_newlines();
|
||||
return parse_package_def();
|
||||
}
|
||||
|
||||
PackageDef Parser::parse_package_def() {
|
||||
consume(TokenType::KwPackage);
|
||||
auto name_tok = consume(TokenType::String);
|
||||
consume(TokenType::Lbrace);
|
||||
|
||||
PackageDef pkg;
|
||||
pkg.name = std::move(name_tok.lexeme);
|
||||
|
||||
parse_body(pkg);
|
||||
|
||||
consume(TokenType::Rbrace);
|
||||
return pkg;
|
||||
}
|
||||
|
||||
void Parser::parse_body(PackageDef& pkg) {
|
||||
while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
skip_newlines();
|
||||
if (at(TokenType::Rbrace) || at(TokenType::Eof)) { break; }
|
||||
|
||||
switch (current_.type) {
|
||||
case TokenType::KwConst: {
|
||||
consume(TokenType::KwConst);
|
||||
if (at(TokenType::KwVersion)) {
|
||||
consume(TokenType::KwVersion);
|
||||
consume(TokenType::Equals);
|
||||
pkg.version = consume(TokenType::String).lexeme;
|
||||
pkg.const_keys.insert("version");
|
||||
} else if (at(TokenType::KwSource)) {
|
||||
consume(TokenType::KwSource);
|
||||
consume(TokenType::Equals);
|
||||
pkg.source = consume(TokenType::String).lexeme;
|
||||
pkg.const_keys.insert("source");
|
||||
} else {
|
||||
throw ParseError(current_.line, current_.col,
|
||||
std::format("unexpected token '{}' after const",
|
||||
token_name(current_.type)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TokenType::KwVersion:
|
||||
consume(TokenType::KwVersion);
|
||||
consume(TokenType::Equals);
|
||||
pkg.version = consume(TokenType::String).lexeme;
|
||||
break;
|
||||
|
||||
case TokenType::KwSource:
|
||||
consume(TokenType::KwSource);
|
||||
consume(TokenType::Equals);
|
||||
pkg.source = consume(TokenType::String).lexeme;
|
||||
break;
|
||||
|
||||
case TokenType::KwLicense:
|
||||
consume(TokenType::KwLicense);
|
||||
consume(TokenType::Equals);
|
||||
pkg.license = consume(TokenType::String).lexeme;
|
||||
break;
|
||||
|
||||
case TokenType::KwProvides:
|
||||
consume(TokenType::KwProvides);
|
||||
consume(TokenType::Equals);
|
||||
pkg.provides = parse_string_list();
|
||||
break;
|
||||
|
||||
case TokenType::KwOutputs:
|
||||
consume(TokenType::KwOutputs);
|
||||
consume(TokenType::Equals);
|
||||
pkg.outputs = parse_string_list();
|
||||
break;
|
||||
|
||||
case TokenType::KwDepends:
|
||||
consume(TokenType::KwDepends);
|
||||
consume(TokenType::Equals);
|
||||
consume(TokenType::Lbracket);
|
||||
skip_newlines();
|
||||
while (!at(TokenType::Rbracket) && !at(TokenType::Eof)) {
|
||||
pkg.depends.push_back(parse_dependency_item());
|
||||
skip_newlines();
|
||||
if (at(TokenType::Comma)) { consume(TokenType::Comma); }
|
||||
skip_newlines();
|
||||
}
|
||||
consume(TokenType::Rbracket);
|
||||
break;
|
||||
|
||||
case TokenType::KwFeatures:
|
||||
consume(TokenType::KwFeatures);
|
||||
consume(TokenType::Lbrace);
|
||||
skip_newlines();
|
||||
while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
if (at(TokenType::Newline)) { advance(); continue; }
|
||||
auto key = consume(TokenType::Ident).lexeme;
|
||||
consume(TokenType::Equals);
|
||||
pkg.features[key] = parse_feature_def();
|
||||
skip_newlines();
|
||||
}
|
||||
consume(TokenType::Rbrace);
|
||||
break;
|
||||
|
||||
case TokenType::KwConfig:
|
||||
consume(TokenType::KwConfig);
|
||||
consume(TokenType::Lbrace);
|
||||
skip_newlines();
|
||||
while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
if (at(TokenType::Newline)) { advance(); continue; }
|
||||
pkg.config_files.push_back(parse_config_file());
|
||||
skip_newlines();
|
||||
}
|
||||
consume(TokenType::Rbrace);
|
||||
break;
|
||||
|
||||
case TokenType::KwPatches:
|
||||
consume(TokenType::KwPatches);
|
||||
consume(TokenType::Equals);
|
||||
consume(TokenType::Lbracket);
|
||||
skip_newlines();
|
||||
while (!at(TokenType::Rbracket) && !at(TokenType::Eof)) {
|
||||
pkg.patches.push_back(parse_patch_item());
|
||||
skip_newlines();
|
||||
if (at(TokenType::Comma)) { consume(TokenType::Comma); }
|
||||
skip_newlines();
|
||||
}
|
||||
consume(TokenType::Rbracket);
|
||||
break;
|
||||
|
||||
case TokenType::KwEnv:
|
||||
consume(TokenType::KwEnv);
|
||||
consume(TokenType::Lbrace);
|
||||
skip_newlines();
|
||||
while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
if (at(TokenType::Newline)) { advance(); continue; }
|
||||
auto key = consume(TokenType::Ident).lexeme;
|
||||
bool soft = false;
|
||||
if (at(TokenType::Ident) && current_.lexeme == "?=") {
|
||||
soft = true;
|
||||
advance();
|
||||
} else {
|
||||
consume(TokenType::Equals);
|
||||
}
|
||||
pkg.env_entries.push_back(
|
||||
{std::move(key), consume(TokenType::String).lexeme, soft});
|
||||
skip_newlines();
|
||||
}
|
||||
consume(TokenType::Rbrace);
|
||||
break;
|
||||
|
||||
case TokenType::KwPrepare:
|
||||
consume(TokenType::KwPrepare);
|
||||
pkg.prepare = parse_phase();
|
||||
break;
|
||||
|
||||
case TokenType::KwBuild:
|
||||
consume(TokenType::KwBuild);
|
||||
pkg.build = parse_phase();
|
||||
break;
|
||||
|
||||
case TokenType::KwCheck:
|
||||
consume(TokenType::KwCheck);
|
||||
pkg.check = parse_phase();
|
||||
break;
|
||||
|
||||
case TokenType::KwInstall:
|
||||
consume(TokenType::KwInstall);
|
||||
pkg.install = parse_phase();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ParseError(current_.line, current_.col,
|
||||
std::format("unexpected token '{}' in package body",
|
||||
token_name(current_.type)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> Parser::parse_string_list() {
|
||||
consume(TokenType::Lbracket);
|
||||
skip_newlines();
|
||||
std::vector<std::string> items;
|
||||
while (!at(TokenType::Rbracket) && !at(TokenType::Eof)) {
|
||||
items.push_back(consume(TokenType::String).lexeme);
|
||||
skip_newlines();
|
||||
if (at(TokenType::Comma)) { consume(TokenType::Comma); }
|
||||
skip_newlines();
|
||||
}
|
||||
consume(TokenType::Rbracket);
|
||||
return items;
|
||||
}
|
||||
|
||||
Dependency Parser::parse_dependency_item() {
|
||||
if (at(TokenType::String)) {
|
||||
auto raw = consume(TokenType::String).lexeme;
|
||||
auto colon = raw.find(':');
|
||||
if (colon != std::string::npos) {
|
||||
return {raw.substr(0, colon), "", raw.substr(colon + 1)};
|
||||
}
|
||||
return {std::move(raw), "", ""};
|
||||
}
|
||||
|
||||
consume(TokenType::Lbrace);
|
||||
skip_newlines();
|
||||
Dependency dep;
|
||||
while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
if (at(TokenType::Newline)) { advance(); continue; }
|
||||
auto key = current_.lexeme; // accept Ident or keyword as object key
|
||||
advance();
|
||||
consume(TokenType::Equals);
|
||||
if (key == "name") { dep.name = consume(TokenType::String).lexeme; }
|
||||
else if (key == "version") { dep.version = consume(TokenType::String).lexeme; }
|
||||
else if (key == "output") { dep.output = consume(TokenType::String).lexeme; }
|
||||
else if (key == "feature") { dep.feature = consume(TokenType::String).lexeme; }
|
||||
else { throw ParseError(current_.line, current_.col,
|
||||
std::format("unknown dependency key '{}'", key)); }
|
||||
skip_newlines();
|
||||
if (at(TokenType::Comma)) { consume(TokenType::Comma); }
|
||||
skip_newlines();
|
||||
}
|
||||
consume(TokenType::Rbrace);
|
||||
return dep;
|
||||
}
|
||||
|
||||
ConfigFile Parser::parse_config_file() {
|
||||
consume(TokenType::Ident); // "file"
|
||||
ConfigFile cf;
|
||||
cf.path = consume(TokenType::String).lexeme;
|
||||
|
||||
if (at(TokenType::Ident) && current_.lexeme == "mode") {
|
||||
advance();
|
||||
consume(TokenType::Equals);
|
||||
cf.mode = consume(TokenType::String).lexeme;
|
||||
}
|
||||
|
||||
consume(TokenType::Lbrace);
|
||||
skip_newlines();
|
||||
while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
if (at(TokenType::Newline)) { advance(); continue; }
|
||||
auto key = current_.lexeme;
|
||||
advance();
|
||||
consume(TokenType::Equals);
|
||||
std::string val;
|
||||
while (!at(TokenType::Newline) && !at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
if (!val.empty() && !at(TokenType::Comma)) { val += ' '; }
|
||||
val += current_.lexeme;
|
||||
advance();
|
||||
}
|
||||
cf.entries[key] = std::move(val);
|
||||
skip_newlines();
|
||||
}
|
||||
consume(TokenType::Rbrace);
|
||||
return cf;
|
||||
}
|
||||
|
||||
bool Parser::parse_bool() {
|
||||
if (at(TokenType::KwTrue)) { advance(); return true; }
|
||||
if (at(TokenType::KwFalse)) { advance(); return false; }
|
||||
throw ParseError(current_.line, current_.col,
|
||||
std::format("expected 'true' or 'false', got '{}'",
|
||||
token_name(current_.type)));
|
||||
}
|
||||
|
||||
FeatureDef Parser::parse_feature_def() {
|
||||
if (at(TokenType::KwTrue) || at(TokenType::KwFalse)) {
|
||||
return {parse_bool(), false, ""};
|
||||
}
|
||||
|
||||
consume(TokenType::Lbrace);
|
||||
skip_newlines();
|
||||
FeatureDef f;
|
||||
while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
if (at(TokenType::Newline)) { advance(); continue; }
|
||||
auto key = current_.lexeme;
|
||||
advance();
|
||||
consume(TokenType::Equals);
|
||||
if (key == "enabled") {
|
||||
f.enabled = parse_bool();
|
||||
} else if (key == "force") {
|
||||
f.force = parse_bool();
|
||||
} else if (key == "flag") {
|
||||
std::string val;
|
||||
while (!at(TokenType::Newline) && !at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
if (!val.empty() && !at(TokenType::Comma)) { val += ' '; }
|
||||
val += current_.lexeme;
|
||||
advance();
|
||||
}
|
||||
f.flag = std::move(val);
|
||||
}
|
||||
skip_newlines();
|
||||
if (at(TokenType::Comma)) { consume(TokenType::Comma); }
|
||||
skip_newlines();
|
||||
}
|
||||
consume(TokenType::Rbrace);
|
||||
return f;
|
||||
}
|
||||
|
||||
Patch Parser::parse_patch_item() {
|
||||
if (at(TokenType::String)) {
|
||||
return {consume(TokenType::String).lexeme, "", 1};
|
||||
}
|
||||
|
||||
consume(TokenType::Lbrace);
|
||||
skip_newlines();
|
||||
Patch p;
|
||||
while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
if (at(TokenType::Newline)) { advance(); continue; }
|
||||
auto key = current_.lexeme;
|
||||
advance();
|
||||
consume(TokenType::Equals);
|
||||
if (key == "url") {
|
||||
p.url = consume(TokenType::String).lexeme;
|
||||
} else if (key == "sha256") {
|
||||
p.sha256 = consume(TokenType::String).lexeme;
|
||||
} else if (key == "level") {
|
||||
p.level = std::stoi(std::string(current_.lexeme));
|
||||
advance();
|
||||
}
|
||||
skip_newlines();
|
||||
if (at(TokenType::Comma)) { consume(TokenType::Comma); }
|
||||
skip_newlines();
|
||||
}
|
||||
consume(TokenType::Rbrace);
|
||||
return p;
|
||||
}
|
||||
|
||||
Phase Parser::parse_phase() {
|
||||
consume(TokenType::Lbrace);
|
||||
skip_newlines();
|
||||
|
||||
Phase phase;
|
||||
while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) {
|
||||
if (at(TokenType::Newline)) { advance(); continue; }
|
||||
phase.commands.push_back(parse_command_line());
|
||||
}
|
||||
consume(TokenType::Rbrace);
|
||||
return 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 += ' '; }
|
||||
cmd += current_.lexeme;
|
||||
advance();
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
PackageDef parse(std::string_view source) {
|
||||
Parser p(source);
|
||||
return p.parse();
|
||||
}
|
||||
|
||||
} // namespace kappa::dsl
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "kappa/eval/vars.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace kappa::eval {
|
||||
|
||||
Scope make_default_scope() {
|
||||
Scope s;
|
||||
s.builtins["prefix"] = "/usr";
|
||||
s.builtins["jobs"] = "1";
|
||||
s.builtins["jobopts"] = "-j1";
|
||||
s.builtins["destdir"] = "/kappa/temp/destdir";
|
||||
s.builtins["userargs"] = "";
|
||||
return s;
|
||||
}
|
||||
|
||||
ResolvedVar resolve(std::string_view name, const Scope& scope) {
|
||||
auto dot = name.find('.');
|
||||
if (dot == std::string_view::npos) {
|
||||
if (auto it = scope.builtins.find(std::string(name)); it != scope.builtins.end()) {
|
||||
return {it->second, VarKind::Builtin};
|
||||
}
|
||||
if (auto it = scope.package.find(std::string(name)); it != scope.package.end()) {
|
||||
return {it->second, VarKind::Package};
|
||||
}
|
||||
return {{}, VarKind::Unknown};
|
||||
}
|
||||
|
||||
auto ns = name.substr(0, dot);
|
||||
auto key = name.substr(dot + 1);
|
||||
|
||||
if (ns == "cfg") {
|
||||
if (auto it = scope.config.find(std::string(key)); it != scope.config.end()) {
|
||||
return {it->second, VarKind::Config};
|
||||
}
|
||||
return {{}, VarKind::Unknown};
|
||||
}
|
||||
|
||||
if (ns == "feature") {
|
||||
if (auto it = scope.features.find(std::string(key)); it != scope.features.end()) {
|
||||
const auto& f = it->second;
|
||||
if (!f.enabled) { return {"", VarKind::Feature}; }
|
||||
return {f.flag, VarKind::Feature};
|
||||
}
|
||||
return {{}, VarKind::Unknown};
|
||||
}
|
||||
|
||||
return {{}, VarKind::Unknown};
|
||||
}
|
||||
|
||||
std::string interpolate(std::string_view input, const Scope& scope) {
|
||||
std::string result;
|
||||
result.reserve(input.size());
|
||||
std::size_t i = 0;
|
||||
bool had_substitution = false;
|
||||
|
||||
while (i < input.size()) {
|
||||
if (input[i] == '$' && i + 1 < input.size() && input[i + 1] == '{') {
|
||||
auto end = input.find('}', i + 2);
|
||||
if (end == std::string_view::npos) {
|
||||
result += input.substr(i);
|
||||
break;
|
||||
}
|
||||
auto var_name = input.substr(i + 2, end - (i + 2));
|
||||
auto rv = resolve(var_name, scope);
|
||||
result += rv.value;
|
||||
had_substitution = true;
|
||||
i = end + 1;
|
||||
} else {
|
||||
result += input[i];
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
if (had_substitution && result.contains("${")) {
|
||||
return interpolate(result, scope);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace kappa::eval
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "kappa/package.hpp"
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::filesystem::path toml_path = (argc > 1) ? argv[1] : "package.toml";
|
||||
|
||||
if (!std::filesystem::exists(toml_path)) {
|
||||
std::cerr << "Error: '" << toml_path << "' not found\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
auto pkg = kappa::parse_package(toml_path.native());
|
||||
kappa::print_package(std::cout, pkg);
|
||||
return 0;
|
||||
} catch (const toml::parse_error& e) {
|
||||
std::cerr << "Parse error:\n" << e << '\n';
|
||||
return 1;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
#include "kappa/package.hpp"
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace kappa {
|
||||
namespace {
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
static constexpr std::array hash_keys = {"sha512"sv, "sha256"sv, "md5"sv};
|
||||
|
||||
static constexpr std::array known_extensions = {
|
||||
"tar.xz"sv, "tar.gz"sv, "tar.zst"sv, "zip"sv, "tar"sv, "zst"sv, "git"sv, "bs2"sv
|
||||
};
|
||||
|
||||
static std::string_view find_hash(const toml::table& src) {
|
||||
for (auto key : hash_keys) {
|
||||
if (src.contains(key)) { return key; }
|
||||
}
|
||||
return ""sv;
|
||||
}
|
||||
|
||||
static std::string extract_extension(std::string_view url) {
|
||||
auto path = url.substr(0, url.find_first_of("?#"));
|
||||
auto filename = path.substr(path.rfind('/') + 1);
|
||||
|
||||
for (auto ext : known_extensions) {
|
||||
auto dotted = std::string(".") + std::string(ext);
|
||||
if (filename.ends_with(dotted)) { return std::string(ext); }
|
||||
}
|
||||
|
||||
auto dot = filename.rfind('.');
|
||||
if (dot != std::string_view::npos) { return std::string(filename.substr(dot + 1)); }
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool is_known_extension(std::string_view ext) {
|
||||
return std::any_of(known_extensions.begin(), known_extensions.end(),
|
||||
[ext](auto k) { return ext == k; });
|
||||
}
|
||||
|
||||
static std::string interpolate(std::string raw, const toml::table& vars) {
|
||||
std::string result;
|
||||
result.reserve(raw.size());
|
||||
std::size_t i = 0;
|
||||
|
||||
while (i < raw.size()) {
|
||||
if (raw[i] == '$' && i + 1 < raw.size() && raw[i + 1] == '{') {
|
||||
auto end = raw.find('}', i + 2);
|
||||
if (end == std::string::npos) {
|
||||
result += raw.substr(i);
|
||||
break;
|
||||
}
|
||||
auto key = raw.substr(i + 2, end - (i + 2));
|
||||
result += vars[key].value_or("");
|
||||
i = end + 1;
|
||||
} else {
|
||||
result += raw[i];
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PackageConfig parse_package(const std::string& toml_path) {
|
||||
auto tbl = toml::parse_file(toml_path);
|
||||
PackageConfig pkg;
|
||||
|
||||
if (auto* pkg_tbl = tbl["package"].as_table()) {
|
||||
pkg.name = (*pkg_tbl)["name"].value_or("");
|
||||
pkg.version = (*pkg_tbl)["version"].value_or("");
|
||||
pkg.description = (*pkg_tbl)["description"].value_or("");
|
||||
}
|
||||
|
||||
if (auto* src = tbl["source"].as_table()) {
|
||||
auto url_str = (*src)["url"].value_or(""sv);
|
||||
auto* pkg_tbl = tbl["package"].as_table();
|
||||
auto resolved = interpolate(std::string(url_str),
|
||||
pkg_tbl != nullptr ? *pkg_tbl : toml::table{});
|
||||
pkg.source_url = std::move(resolved);
|
||||
|
||||
pkg.source_extension = extract_extension(pkg.source_url);
|
||||
|
||||
auto hash_name = find_hash(*src);
|
||||
if (!hash_name.empty()) {
|
||||
pkg.source_hash = std::string(hash_name) + ':'
|
||||
+ std::string((*src)[hash_name].value_or(""sv));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* deps_tbl = tbl["dependencies"].as_table()) {
|
||||
if (auto* arr = (*deps_tbl)["deps"].as_array()) {
|
||||
for (std::size_t i = 0; i < arr->size(); ++i) {
|
||||
pkg.dependencies.emplace_back((*arr)[i].value_or(""sv));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* bld = tbl["build"].as_table()) {
|
||||
pkg.build_system = (*bld)["system"].value_or(""sv);
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
void print_package(std::ostream& os, const PackageConfig& pkg) {
|
||||
os << "package.name = " << pkg.name << '\n';
|
||||
os << "package.version = " << pkg.version << '\n';
|
||||
os << "package.description = " << pkg.description << '\n';
|
||||
|
||||
if (!pkg.source_url.empty()) {
|
||||
os << "source.url = " << pkg.source_url << '\n';
|
||||
}
|
||||
if (!pkg.source_extension.empty()) {
|
||||
os << "source.extension = " << pkg.source_extension;
|
||||
if (!is_known_extension(pkg.source_extension)) {
|
||||
os << " (unrecognised)";
|
||||
}
|
||||
os << '\n';
|
||||
}
|
||||
if (!pkg.source_hash.empty()) {
|
||||
os << "source.hash = " << pkg.source_hash << '\n';
|
||||
}
|
||||
|
||||
if (!pkg.dependencies.empty()) {
|
||||
os << "dependencies = [";
|
||||
for (std::size_t i = 0; i < pkg.dependencies.size(); ++i) {
|
||||
if (i > 0) { os << ", "; }
|
||||
os << '"' << pkg.dependencies[i] << '"';
|
||||
}
|
||||
os << "]\n";
|
||||
}
|
||||
|
||||
if (!pkg.build_system.empty()) {
|
||||
os << "build.system = " << pkg.build_system << '\n';
|
||||
}
|
||||
|
||||
os << '\n';
|
||||
}
|
||||
|
||||
} // namespace kappa
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "kappa/paths.hpp"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
namespace kappa::paths {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
} // namespace kappa::paths
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
|
||||
"name": "kappa",
|
||||
"version": "0.1.0",
|
||||
"dependencies": [
|
||||
"tomlplusplus"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user