feat: Linux/cloud platform stack (TLS, registry, static/cross, selfhost PM)
ci / build (ubuntu) (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
ci / build (ubuntu) (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
Ship the QUALITY_PLAN platform focus: thin/minimal runtime, --static/--target, Nexus HTTPS/mTLS with graceful stop, lock checksums + install --locked, selfhost registry (search/add/HTTP), containers, and CI smokes for cloud path.
This commit is contained in:
@@ -514,8 +514,174 @@ module CBackend {
|
||||
CBE_MarkMovedFromNodeHint(cbe, node, "");
|
||||
}
|
||||
|
||||
/// Find a same-module function by name (for cross-fn ownership, session 77).
|
||||
func CBE_FindFuncByName(cbe: *CEmitter, name: String) -> *HirFunc {
|
||||
if cbe == null as *CEmitter || cbe.mod == null as *HirModule { return null as *HirFunc; }
|
||||
if name == null as String || String_Eq(name, "") { return null as *HirFunc; }
|
||||
var i: int = 0;
|
||||
while i < cbe.mod.funcCount {
|
||||
if String_Eq(cbe.mod.funcs[i].name, name) {
|
||||
return &cbe.mod.funcs[i];
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return null as *HirFunc;
|
||||
}
|
||||
|
||||
/// If `node` is `&local` (or load of that), return owner local (alias-resolved).
|
||||
func CBE_ArgAmpOwner(cbe: *CEmitter, node: *HirNode) -> String {
|
||||
if node == null as *HirNode { return ""; }
|
||||
var n: *HirNode = node;
|
||||
if n.kind == hLoad { n = n.child1; }
|
||||
if n == null as *HirNode { return ""; }
|
||||
if n.kind == hUnary && n.intValue == tkAmp {
|
||||
let op: *HirNode = n.child1;
|
||||
if op != null as *HirNode && op.kind == hVar {
|
||||
return CBE_ResolvePtrAlias(cbe, op.strValue);
|
||||
}
|
||||
}
|
||||
// bare pointer local that aliases an owner
|
||||
if n.kind == hVar {
|
||||
let owner: String = CBE_ResolvePtrAlias(cbe, n.strValue);
|
||||
if !String_Eq(owner, n.strValue) {
|
||||
return owner;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// True if C type name looks like a pointer (`Bag*` / `*Bag`).
|
||||
func CBE_TypeNameIsPointer(tn: String) -> bool {
|
||||
if tn == null as String || String_Eq(tn, "") { return false; }
|
||||
if String_Contains(tn, "*") { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
/// If HIR expr is a field path rooted at `param`, mark `owner` partial-moved.
|
||||
func CBE_ApplyIfParamFieldMove(cbe: *CEmitter, expr: *HirNode, param: String, owner: String) {
|
||||
if expr == null as *HirNode { return; }
|
||||
if String_Eq(param, "") || String_Eq(owner, "") { return; }
|
||||
// Whole pointee: *param
|
||||
if expr.kind == hUnary && expr.intValue == tkStar {
|
||||
let op: *HirNode = expr.child1;
|
||||
if op != null as *HirNode && op.kind == hVar && String_Eq(op.strValue, param) {
|
||||
CBE_AddMoved(cbe, owner);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if expr.kind == hFieldAccess || expr.kind == hFieldPtr || expr.kind == hArrowField {
|
||||
let base: String = CBE_BaseVarNameRaw(expr);
|
||||
if String_Eq(base, param) {
|
||||
let path: String = CBE_FieldPathFromNode(expr);
|
||||
if !String_Eq(path, "") {
|
||||
CBE_AddPartialMoved(cbe, owner, path);
|
||||
CBE_AddMoved(cbe, owner);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if expr.kind == hLoad {
|
||||
CBE_ApplyIfParamFieldMove(cbe, expr.child1, param, owner);
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk callee HIR body; when param fields are moved, mark call-site `owner`.
|
||||
func CBE_ScanBodyParamMoves(cbe: *CEmitter, node: *HirNode, param: String, owner: String) {
|
||||
if node == null as *HirNode { return; }
|
||||
let kind: int = node.kind;
|
||||
if kind == hReturn {
|
||||
CBE_ApplyIfParamFieldMove(cbe, node.child1, param, owner);
|
||||
return;
|
||||
}
|
||||
if kind == hStore {
|
||||
CBE_ApplyIfParamFieldMove(cbe, node.child2, param, owner);
|
||||
// still walk both sides for nested
|
||||
CBE_ScanBodyParamMoves(cbe, node.child1, param, owner);
|
||||
CBE_ScanBodyParamMoves(cbe, node.child2, param, owner);
|
||||
return;
|
||||
}
|
||||
if kind == hBlock {
|
||||
// Stmts linked via child3 starting at child1 (selfhost HIR convention).
|
||||
var s: *HirNode = node.child1;
|
||||
while s != null as *HirNode {
|
||||
CBE_ScanBodyParamMoves(cbe, s, param, owner);
|
||||
s = s.child3;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if kind == hIf {
|
||||
// cond, then, else
|
||||
CBE_ScanBodyParamMoves(cbe, node.child1, param, owner);
|
||||
CBE_ScanBodyParamMoves(cbe, node.child2, param, owner);
|
||||
CBE_ScanBodyParamMoves(cbe, node.child3, param, owner);
|
||||
return;
|
||||
}
|
||||
if kind == hWhile || kind == hLoop {
|
||||
CBE_ScanBodyParamMoves(cbe, node.child1, param, owner);
|
||||
CBE_ScanBodyParamMoves(cbe, node.child2, param, owner);
|
||||
return;
|
||||
}
|
||||
// Do not walk child3 generically — it is often the *next sibling* in a
|
||||
// statement list (handled by hBlock). Only walk expression children.
|
||||
CBE_ScanBodyParamMoves(cbe, node.child1, param, owner);
|
||||
CBE_ScanBodyParamMoves(cbe, node.child2, param, owner);
|
||||
}
|
||||
|
||||
/// Session 77: `TakeItems(&bag)` — scan callee body for moves of `p.field`.
|
||||
func CBE_MarkCrossFuncFromCall(cbe: *CEmitter, call: *HirNode) {
|
||||
if call == null as *HirNode || call.kind != hCall { return; }
|
||||
let fname: String = call.strValue;
|
||||
if String_Eq(fname, "") { return; }
|
||||
let f: *HirFunc = CBE_FindFuncByName(cbe, fname);
|
||||
if f == null as *HirFunc || f.body == null as *HirNode { return; }
|
||||
// Collect args: child1, child2, then extraData list
|
||||
var argIdx: int = 0;
|
||||
var arg: *HirNode = call.child1;
|
||||
while argIdx < f.paramCount {
|
||||
if arg == null as *HirNode {
|
||||
// try child2 for arg1
|
||||
if argIdx == 1 { arg = call.child2; }
|
||||
}
|
||||
if arg == null as *HirNode && argIdx >= 2 {
|
||||
break;
|
||||
}
|
||||
// For args beyond 2, walk extraData
|
||||
if argIdx >= 2 {
|
||||
var ai: int = 0;
|
||||
var cur: *HirArgList = call.extraData as *HirArgList;
|
||||
while ai < call.extraCount && cur != null as *HirArgList {
|
||||
if ai == argIdx - 2 {
|
||||
arg = cur.node;
|
||||
break;
|
||||
}
|
||||
cur = cur.next;
|
||||
ai = ai + 1;
|
||||
}
|
||||
}
|
||||
if arg == null as *HirNode { break; }
|
||||
let hp: *HirParam = CBE_FuncParam(f, argIdx);
|
||||
if hp != null as *HirParam && CBE_TypeNameIsPointer(hp.typeName) {
|
||||
let owner: String = CBE_ArgAmpOwner(cbe, arg);
|
||||
if !String_Eq(owner, "") {
|
||||
CBE_ScanBodyParamMoves(cbe, f.body, hp.name, owner);
|
||||
}
|
||||
}
|
||||
// advance arg for next
|
||||
if argIdx == 0 {
|
||||
arg = call.child2;
|
||||
} else {
|
||||
arg = null as *HirNode;
|
||||
}
|
||||
argIdx = argIdx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
func CBE_MarkMovedFromNodeHint(cbe: *CEmitter, node: *HirNode, valueTypeHint: String) {
|
||||
if node == null as *HirNode { return; }
|
||||
if node.kind == hCall {
|
||||
CBE_MarkCrossFuncFromCall(cbe, node);
|
||||
return;
|
||||
}
|
||||
if node.kind == hVar {
|
||||
CBE_AddMoved(cbe, node.strValue);
|
||||
return;
|
||||
|
||||
+584
-153
@@ -15,6 +15,7 @@ module Cli {
|
||||
extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
|
||||
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
|
||||
extern func bux_system(cmd: String) -> int;
|
||||
extern func bux_process_output(cmd: String) -> String;
|
||||
extern func bux_getenv(name: String) -> String;
|
||||
extern func bux_setenv(name: String, value: String) -> int;
|
||||
extern func bux_cc_ld_stable() -> String;
|
||||
@@ -37,6 +38,175 @@ module Cli {
|
||||
return bux_dir_exists(path) != 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Link / runtime helpers (session 76 — selfhost parity with bootstrap 75)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_EnvTruthy(name: String) -> bool {
|
||||
let v: String = bux_getenv(name);
|
||||
if v == null as String { return false; }
|
||||
if String_Eq(v, "1") { return true; }
|
||||
if String_Eq(v, "true") { return true; }
|
||||
if String_Eq(v, "yes") { return true; }
|
||||
if String_Eq(v, "on") { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
func Cli_RuntimeRel(isStatic: bool, targetTriple: String) -> String {
|
||||
let env: String = bux_getenv("BUX_RUNTIME");
|
||||
if env != null as String {
|
||||
if String_Eq(env, "win") || String_Eq(env, "windows") {
|
||||
return "rt/runtime_win.c";
|
||||
}
|
||||
if String_Eq(env, "minimal") || String_Eq(env, "thin") ||
|
||||
String_Eq(env, "embed") || String_Eq(env, "embedded") ||
|
||||
String_Eq(env, "freestanding") {
|
||||
return "rt/runtime_minimal.c";
|
||||
}
|
||||
if String_Eq(env, "full") || String_Eq(env, "posix") {
|
||||
return "rt/runtime.c";
|
||||
}
|
||||
}
|
||||
if isStatic {
|
||||
return "rt/runtime_minimal.c";
|
||||
}
|
||||
if targetTriple != null as String && !String_Eq(targetTriple, "") {
|
||||
return "rt/runtime_minimal.c";
|
||||
}
|
||||
return "rt/runtime.c";
|
||||
}
|
||||
|
||||
func Cli_IsThinRuntimePath(rtPath: String) -> bool {
|
||||
if String_Contains(rtPath, "runtime_minimal.c") { return true; }
|
||||
if String_Contains(rtPath, "runtime_win.c") { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
func Cli_FindRtFile(projectDir: String, rel: String) -> String {
|
||||
// Try projectDir/rel, then parent, then grandparent (selfhost build dirs).
|
||||
var p: String = bux_path_join(projectDir, rel);
|
||||
if FileExists(p) { return p; }
|
||||
p = bux_path_join(projectDir, String_Concat("../", rel));
|
||||
if FileExists(p) { return p; }
|
||||
p = bux_path_join(projectDir, String_Concat("../../", rel));
|
||||
if FileExists(p) { return p; }
|
||||
// Next to stdlib: $BUX_STDLIB/../rt/...
|
||||
let stdlib: String = bux_getenv("BUX_STDLIB");
|
||||
if stdlib != null as String && !String_Eq(stdlib, "") {
|
||||
p = bux_path_join(bux_path_parent(stdlib), rel);
|
||||
if FileExists(p) { return p; }
|
||||
}
|
||||
// Also try finding lib/ next to project to infer repo root
|
||||
let libBeside: String = Cli_FindStdlibDir(projectDir);
|
||||
if !String_Eq(libBeside, "") {
|
||||
p = bux_path_join(bux_path_parent(libBeside), rel);
|
||||
if FileExists(p) { return p; }
|
||||
}
|
||||
// cwd-relative (single-file builds)
|
||||
if FileExists(rel) { return rel; }
|
||||
p = String_Concat("../", rel);
|
||||
if FileExists(p) { return p; }
|
||||
p = String_Concat("../../", rel);
|
||||
if FileExists(p) { return p; }
|
||||
return "";
|
||||
}
|
||||
|
||||
func Cli_PickCc(targetTriple: String) -> String {
|
||||
let envCc: String = bux_getenv("BUX_CC");
|
||||
if envCc != null as String && !String_Eq(envCc, "") {
|
||||
return envCc;
|
||||
}
|
||||
if targetTriple != null as String && !String_Eq(targetTriple, "") {
|
||||
let tripleGcc: String = String_Concat(targetTriple, "-gcc");
|
||||
let probe: String = String_Concat("command -v ", tripleGcc);
|
||||
probe = String_Concat(probe, " >/dev/null 2>&1");
|
||||
if bux_system(probe) == 0 {
|
||||
return tripleGcc;
|
||||
}
|
||||
let probeClang: String = "command -v clang >/dev/null 2>&1";
|
||||
if bux_system(probeClang) == 0 {
|
||||
return "clang";
|
||||
}
|
||||
return tripleGcc;
|
||||
}
|
||||
return "cc";
|
||||
}
|
||||
|
||||
func Cli_CcNeedsTargetFlag(ccBin: String) -> bool {
|
||||
// clang needs -target; *-gcc is already a cross binary.
|
||||
if String_Eq(ccBin, "clang") { return true; }
|
||||
if String_Contains(ccBin, "clang-") { return true; }
|
||||
if String_Contains(ccBin, "/clang") { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
func Cli_LinkProgram(cFile: String, outBin: String, projectDir: String,
|
||||
targetTriple: String, isRelease: bool, isStatic: bool) -> int {
|
||||
let relRt: String = Cli_RuntimeRel(isStatic, targetTriple);
|
||||
let rtPath: String = Cli_FindRtFile(projectDir, relRt);
|
||||
let ioPath: String = Cli_FindRtFile(projectDir, "rt/io.c");
|
||||
if String_Eq(rtPath, "") {
|
||||
Print("Error: runtime not found: ");
|
||||
PrintLine(relRt);
|
||||
return 1;
|
||||
}
|
||||
if String_Eq(ioPath, "") {
|
||||
PrintLine("Error: rt/io.c not found");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let thin: bool = Cli_IsThinRuntimePath(rtPath);
|
||||
var optFlags: String = "-O0 -g";
|
||||
if isRelease {
|
||||
optFlags = "-O2 -DNDEBUG";
|
||||
}
|
||||
let extraCf: String = bux_getenv("BUX_CFLAGS");
|
||||
if extraCf != null as String && !String_Eq(extraCf, "") {
|
||||
optFlags = String_Concat(optFlags, " ");
|
||||
optFlags = String_Concat(optFlags, extraCf);
|
||||
}
|
||||
if isStatic || Cli_EnvTruthy("BUX_STATIC") {
|
||||
optFlags = String_Concat(optFlags, " -static");
|
||||
}
|
||||
|
||||
let ccBin: String = Cli_PickCc(targetTriple);
|
||||
var cmdBuf: StringBuilder = StringBuilder_NewCap(768);
|
||||
StringBuilder_Append(&cmdBuf, ccBin);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
StringBuilder_Append(&cmdBuf, optFlags);
|
||||
if targetTriple != null as String && !String_Eq(targetTriple, "") {
|
||||
if Cli_CcNeedsTargetFlag(ccBin) {
|
||||
StringBuilder_Append(&cmdBuf, " -target ");
|
||||
StringBuilder_Append(&cmdBuf, targetTriple);
|
||||
}
|
||||
}
|
||||
if thin {
|
||||
StringBuilder_Append(&cmdBuf, " -ffunction-sections -fdata-sections");
|
||||
} else {
|
||||
StringBuilder_Append(&cmdBuf, " -pthread");
|
||||
StringBuilder_Append(&cmdBuf, bux_cc_ld_stable());
|
||||
}
|
||||
StringBuilder_Append(&cmdBuf, " -o ");
|
||||
StringBuilder_Append(&cmdBuf, outBin);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
StringBuilder_Append(&cmdBuf, cFile);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
StringBuilder_Append(&cmdBuf, rtPath);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
StringBuilder_Append(&cmdBuf, ioPath);
|
||||
if thin {
|
||||
StringBuilder_Append(&cmdBuf, " -Wl,--gc-sections -lm");
|
||||
} else {
|
||||
StringBuilder_Append(&cmdBuf, " -lm -lssl -lcrypto");
|
||||
}
|
||||
let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf));
|
||||
if ccRc != 0 {
|
||||
PrintLine("Error: C compilation failed");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Diagnostic formatting (Rust-style errors with snippets)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -289,11 +459,11 @@ func Cli_Compile(source: String, sourceName: String, targetTriple: String) -> St
|
||||
// Build command
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_Build(srcPath: String, outPath: String, targetTriple: String, isRelease: bool) -> int {
|
||||
func Cli_Build(srcPath: String, outPath: String, targetTriple: String, isRelease: bool, isStatic: bool) -> int {
|
||||
// If building the standard project entry point, use full project build
|
||||
// which merges stdlib and supports multi-file projects
|
||||
if String_Eq(srcPath, "src/Main.bux") || String_Eq(srcPath, "src/main.bux") {
|
||||
let rc: int = Cli_BuildProject(".", targetTriple, isRelease);
|
||||
let rc: int = Cli_BuildProject(".", targetTriple, isRelease, isStatic);
|
||||
if rc == 0 && !String_Eq(outPath, "build/main") {
|
||||
// Rename output if custom path was requested
|
||||
bux_system(String_Concat(String_Concat("mv build/main ", outPath), " 2>/dev/null || true"));
|
||||
@@ -328,69 +498,10 @@ func Cli_Build(srcPath: String, outPath: String, targetTriple: String, isRelease
|
||||
Print(" → C written to ");
|
||||
PrintLine(cFile);
|
||||
|
||||
// Find runtime.c and io.c for linking (rt/ directory)
|
||||
var rtPath: String = "rt/runtime.c";
|
||||
var ioPath: String = "rt/io.c";
|
||||
if !FileExists(rtPath) {
|
||||
rtPath = "../rt/runtime.c";
|
||||
}
|
||||
if !FileExists(ioPath) {
|
||||
ioPath = "../rt/io.c";
|
||||
}
|
||||
if !FileExists(rtPath) {
|
||||
rtPath = "../../rt/runtime.c";
|
||||
}
|
||||
if !FileExists(ioPath) {
|
||||
ioPath = "../../rt/io.c";
|
||||
}
|
||||
|
||||
// Compile with cc or clang for cross-compilation
|
||||
// Default: -O0 -g (debug, matches bootstrap). --release: -O2 -DNDEBUG.
|
||||
PrintLine("Compiling C...");
|
||||
var cmdBuf: StringBuilder = StringBuilder_NewCap(512);
|
||||
var optFlags: String = "-O0 -g";
|
||||
if isRelease {
|
||||
optFlags = "-O2 -DNDEBUG";
|
||||
}
|
||||
let extraCf: String = bux_getenv("BUX_CFLAGS");
|
||||
if extraCf != null as String && !String_Eq(extraCf, "") {
|
||||
optFlags = String_Concat(optFlags, " ");
|
||||
optFlags = String_Concat(optFlags, extraCf);
|
||||
}
|
||||
if !String_Eq(targetTriple, "") {
|
||||
StringBuilder_Append(&cmdBuf, "clang ");
|
||||
StringBuilder_Append(&cmdBuf, optFlags);
|
||||
StringBuilder_Append(&cmdBuf, " -pthread");
|
||||
StringBuilder_Append(&cmdBuf, bux_cc_ld_stable());
|
||||
StringBuilder_Append(&cmdBuf, " -target ");
|
||||
StringBuilder_Append(&cmdBuf, targetTriple);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
} else {
|
||||
StringBuilder_Append(&cmdBuf, "cc ");
|
||||
StringBuilder_Append(&cmdBuf, optFlags);
|
||||
StringBuilder_Append(&cmdBuf, " -pthread");
|
||||
StringBuilder_Append(&cmdBuf, bux_cc_ld_stable());
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
}
|
||||
StringBuilder_Append(&cmdBuf, "-o ");
|
||||
StringBuilder_Append(&cmdBuf, outPath);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
StringBuilder_Append(&cmdBuf, cFile);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
if FileExists(rtPath) {
|
||||
StringBuilder_Append(&cmdBuf, rtPath);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
}
|
||||
if FileExists(ioPath) {
|
||||
StringBuilder_Append(&cmdBuf, ioPath);
|
||||
StringBuilder_Append(&cmdBuf, " ");
|
||||
}
|
||||
StringBuilder_Append(&cmdBuf, "-lm");
|
||||
StringBuilder_Append(&cmdBuf, " -lcrypto");
|
||||
let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf));
|
||||
if ccRc != 0 {
|
||||
PrintLine("Error: C compilation failed");
|
||||
return 1;
|
||||
let linkRc: int = Cli_LinkProgram(cFile, outPath, ".", targetTriple, isRelease, isStatic);
|
||||
if linkRc != 0 {
|
||||
return linkRc;
|
||||
}
|
||||
Print(" → Binary: ");
|
||||
PrintLine(outPath);
|
||||
@@ -922,6 +1033,40 @@ func Cli_Init() -> int {
|
||||
// Test command — build and run tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Registry — search / add by name (session 81)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_Search(query: String) -> int {
|
||||
let reg: Registry = Reg_FindIndex();
|
||||
if String_Eq(reg.path, "") {
|
||||
if !String_Eq(reg.sourceUrl, "") {
|
||||
Print("Error: failed to fetch registry from ");
|
||||
PrintLine(reg.sourceUrl);
|
||||
PrintLine("hint: curl/wget + network, or BUX_REGISTRY=local file");
|
||||
} else {
|
||||
PrintLine("Error: no package registry (set BUX_REGISTRY or config/registry.toml)");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if !String_Eq(reg.sourceUrl, "") {
|
||||
Print("Registry: ");
|
||||
PrintLine(reg.sourceUrl);
|
||||
Print(" (cached: ");
|
||||
Print(reg.path);
|
||||
PrintLine(")");
|
||||
} else {
|
||||
Print("Registry: ");
|
||||
PrintLine(reg.path);
|
||||
}
|
||||
let hits: int = Reg_Search(reg, query);
|
||||
if hits == 0 {
|
||||
PrintLine("No packages matched.");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Add — add a dependency to bux.toml
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -952,6 +1097,48 @@ func Cli_Add(pkgName: String, url: String) -> int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Resolve package from registry index and add as path/git dep.
|
||||
func Cli_AddFromRegistry(pkgName: String, versionReq: String) -> int {
|
||||
let reg: Registry = Reg_FindIndex();
|
||||
if String_Eq(reg.path, "") {
|
||||
if !String_Eq(reg.sourceUrl, "") {
|
||||
Print("Error: failed to fetch registry from ");
|
||||
PrintLine(reg.sourceUrl);
|
||||
} else {
|
||||
PrintLine("Error: no package registry (set BUX_REGISTRY)");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
let pkg: RegistryPackage = Reg_Lookup(reg, pkgName, versionReq);
|
||||
if String_Eq(pkg.name, "") {
|
||||
Print("Error: package '");
|
||||
Print(pkgName);
|
||||
PrintLine("' not found in registry");
|
||||
PrintLine("hint: buxc search | buxc add name <url>");
|
||||
return 1;
|
||||
}
|
||||
var url: String = pkg.source;
|
||||
if !String_Eq(pkg.resolvedPath, "") {
|
||||
url = pkg.resolvedPath;
|
||||
}
|
||||
if !String_Eq(reg.sourceUrl, "") {
|
||||
Print("Resolved '");
|
||||
Print(pkgName);
|
||||
Print("' ");
|
||||
Print(pkg.version);
|
||||
Print(" from registry ");
|
||||
PrintLine(reg.sourceUrl);
|
||||
} else {
|
||||
Print("Resolved '");
|
||||
Print(pkgName);
|
||||
Print("' ");
|
||||
Print(pkg.version);
|
||||
Print(" from registry ");
|
||||
PrintLine(reg.path);
|
||||
}
|
||||
return Cli_Add(pkgName, url);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Remove — remove a dependency from bux.toml
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -982,6 +1169,242 @@ func Cli_Remove(pkgName: String) -> int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Package checksum (session 80 — selfhost install --locked parity)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_PackageChecksum(dir: String) -> String {
|
||||
// Same idea as bootstrap: sha1 of sorted *.bux paths+contents via shell.
|
||||
if String_Eq(dir, "") || !DirExists(dir) {
|
||||
return "";
|
||||
}
|
||||
let cmd: String = String_Concat(
|
||||
"find \"",
|
||||
String_Concat(dir, "\" -name '*.bux' -type f 2>/dev/null | LC_ALL=C sort | xargs cat 2>/dev/null | sha1sum | awk '{print $1}'")
|
||||
);
|
||||
let out: String = bux_process_output(cmd);
|
||||
if out == null as String { return ""; }
|
||||
// trim trailing newline
|
||||
var s: String = String_Trim(out);
|
||||
return s;
|
||||
}
|
||||
|
||||
func Cli_ShellQuote(s: String) -> String {
|
||||
return String_Concat("\"", String_Concat(s, "\""));
|
||||
}
|
||||
|
||||
func Cli_IsAbsPath(p: String) -> bool {
|
||||
if String_Eq(p, "") { return false; }
|
||||
if p[0] == 47 as char8 { return true; } // /
|
||||
return false;
|
||||
}
|
||||
|
||||
func Cli_DepResolvedPath(projectDir: String, depName: String, depUrl: String) -> String {
|
||||
// Path dep: absolute or relative; git dep: deps/<name>
|
||||
if Cli_IsAbsPath(depUrl) {
|
||||
return depUrl;
|
||||
}
|
||||
if String_StartsWith(depUrl, "http://") || String_StartsWith(depUrl, "https://") ||
|
||||
String_EndsWith(depUrl, ".git") {
|
||||
return bux_path_join(bux_path_join(projectDir, "deps"), depName);
|
||||
}
|
||||
// relative path
|
||||
return bux_path_join(projectDir, depUrl);
|
||||
}
|
||||
|
||||
func Cli_InstallLocked(projectDir: String) -> int {
|
||||
let lockPath: String = bux_path_join(projectDir, "bux.lock");
|
||||
if !FileExists(lockPath) {
|
||||
PrintLine("Error: install --locked: bux.lock missing (run install first)");
|
||||
return 1;
|
||||
}
|
||||
let content: String = ReadFile(lockPath);
|
||||
if content == null as String || String_Eq(content, "") {
|
||||
PrintLine("Error: install --locked: empty lock");
|
||||
return 1;
|
||||
}
|
||||
// Parse [[Package]] blocks: Name, Version, Source, Checksum
|
||||
var name: String = "";
|
||||
var version: String = "";
|
||||
var source: String = "";
|
||||
var checksum: String = "";
|
||||
var verified: int = 0;
|
||||
let nlines: uint = String_SplitCount(content, "\n");
|
||||
var li: uint = 0;
|
||||
while li <= nlines {
|
||||
var line: String = "";
|
||||
if li < nlines {
|
||||
line = String_Trim(String_SplitPart(content, "\n", li));
|
||||
}
|
||||
let isEnd: bool = li == nlines;
|
||||
let isNew: bool = String_StartsWith(line, "[[Package]]") || String_StartsWith(line, "[[package]]");
|
||||
if (isNew || isEnd) && !String_Eq(name, "") {
|
||||
// verify entry
|
||||
var path: String = source;
|
||||
if !Cli_IsAbsPath(path) && !String_StartsWith(path, "http") {
|
||||
path = bux_path_join(projectDir, source);
|
||||
}
|
||||
if String_StartsWith(source, "http://") || String_StartsWith(source, "https://") ||
|
||||
String_EndsWith(source, ".git") {
|
||||
path = bux_path_join(bux_path_join(projectDir, "deps"), name);
|
||||
}
|
||||
if !DirExists(path) {
|
||||
Print("Error: install --locked: missing ");
|
||||
Print(name);
|
||||
Print(" at ");
|
||||
PrintLine(path);
|
||||
return 1;
|
||||
}
|
||||
if !String_Eq(checksum, "") {
|
||||
let got: String = Cli_PackageChecksum(path);
|
||||
if !String_Eq(got, checksum) {
|
||||
Print("Error: install --locked: checksum mismatch for ");
|
||||
PrintLine(name);
|
||||
Print(" lock: "); PrintLine(checksum);
|
||||
Print(" got: "); PrintLine(got);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Print("locked ok: ");
|
||||
Print(name);
|
||||
Print(" ");
|
||||
PrintLine(version);
|
||||
verified = verified + 1;
|
||||
name = "";
|
||||
version = "";
|
||||
source = "";
|
||||
checksum = "";
|
||||
}
|
||||
if isEnd { break; }
|
||||
if isNew {
|
||||
li = li + 1;
|
||||
continue;
|
||||
}
|
||||
if String_StartsWith(line, "Name") || String_StartsWith(line, "name") {
|
||||
// Name = "x"
|
||||
let parts: uint = String_SplitCount(line, "\"");
|
||||
if parts >= 2 {
|
||||
name = String_SplitPart(line, "\"", 1);
|
||||
}
|
||||
} else if String_StartsWith(line, "Version") || String_StartsWith(line, "version") {
|
||||
let parts: uint = String_SplitCount(line, "\"");
|
||||
if parts >= 2 {
|
||||
version = String_SplitPart(line, "\"", 1);
|
||||
}
|
||||
} else if String_StartsWith(line, "Source") || String_StartsWith(line, "source") {
|
||||
let parts: uint = String_SplitCount(line, "\"");
|
||||
if parts >= 2 {
|
||||
source = String_SplitPart(line, "\"", 1);
|
||||
}
|
||||
} else if String_StartsWith(line, "Checksum") || String_StartsWith(line, "checksum") {
|
||||
let parts: uint = String_SplitCount(line, "\"");
|
||||
if parts >= 2 {
|
||||
checksum = String_SplitPart(line, "\"", 1);
|
||||
}
|
||||
}
|
||||
li = li + 1;
|
||||
}
|
||||
Print("install --locked: ");
|
||||
PrintInt(verified as int64);
|
||||
PrintLine(" package(s) verified");
|
||||
return 0;
|
||||
}
|
||||
|
||||
func Cli_Install(projectDir: String, lockedOnly: bool) -> int {
|
||||
if lockedOnly {
|
||||
return Cli_InstallLocked(projectDir);
|
||||
}
|
||||
let tomlPath: String = bux_path_join(projectDir, "bux.toml");
|
||||
if !FileExists(tomlPath) {
|
||||
PrintLine("Error: no bux.toml found");
|
||||
return 1;
|
||||
}
|
||||
let man: Manifest = Manifest_Load(tomlPath);
|
||||
if man.depCount == 0 {
|
||||
// empty lock
|
||||
discard WriteFile(bux_path_join(projectDir, "bux.lock"), "");
|
||||
PrintLine("install: no dependencies (empty lock)");
|
||||
return 0;
|
||||
}
|
||||
var sb: StringBuilder = StringBuilder_NewCap(1024);
|
||||
var i: int = 0;
|
||||
while i < man.depCount {
|
||||
var depName: String = "";
|
||||
var depUrl: String = "";
|
||||
if i == 0 { depName = man.depName0; depUrl = man.depUrl0; }
|
||||
else if i == 1 { depName = man.depName1; depUrl = man.depUrl1; }
|
||||
else if i == 2 { depName = man.depName2; depUrl = man.depUrl2; }
|
||||
else if i == 3 { depName = man.depName3; depUrl = man.depUrl3; }
|
||||
else if i == 4 { depName = man.depName4; depUrl = man.depUrl4; }
|
||||
else if i == 5 { depName = man.depName5; depUrl = man.depUrl5; }
|
||||
else if i == 6 { depName = man.depName6; depUrl = man.depUrl6; }
|
||||
else if i == 7 { depName = man.depName7; depUrl = man.depUrl7; }
|
||||
|
||||
// Fetch git deps into deps/<name> when missing
|
||||
if String_StartsWith(depUrl, "http://") || String_StartsWith(depUrl, "https://") ||
|
||||
String_EndsWith(depUrl, ".git") {
|
||||
let depsDir: String = bux_path_join(projectDir, "deps");
|
||||
discard bux_mkdir_if_needed(depsDir);
|
||||
let depPath: String = bux_path_join(depsDir, depName);
|
||||
if !DirExists(depPath) {
|
||||
Print("Fetching "); Print(depName); Print(" from "); PrintLine(depUrl);
|
||||
let cmd: String = String_Concat("git clone --quiet \"", String_Concat(depUrl, String_Concat("\" \"", String_Concat(depPath, "\""))));
|
||||
if bux_system(cmd) != 0 {
|
||||
Print("Error: failed to fetch "); PrintLine(depName);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let path: String = Cli_DepResolvedPath(projectDir, depName, depUrl);
|
||||
if !DirExists(path) {
|
||||
Print("Error: dependency path missing: ");
|
||||
PrintLine(path);
|
||||
return 1;
|
||||
}
|
||||
var ver: String = "0.0.0";
|
||||
let depToml: String = bux_path_join(path, "bux.toml");
|
||||
if FileExists(depToml) {
|
||||
let dm: Manifest = Manifest_Load(depToml);
|
||||
if !String_Eq(dm.version, "") {
|
||||
ver = dm.version;
|
||||
}
|
||||
}
|
||||
let csum: String = Cli_PackageChecksum(path);
|
||||
StringBuilder_Append(&sb, "[[Package]]\n");
|
||||
StringBuilder_Append(&sb, "Name = \"");
|
||||
StringBuilder_Append(&sb, depName);
|
||||
StringBuilder_Append(&sb, "\"\n");
|
||||
StringBuilder_Append(&sb, "Version = \"");
|
||||
StringBuilder_Append(&sb, ver);
|
||||
StringBuilder_Append(&sb, "\"\n");
|
||||
StringBuilder_Append(&sb, "Source = \"");
|
||||
StringBuilder_Append(&sb, path);
|
||||
StringBuilder_Append(&sb, "\"\n");
|
||||
if !String_Eq(csum, "") {
|
||||
StringBuilder_Append(&sb, "Checksum = \"");
|
||||
StringBuilder_Append(&sb, csum);
|
||||
StringBuilder_Append(&sb, "\"\n");
|
||||
}
|
||||
StringBuilder_Append(&sb, "\n");
|
||||
Print("Resolved ");
|
||||
Print(depName);
|
||||
Print(" ");
|
||||
Print(ver);
|
||||
Print(" → ");
|
||||
PrintLine(path);
|
||||
i = i + 1;
|
||||
}
|
||||
let lockPath: String = bux_path_join(projectDir, "bux.lock");
|
||||
if !WriteFile(lockPath, StringBuilder_Build(&sb)) {
|
||||
PrintLine("Error: cannot write bux.lock");
|
||||
return 1;
|
||||
}
|
||||
Print("Generated ");
|
||||
PrintLine(lockPath);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fetch — download dependencies into deps/
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1302,7 +1725,7 @@ func Cli_Test(projectDir: String, filter: String) -> int {
|
||||
// Without --filter, build and run the project's own Main first.
|
||||
// With --filter, only run matching tests/*.bux files.
|
||||
if String_Eq(filter, "") {
|
||||
let mainRc: int = Cli_BuildProject(projectDir, "", false);
|
||||
let mainRc: int = Cli_BuildProject(projectDir, "", false, false);
|
||||
if mainRc != 0 {
|
||||
PrintLine("Main test build failed");
|
||||
return mainRc;
|
||||
@@ -1419,7 +1842,7 @@ func Cli_Test(projectDir: String, filter: String) -> int {
|
||||
}
|
||||
}
|
||||
|
||||
let buildRc: int = Cli_BuildProject(tmpDir, "", false);
|
||||
let buildRc: int = Cli_BuildProject(tmpDir, "", false, false);
|
||||
if buildRc != 0 {
|
||||
PrintLine("FAIL │");
|
||||
failed = failed + 1;
|
||||
@@ -1467,7 +1890,7 @@ func Cli_Test(projectDir: String, filter: String) -> int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool) -> int {
|
||||
func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool, isStatic: bool) -> int {
|
||||
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
|
||||
var outName: String = man.name;
|
||||
if String_Eq(outName, "") {
|
||||
@@ -1629,27 +2052,37 @@ func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool)
|
||||
PrintLine("");
|
||||
}
|
||||
|
||||
// Merge dependency declarations (deps shadow stdlib, user shadows deps)
|
||||
// Merge dependency declarations (path or deps/<name>; shadow stdlib)
|
||||
if man.depCount > 0 {
|
||||
let depsDir: String = bux_path_join(projectDir, "deps");
|
||||
var di: int = 0;
|
||||
while di < man.depCount {
|
||||
var depName: String = "";
|
||||
if di == 0 { depName = man.depName0; }
|
||||
else if di == 1 { depName = man.depName1; }
|
||||
else if di == 2 { depName = man.depName2; }
|
||||
else if di == 3 { depName = man.depName3; }
|
||||
else if di == 4 { depName = man.depName4; }
|
||||
else if di == 5 { depName = man.depName5; }
|
||||
else if di == 6 { depName = man.depName6; }
|
||||
else if di == 7 { depName = man.depName7; }
|
||||
let depSrcDir: String = bux_path_join(bux_path_join(depsDir, depName), "src");
|
||||
var depUrl: String = "";
|
||||
if di == 0 { depName = man.depName0; depUrl = man.depUrl0; }
|
||||
else if di == 1 { depName = man.depName1; depUrl = man.depUrl1; }
|
||||
else if di == 2 { depName = man.depName2; depUrl = man.depUrl2; }
|
||||
else if di == 3 { depName = man.depName3; depUrl = man.depUrl3; }
|
||||
else if di == 4 { depName = man.depName4; depUrl = man.depUrl4; }
|
||||
else if di == 5 { depName = man.depName5; depUrl = man.depUrl5; }
|
||||
else if di == 6 { depName = man.depName6; depUrl = man.depUrl6; }
|
||||
else if di == 7 { depName = man.depName7; depUrl = man.depUrl7; }
|
||||
// Prefer resolved path (absolute / relative / deps/<name>)
|
||||
let depRoot: String = Cli_DepResolvedPath(projectDir, depName, depUrl);
|
||||
var depSrcDir: String = bux_path_join(depRoot, "src");
|
||||
if !DirExists(depSrcDir) {
|
||||
// package root may be the src tree itself
|
||||
if DirExists(depRoot) {
|
||||
depSrcDir = depRoot;
|
||||
}
|
||||
}
|
||||
if DirExists(depSrcDir) {
|
||||
var depFileCount: int = 0;
|
||||
let depFiles: *String = bux_list_dir(depSrcDir, ".bux", &depFileCount);
|
||||
if depFileCount > 0 {
|
||||
Print("Merging dependency: ");
|
||||
PrintLine(depName);
|
||||
Print(" from ");
|
||||
PrintLine(depSrcDir);
|
||||
var dfi: int = 0;
|
||||
while dfi < depFileCount {
|
||||
Print(" Merging ");
|
||||
@@ -1661,6 +2094,9 @@ func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool)
|
||||
dfi = dfi + 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Print("WARN: dependency sources not found for ");
|
||||
PrintLine(depName);
|
||||
}
|
||||
di = di + 1;
|
||||
}
|
||||
@@ -1742,69 +2178,11 @@ func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool)
|
||||
Print("C code written to ");
|
||||
PrintLine(cFile);
|
||||
|
||||
// Find runtime.c and io.c (look in rt/ directory)
|
||||
var rtPath: String = bux_path_join(projectDir, "rt/runtime.c");
|
||||
var ioPath: String = bux_path_join(projectDir, "rt/io.c");
|
||||
if !FileExists(rtPath) {
|
||||
rtPath = bux_path_join(projectDir, "../rt/runtime.c");
|
||||
}
|
||||
if !FileExists(ioPath) {
|
||||
ioPath = bux_path_join(projectDir, "../rt/io.c");
|
||||
}
|
||||
if !FileExists(rtPath) {
|
||||
rtPath = bux_path_join(projectDir, "../../rt/runtime.c");
|
||||
}
|
||||
if !FileExists(ioPath) {
|
||||
ioPath = bux_path_join(projectDir, "../../rt/io.c");
|
||||
}
|
||||
|
||||
// Compile with cc — default debug (-O0 -g); --release → -O2 -DNDEBUG
|
||||
PrintLine("Compiling C...");
|
||||
let outBin: String = bux_path_join(buildDir, outName);
|
||||
var ccBuf: StringBuilder = StringBuilder_NewCap(512);
|
||||
var optFlags2: String = "-O0 -g";
|
||||
if isRelease {
|
||||
optFlags2 = "-O2 -DNDEBUG";
|
||||
}
|
||||
let extraCf2: String = bux_getenv("BUX_CFLAGS");
|
||||
if extraCf2 != null as String && !String_Eq(extraCf2, "") {
|
||||
optFlags2 = String_Concat(optFlags2, " ");
|
||||
optFlags2 = String_Concat(optFlags2, extraCf2);
|
||||
}
|
||||
if !String_Eq(targetTriple, "") {
|
||||
StringBuilder_Append(&ccBuf, "clang ");
|
||||
StringBuilder_Append(&ccBuf, optFlags2);
|
||||
StringBuilder_Append(&ccBuf, " -pthread");
|
||||
StringBuilder_Append(&ccBuf, bux_cc_ld_stable());
|
||||
StringBuilder_Append(&ccBuf, " -target ");
|
||||
StringBuilder_Append(&ccBuf, targetTriple);
|
||||
StringBuilder_Append(&ccBuf, " ");
|
||||
} else {
|
||||
StringBuilder_Append(&ccBuf, "cc ");
|
||||
StringBuilder_Append(&ccBuf, optFlags2);
|
||||
StringBuilder_Append(&ccBuf, " -pthread");
|
||||
StringBuilder_Append(&ccBuf, bux_cc_ld_stable());
|
||||
StringBuilder_Append(&ccBuf, " ");
|
||||
}
|
||||
StringBuilder_Append(&ccBuf, "-o ");
|
||||
StringBuilder_Append(&ccBuf, outBin);
|
||||
StringBuilder_Append(&ccBuf, " ");
|
||||
StringBuilder_Append(&ccBuf, cFile);
|
||||
StringBuilder_Append(&ccBuf, " ");
|
||||
if FileExists(rtPath) {
|
||||
StringBuilder_Append(&ccBuf, rtPath);
|
||||
StringBuilder_Append(&ccBuf, " ");
|
||||
}
|
||||
if FileExists(ioPath) {
|
||||
StringBuilder_Append(&ccBuf, ioPath);
|
||||
StringBuilder_Append(&ccBuf, " ");
|
||||
}
|
||||
StringBuilder_Append(&ccBuf, "-lm");
|
||||
StringBuilder_Append(&ccBuf, " -lcrypto");
|
||||
let ccRc: int = bux_system(StringBuilder_Build(&ccBuf));
|
||||
if ccRc != 0 {
|
||||
PrintLine("Error: C compilation failed");
|
||||
return 1;
|
||||
let linkRc2: int = Cli_LinkProgram(cFile, outBin, projectDir, targetTriple, isRelease, isStatic);
|
||||
if linkRc2 != 0 {
|
||||
return linkRc2;
|
||||
}
|
||||
|
||||
Print("Build successful: ");
|
||||
@@ -1816,8 +2194,8 @@ func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool)
|
||||
// Run command — build project and execute
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_RunProject(projectDir: String, targetTriple: String, isRelease: bool) -> int {
|
||||
let rc: int = Cli_BuildProject(projectDir, targetTriple, isRelease);
|
||||
func Cli_RunProject(projectDir: String, targetTriple: String, isRelease: bool, isStatic: bool) -> int {
|
||||
let rc: int = Cli_BuildProject(projectDir, targetTriple, isRelease, isStatic);
|
||||
if rc != 0 {
|
||||
return rc;
|
||||
}
|
||||
@@ -1837,9 +2215,10 @@ func Cli_RunProject(projectDir: String, targetTriple: String, isRelease: bool) -
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Cli_Run(args: *String, argCount: int) -> int {
|
||||
/* Scan for --target and --release flags before processing command */
|
||||
/* Scan for --target / --release / --static before processing command */
|
||||
var targetTriple: String = "";
|
||||
var isRelease: bool = false;
|
||||
var isStatic: bool = Cli_EnvTruthy("BUX_STATIC");
|
||||
var ai: int = 1;
|
||||
while ai < argCount {
|
||||
if String_Eq(args[ai], "--target") && ai + 1 < argCount {
|
||||
@@ -1854,7 +2233,15 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
||||
ai = ai - 1;
|
||||
} else if String_Eq(args[ai], "--release") {
|
||||
isRelease = true;
|
||||
/* Remove --release from args by shifting */
|
||||
var j: int = ai;
|
||||
while j + 1 < argCount {
|
||||
args[j] = args[j + 1];
|
||||
j = j + 1;
|
||||
}
|
||||
argCount = argCount - 1;
|
||||
ai = ai - 1;
|
||||
} else if String_Eq(args[ai], "--static") {
|
||||
isStatic = true;
|
||||
var j: int = ai;
|
||||
while j + 1 < argCount {
|
||||
args[j] = args[j + 1];
|
||||
@@ -1869,12 +2256,17 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
||||
if argCount < 2 {
|
||||
PrintLine("Bux Self-Hosting Compiler v0.2.0");
|
||||
PrintLine("Usage: buxc <command> [args]");
|
||||
PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, doc, test, run, project, help, version");
|
||||
PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version");
|
||||
PrintLine(" test --filter <name> Only run tests/*.bux whose name contains <name>");
|
||||
PrintLine(" search [query] Search package registry");
|
||||
PrintLine(" install [--locked] Write/verify bux.lock (checksums)");
|
||||
PrintLine(" add <name> [ver|url] Registry resolve or explicit source");
|
||||
PrintLine(" fmt --check [path] Exit 1 if any file would be reformatted (CI)");
|
||||
PrintLine(" doc --out file.md [path] API docs from /// comments");
|
||||
PrintLine(" --release Optimize (-O2 -DNDEBUG); default is -O0 -g");
|
||||
PrintLine(" BUX_CFLAGS Extra flags appended to cc");
|
||||
PrintLine(" --static Fully-static link (minimal runtime)");
|
||||
PrintLine(" --target <triple> Cross-compile (e.g. aarch64-linux-gnu)");
|
||||
PrintLine(" BUX_CFLAGS / BUX_CC / BUX_RUNTIME / BUX_STATIC / BUX_REGISTRY");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1887,12 +2279,17 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
||||
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
|
||||
PrintLine("Bux Self-Hosting Compiler v0.2.0");
|
||||
PrintLine("Usage: buxc <command> [args]");
|
||||
PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, doc, test, run, project, help, version");
|
||||
PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version");
|
||||
PrintLine(" test --filter <name> Only run tests/*.bux whose name contains <name>");
|
||||
PrintLine(" search [query] Search package registry");
|
||||
PrintLine(" install [--locked] Write/verify bux.lock (checksums)");
|
||||
PrintLine(" add <name> [ver|url] Registry resolve or explicit source");
|
||||
PrintLine(" fmt --check [path] Exit 1 if any file would be reformatted (CI)");
|
||||
PrintLine(" doc --out file.md [path] API docs from /// comments");
|
||||
PrintLine(" --release Optimize (-O2 -DNDEBUG); default is -O0 -g");
|
||||
PrintLine(" BUX_CFLAGS Extra flags appended to cc");
|
||||
PrintLine(" --static Fully-static link (minimal runtime)");
|
||||
PrintLine(" --target <triple> Cross-compile (e.g. aarch64-linux-gnu)");
|
||||
PrintLine(" BUX_CFLAGS / BUX_CC / BUX_RUNTIME / BUX_STATIC / BUX_REGISTRY");
|
||||
PrintLine("Pipeline modules:");
|
||||
PrintLine(" Lexer ✅");
|
||||
PrintLine(" Parser ✅");
|
||||
@@ -1914,12 +2311,31 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
||||
return Cli_Init();
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "search") {
|
||||
var query: String = "";
|
||||
if argCount >= 3 {
|
||||
query = args[2];
|
||||
}
|
||||
return Cli_Search(query);
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "add") {
|
||||
if argCount < 4 {
|
||||
PrintLine("Usage: buxc add <name> <url>");
|
||||
if argCount < 3 {
|
||||
PrintLine("Usage: buxc add <name> [version|url]");
|
||||
PrintLine(" buxc add greet # resolve from registry");
|
||||
PrintLine(" buxc add greet 0.1.1 # registry version");
|
||||
PrintLine(" buxc add greet /path/or/git-url");
|
||||
return 1;
|
||||
}
|
||||
return Cli_Add(args[2], args[3]);
|
||||
if argCount >= 4 {
|
||||
let a3: String = args[3];
|
||||
if !String_Contains(a3, "/") && !String_StartsWith(a3, "http") &&
|
||||
!String_EndsWith(a3, ".git") {
|
||||
return Cli_AddFromRegistry(args[2], a3);
|
||||
}
|
||||
return Cli_Add(args[2], a3);
|
||||
}
|
||||
return Cli_AddFromRegistry(args[2], "*");
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "remove") {
|
||||
@@ -1934,6 +2350,21 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
||||
return Cli_Fetch();
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "install") {
|
||||
var lockedOnly: bool = false;
|
||||
var dir: String = ".";
|
||||
var ii: int = 2;
|
||||
while ii < argCount {
|
||||
if String_Eq(args[ii], "--locked") {
|
||||
lockedOnly = true;
|
||||
} else if !String_StartsWith(args[ii], "-") {
|
||||
dir = args[ii];
|
||||
}
|
||||
ii = ii + 1;
|
||||
}
|
||||
return Cli_Install(dir, lockedOnly);
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "fmt") {
|
||||
var dir: String = ".";
|
||||
var checkOnly: bool = false;
|
||||
@@ -1980,13 +2411,13 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
||||
let out: String = "build/main";
|
||||
if argCount >= 3 { src = args[2]; }
|
||||
if argCount >= 4 { out = args[3]; }
|
||||
return Cli_Build(src, out, targetTriple, isRelease);
|
||||
return Cli_Build(src, out, targetTriple, isRelease, isStatic);
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "project") {
|
||||
let dir: String = ".";
|
||||
if argCount >= 3 { dir = args[2]; }
|
||||
return Cli_BuildProject(dir, targetTriple, isRelease);
|
||||
return Cli_BuildProject(dir, targetTriple, isRelease, isStatic);
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "test") {
|
||||
@@ -2011,7 +2442,7 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
||||
if String_Eq(cmd, "run") {
|
||||
let dir: String = ".";
|
||||
if argCount >= 3 { dir = args[2]; }
|
||||
return Cli_RunProject(dir, targetTriple, isRelease);
|
||||
return Cli_RunProject(dir, targetTriple, isRelease, isStatic);
|
||||
}
|
||||
|
||||
Print("Unknown command: ");
|
||||
|
||||
+11
-2
@@ -449,12 +449,21 @@ module MacroExpand {
|
||||
e.macroPat = pat;
|
||||
return e;
|
||||
}
|
||||
// expr / tt
|
||||
// expr rejects stmt/pat wrappers
|
||||
if String_Eq(kindStr, "expr") {
|
||||
if aexp.kind == ekMacroStmt || aexp.kind == ekMacroPat { return null as *Expr; }
|
||||
return aexp;
|
||||
}
|
||||
// tt — any single call-site AST fragment (session 76; raw token trees later)
|
||||
if String_Eq(kindStr, "tt") {
|
||||
return aexp;
|
||||
}
|
||||
// default: treat as expr
|
||||
if aexp.kind == ekMacroStmt || aexp.kind == ekMacroPat { return null as *Expr; }
|
||||
return aexp;
|
||||
}
|
||||
|
||||
// Fragment kind check: "ident" | "literal" | "block" | "stmt" | "pat" | expr|tt
|
||||
// Fragment kind check: "ident" | "literal" | "block" | "stmt" | "pat" | "expr" | "tt"
|
||||
func Macro_FragMatches(kindStr: String, aexp: *Expr) -> bool {
|
||||
return Macro_CoerceArg(kindStr, aexp) != null as *Expr;
|
||||
}
|
||||
|
||||
+32
-1
@@ -61,7 +61,7 @@ module Manifest {
|
||||
currentSection = "Package";
|
||||
} else if String_StartsWith(line, "[Build]") {
|
||||
currentSection = "Build";
|
||||
} else if String_StartsWith(line, "[dependencies]") {
|
||||
} else if String_StartsWith(line, "[dependencies]") || String_StartsWith(line, "[Dependencies]") {
|
||||
currentSection = "dependencies";
|
||||
} else {
|
||||
currentSection = "";
|
||||
@@ -84,6 +84,37 @@ module Manifest {
|
||||
}
|
||||
}
|
||||
|
||||
// Inline table: { Path = "/abs/..." } — extract quoted Path value
|
||||
if String_StartsWith(val, "{") {
|
||||
var foundPath: String = "";
|
||||
let pl: uint = bux_strlen(line);
|
||||
var pi: uint = 0;
|
||||
while pi + 4 < pl {
|
||||
// match ASCII 'P','a','t','h'
|
||||
if line[pi] == 80 as char8 && line[pi + 1] == 97 as char8 &&
|
||||
line[pi + 2] == 116 as char8 && line[pi + 3] == 104 as char8 {
|
||||
var q1: int = -1;
|
||||
var qj: uint = pi;
|
||||
while qj < pl {
|
||||
if line[qj] == 34 as char8 {
|
||||
if q1 < 0 {
|
||||
q1 = qj as int;
|
||||
} else {
|
||||
foundPath = String_Slice(line, (q1 + 1) as uint, (qj as int - q1 - 1) as uint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
qj = qj + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
pi = pi + 1;
|
||||
}
|
||||
if !String_Eq(foundPath, "") {
|
||||
val = foundPath;
|
||||
}
|
||||
}
|
||||
|
||||
if String_Eq(currentSection, "Package") {
|
||||
if String_Eq(key, "Name") { m.name = val; }
|
||||
if String_Eq(key, "Version") { m.version = val; }
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
// registry.bux — package registry index for selfhost (session 81)
|
||||
// Parity with bootstrap registry.nim: local file + HTTP(S) cache.
|
||||
module Registry {
|
||||
|
||||
extern func Print(s: String);
|
||||
extern func PrintLine(s: String);
|
||||
extern func bux_strlen(s: String) -> uint;
|
||||
extern func bux_read_file(path: String) -> String;
|
||||
extern func bux_write_file(path: String, content: String) -> bool;
|
||||
extern func bux_file_exists(path: String) -> int;
|
||||
extern func bux_dir_exists(path: String) -> int;
|
||||
extern func bux_mkdir_if_needed(path: String) -> int;
|
||||
extern func bux_path_join(a: String, b: String) -> String;
|
||||
extern func bux_path_parent(path: String) -> String;
|
||||
extern func bux_getenv(name: String) -> String;
|
||||
extern func bux_getcwd() -> String;
|
||||
extern func bux_system(cmd: String) -> int;
|
||||
extern func bux_process_output(cmd: String) -> String;
|
||||
|
||||
const REG_MAX: int = 64;
|
||||
|
||||
struct RegistryPackage {
|
||||
name: String;
|
||||
version: String;
|
||||
source: String;
|
||||
description: String;
|
||||
resolvedPath: String;
|
||||
}
|
||||
|
||||
struct Registry {
|
||||
path: String; // local index path (or cache path)
|
||||
sourceUrl: String; // non-empty if from HTTP(S)
|
||||
count: int;
|
||||
// fixed slots (no dynamic arrays in selfhost compiler easily)
|
||||
p0: RegistryPackage;
|
||||
p1: RegistryPackage;
|
||||
p2: RegistryPackage;
|
||||
p3: RegistryPackage;
|
||||
p4: RegistryPackage;
|
||||
p5: RegistryPackage;
|
||||
p6: RegistryPackage;
|
||||
p7: RegistryPackage;
|
||||
p8: RegistryPackage;
|
||||
p9: RegistryPackage;
|
||||
p10: RegistryPackage;
|
||||
p11: RegistryPackage;
|
||||
p12: RegistryPackage;
|
||||
p13: RegistryPackage;
|
||||
p14: RegistryPackage;
|
||||
p15: RegistryPackage;
|
||||
// up to 16 packages is enough for smoke/demo; expand if needed
|
||||
}
|
||||
|
||||
func Reg_FileExists(path: String) -> bool {
|
||||
return bux_file_exists(path) != 0;
|
||||
}
|
||||
|
||||
func Reg_DirExists(path: String) -> bool {
|
||||
return bux_dir_exists(path) != 0;
|
||||
}
|
||||
|
||||
func Reg_IsHttpUrl(s: String) -> bool {
|
||||
if String_StartsWith(s, "http://") { return true; }
|
||||
if String_StartsWith(s, "https://") { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
func Reg_EnvTruthy(name: String) -> bool {
|
||||
let v: String = bux_getenv(name);
|
||||
if v == null as String { return false; }
|
||||
if String_Eq(v, "") { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
func Reg_HomeCacheDir() -> String {
|
||||
let home: String = bux_getenv("HOME");
|
||||
if home == null as String || String_Eq(home, "") {
|
||||
return ".bux/cache";
|
||||
}
|
||||
return bux_path_join(bux_path_join(home, ".bux"), "cache");
|
||||
}
|
||||
|
||||
func Reg_FetchHttp(url: String) -> String {
|
||||
// Download to ~/.bux/cache/registry_http.toml; return local path or "".
|
||||
let cacheDir: String = Reg_HomeCacheDir();
|
||||
discard bux_mkdir_if_needed(bux_path_join(bux_getenv("HOME"), ".bux"));
|
||||
discard bux_mkdir_if_needed(cacheDir);
|
||||
let cachePath: String = bux_path_join(cacheDir, "registry_http.toml");
|
||||
let metaPath: String = bux_path_join(cacheDir, "registry_http.url");
|
||||
let force: bool = Reg_EnvTruthy("BUX_REGISTRY_REFRESH");
|
||||
if !force && Reg_FileExists(cachePath) && Reg_FileExists(metaPath) {
|
||||
let cachedUrl: String = String_Trim(bux_read_file(metaPath));
|
||||
if String_Eq(cachedUrl, url) {
|
||||
return cachePath;
|
||||
}
|
||||
}
|
||||
var kflag: String = "";
|
||||
if Reg_EnvTruthy("BUX_REGISTRY_INSECURE") {
|
||||
kflag = " -k";
|
||||
}
|
||||
// prefer curl
|
||||
let curlCmd: String = String_Concat(
|
||||
"curl -fsSL",
|
||||
String_Concat(kflag, String_Concat(" --max-time 30 -o \"", String_Concat(cachePath, String_Concat("\" \"", String_Concat(url, "\"")))))
|
||||
);
|
||||
var ok: bool = false;
|
||||
if bux_system("command -v curl >/dev/null 2>&1") == 0 {
|
||||
ok = bux_system(curlCmd) == 0 && Reg_FileExists(cachePath);
|
||||
} else if bux_system("command -v wget >/dev/null 2>&1") == 0 {
|
||||
var nflag: String = "";
|
||||
if Reg_EnvTruthy("BUX_REGISTRY_INSECURE") {
|
||||
nflag = " --no-check-certificate";
|
||||
}
|
||||
let wgetCmd: String = String_Concat(
|
||||
"wget -q",
|
||||
String_Concat(nflag, String_Concat(" -T 30 -O \"", String_Concat(cachePath, String_Concat("\" \"", String_Concat(url, "\"")))))
|
||||
);
|
||||
ok = bux_system(wgetCmd) == 0 && Reg_FileExists(cachePath);
|
||||
}
|
||||
if !ok {
|
||||
return "";
|
||||
}
|
||||
discard bux_write_file(metaPath, String_Concat(url, "\n"));
|
||||
return cachePath;
|
||||
}
|
||||
|
||||
func Reg_StripQuotes(val: String) -> String {
|
||||
var v: String = String_Trim(val);
|
||||
if String_StartsWith(v, "\"") && String_EndsWith(v, "\"") {
|
||||
let n: uint = bux_strlen(v);
|
||||
if n >= 2 {
|
||||
return String_Slice(v, 1, n - 2);
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
func Reg_ResolveSource(src: String, indexDir: String) -> String {
|
||||
// file: or path: → absolute path; else ""
|
||||
var p: String = src;
|
||||
if String_StartsWith(src, "file:") {
|
||||
p = String_Slice(src, 5, bux_strlen(src) - 5);
|
||||
if String_StartsWith(p, "//") {
|
||||
p = String_Slice(p, 2, bux_strlen(p) - 2);
|
||||
}
|
||||
} else if String_StartsWith(src, "path:") {
|
||||
p = String_Slice(src, 5, bux_strlen(src) - 5);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
if String_StartsWith(p, "/") {
|
||||
return p;
|
||||
}
|
||||
return bux_path_join(indexDir, p);
|
||||
}
|
||||
|
||||
func Reg_SetPkg(reg: *Registry, idx: int, pkg: RegistryPackage) {
|
||||
if idx == 0 { reg.p0 = pkg; }
|
||||
else if idx == 1 { reg.p1 = pkg; }
|
||||
else if idx == 2 { reg.p2 = pkg; }
|
||||
else if idx == 3 { reg.p3 = pkg; }
|
||||
else if idx == 4 { reg.p4 = pkg; }
|
||||
else if idx == 5 { reg.p5 = pkg; }
|
||||
else if idx == 6 { reg.p6 = pkg; }
|
||||
else if idx == 7 { reg.p7 = pkg; }
|
||||
else if idx == 8 { reg.p8 = pkg; }
|
||||
else if idx == 9 { reg.p9 = pkg; }
|
||||
else if idx == 10 { reg.p10 = pkg; }
|
||||
else if idx == 11 { reg.p11 = pkg; }
|
||||
else if idx == 12 { reg.p12 = pkg; }
|
||||
else if idx == 13 { reg.p13 = pkg; }
|
||||
else if idx == 14 { reg.p14 = pkg; }
|
||||
else if idx == 15 { reg.p15 = pkg; }
|
||||
}
|
||||
|
||||
func Reg_GetPkg(reg: Registry, idx: int) -> RegistryPackage {
|
||||
if idx == 0 { return reg.p0; }
|
||||
if idx == 1 { return reg.p1; }
|
||||
if idx == 2 { return reg.p2; }
|
||||
if idx == 3 { return reg.p3; }
|
||||
if idx == 4 { return reg.p4; }
|
||||
if idx == 5 { return reg.p5; }
|
||||
if idx == 6 { return reg.p6; }
|
||||
if idx == 7 { return reg.p7; }
|
||||
if idx == 8 { return reg.p8; }
|
||||
if idx == 9 { return reg.p9; }
|
||||
if idx == 10 { return reg.p10; }
|
||||
if idx == 11 { return reg.p11; }
|
||||
if idx == 12 { return reg.p12; }
|
||||
if idx == 13 { return reg.p13; }
|
||||
if idx == 14 { return reg.p14; }
|
||||
return reg.p15;
|
||||
}
|
||||
|
||||
func Reg_ParseContent(content: String, indexPath: String) -> Registry {
|
||||
var reg: Registry;
|
||||
reg.path = indexPath;
|
||||
reg.sourceUrl = "";
|
||||
reg.count = 0;
|
||||
let indexDir: String = bux_path_parent(indexPath);
|
||||
var cur: RegistryPackage;
|
||||
var inPkg: bool = false;
|
||||
let nlines: uint = String_SplitCount(content, "\n");
|
||||
var i: uint = 0;
|
||||
while i <= nlines {
|
||||
var line: String = "";
|
||||
if i < nlines {
|
||||
line = String_Trim(String_SplitPart(content, "\n", i));
|
||||
}
|
||||
let flush: bool = (i == nlines) || String_Eq(line, "[[package]]") || String_Eq(line, "[[Package]]");
|
||||
if flush && inPkg && !String_Eq(cur.name, "") {
|
||||
cur.resolvedPath = Reg_ResolveSource(cur.source, indexDir);
|
||||
if reg.count < 16 {
|
||||
Reg_SetPkg(®, reg.count, cur);
|
||||
reg.count = reg.count + 1;
|
||||
}
|
||||
cur.name = "";
|
||||
cur.version = "";
|
||||
cur.source = "";
|
||||
cur.description = "";
|
||||
cur.resolvedPath = "";
|
||||
}
|
||||
if i == nlines { break; }
|
||||
if String_Eq(line, "") || String_StartsWith(line, "#") {
|
||||
i = i + 1;
|
||||
continue;
|
||||
}
|
||||
if String_Eq(line, "[[package]]") || String_Eq(line, "[[Package]]") {
|
||||
inPkg = true;
|
||||
i = i + 1;
|
||||
continue;
|
||||
}
|
||||
if !inPkg {
|
||||
i = i + 1;
|
||||
continue;
|
||||
}
|
||||
let eqc: uint = String_SplitCount(line, "=");
|
||||
if eqc >= 2 {
|
||||
let key: String = String_Trim(String_SplitPart(line, "=", 0));
|
||||
let val: String = Reg_StripQuotes(String_SplitPart(line, "=", 1));
|
||||
// lowercase-ish compare for common keys
|
||||
if String_Eq(key, "name") || String_Eq(key, "Name") {
|
||||
cur.name = val;
|
||||
} else if String_Eq(key, "version") || String_Eq(key, "Version") {
|
||||
cur.version = val;
|
||||
} else if String_Eq(key, "source") || String_Eq(key, "Source") {
|
||||
cur.source = val;
|
||||
} else if String_Eq(key, "description") || String_Eq(key, "Description") {
|
||||
cur.description = val;
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return reg;
|
||||
}
|
||||
|
||||
func Reg_FindIndex() -> Registry {
|
||||
var reg: Registry;
|
||||
reg.path = "";
|
||||
reg.sourceUrl = "";
|
||||
reg.count = 0;
|
||||
let env: String = bux_getenv("BUX_REGISTRY");
|
||||
if env != null as String && !String_Eq(env, "") {
|
||||
if Reg_IsHttpUrl(env) {
|
||||
let local: String = Reg_FetchHttp(env);
|
||||
if String_Eq(local, "") {
|
||||
reg.sourceUrl = env;
|
||||
return reg;
|
||||
}
|
||||
let content: String = bux_read_file(local);
|
||||
reg = Reg_ParseContent(content, local);
|
||||
reg.sourceUrl = env;
|
||||
reg.path = local;
|
||||
return reg;
|
||||
}
|
||||
if Reg_FileExists(env) {
|
||||
let content: String = bux_read_file(env);
|
||||
reg = Reg_ParseContent(content, env);
|
||||
reg.path = env;
|
||||
return reg;
|
||||
}
|
||||
}
|
||||
let home: String = bux_getenv("HOME");
|
||||
if home != null as String && !String_Eq(home, "") {
|
||||
let homeIdx: String = bux_path_join(bux_path_join(home, ".bux"), "registry.toml");
|
||||
if Reg_FileExists(homeIdx) {
|
||||
let content: String = bux_read_file(homeIdx);
|
||||
reg = Reg_ParseContent(content, homeIdx);
|
||||
reg.path = homeIdx;
|
||||
return reg;
|
||||
}
|
||||
}
|
||||
// cwd-relative candidates
|
||||
let cwd: String = bux_getcwd();
|
||||
var c0: String = bux_path_join(cwd, "config/registry.toml");
|
||||
if Reg_FileExists(c0) {
|
||||
let content: String = bux_read_file(c0);
|
||||
reg = Reg_ParseContent(content, c0);
|
||||
reg.path = c0;
|
||||
return reg;
|
||||
}
|
||||
c0 = bux_path_join(cwd, "../config/registry.toml");
|
||||
if Reg_FileExists(c0) {
|
||||
let content: String = bux_read_file(c0);
|
||||
reg = Reg_ParseContent(content, c0);
|
||||
reg.path = c0;
|
||||
return reg;
|
||||
}
|
||||
c0 = bux_path_join(cwd, "../../config/registry.toml");
|
||||
if Reg_FileExists(c0) {
|
||||
let content: String = bux_read_file(c0);
|
||||
reg = Reg_ParseContent(content, c0);
|
||||
reg.path = c0;
|
||||
return reg;
|
||||
}
|
||||
return reg;
|
||||
}
|
||||
|
||||
func Reg_VersionOk(have: String, req: String) -> bool {
|
||||
if String_Eq(req, "") || String_Eq(req, "*") { return true; }
|
||||
return String_Eq(have, req);
|
||||
}
|
||||
|
||||
// Lookup by name; versionReq "*" = last matching entry (highest listed last).
|
||||
func Reg_Lookup(reg: Registry, name: String, versionReq: String) -> RegistryPackage {
|
||||
var found: RegistryPackage;
|
||||
found.name = "";
|
||||
var i: int = 0;
|
||||
while i < reg.count {
|
||||
let p: RegistryPackage = Reg_GetPkg(reg, i);
|
||||
if String_Eq(p.name, name) && Reg_VersionOk(p.version, versionReq) {
|
||||
found = p;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
func Reg_Search(reg: Registry, query: String) -> int {
|
||||
// print hits; return count
|
||||
var hits: int = 0;
|
||||
// Dedupe by name: keep last version
|
||||
// Simple O(n^2): for each pkg, if last occurrence of name, print
|
||||
var i: int = 0;
|
||||
while i < reg.count {
|
||||
let p: RegistryPackage = Reg_GetPkg(reg, i);
|
||||
var isLast: bool = true;
|
||||
var j: int = i + 1;
|
||||
while j < reg.count {
|
||||
let q: RegistryPackage = Reg_GetPkg(reg, j);
|
||||
if String_Eq(q.name, p.name) {
|
||||
isLast = false;
|
||||
break;
|
||||
}
|
||||
j = j + 1;
|
||||
}
|
||||
if isLast {
|
||||
var ok: bool = true;
|
||||
if !String_Eq(query, "") {
|
||||
ok = String_Contains(p.name, query) || String_Contains(p.description, query);
|
||||
}
|
||||
if ok {
|
||||
Print(" ");
|
||||
Print(p.name);
|
||||
Print(" ");
|
||||
Print(p.version);
|
||||
Print(" — ");
|
||||
if !String_Eq(p.description, "") {
|
||||
PrintLine(p.description);
|
||||
} else {
|
||||
PrintLine(p.source);
|
||||
}
|
||||
hits = hits + 1;
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user