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

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:
2026-07-23 23:00:55 +03:00
parent a939f74b1b
commit a785747c37
44 changed files with 4318 additions and 279 deletions
+166
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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; }
+380
View File
@@ -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, 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;
}
}