commit 8cd4bd32bec1a5404143f121282da56f43474145
parent 31706f330ce37fb7b4f6d0239b1d8a32dae813f3
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 23:23:26 -0700
build: wire coordinator end to end
Diffstat:
17 files changed, 3705 insertions(+), 118 deletions(-)
diff --git a/driver/cmd/build_coord.c b/driver/cmd/build_coord.c
@@ -0,0 +1,411 @@
+#include <kit/build_coord.h>
+#include <kit/cas.h>
+#include <kit/core.h>
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "dist_host.h"
+#include "driver.h"
+#include "env.h"
+#include "env_build.h"
+
+#define BUILD_TOOL "build"
+#define BUILD_DEFAULT_DEF "BUILD.kit"
+#define BUILD_MAX_CONFIG 128u
+#define BUILD_MAX_ARGS 128u
+
+typedef struct BuildCli {
+ const char* store;
+ const char* root;
+ const char* def;
+ const char* target;
+ KitBuildKV config[BUILD_MAX_CONFIG];
+ size_t nconfig;
+ KitSlice args[BUILD_MAX_ARGS];
+ size_t nargs;
+ int verify;
+} BuildCli;
+
+void driver_help_build(void) {
+ driver_printf(
+ "kit build - content-addressed build coordinator\n"
+ "\n"
+ "USAGE\n"
+ " kit build [--store DIR] [--root DIR] [--def FILE]\n"
+ " [--config K=V]... [--arg VALUE]... [--verify] TARGET\n"
+ " kit build --client config-get KEY\n"
+ " kit build --client source PATH\n"
+ " kit build --client glob PATTERN\n"
+ " kit build --client need [--config K=V]... [--arg VALUE]... TARGET\n"
+ "\n"
+ "OPTIONS\n"
+ " --store DIR Build store root (default: $KIT cache/build)\n"
+ " --root DIR Workspace root (default: .)\n"
+ " --def FILE Build definition path (default: BUILD.kit)\n"
+ " --config K=V Seed propagated configuration\n"
+ " --arg VALUE Add one target-local argv value\n"
+ " --verify Verify the returned output tree in the CAS\n");
+}
+
+static const char* build_status_name(KitStatus st) {
+ switch (st) {
+ case KIT_OK: return "ok";
+ case KIT_ERR: return "error";
+ case KIT_NOMEM: return "out of memory";
+ case KIT_INVALID: return "invalid input";
+ case KIT_UNSUPPORTED: return "unsupported";
+ case KIT_MALFORMED: return "malformed input";
+ case KIT_IO: return "I/O error";
+ case KIT_NOT_FOUND: return "not found";
+ case KIT_AMBIGUOUS: return "ambiguous input";
+ }
+ return "error";
+}
+
+static int build_join_path(char* out, size_t cap, const char* a,
+ const char* b) {
+ size_t na, nb;
+ int sep;
+ if (!out || cap == 0u || !a || !b) return 1;
+ na = driver_strlen(a);
+ nb = driver_strlen(b);
+ sep = na > 0u && a[na - 1u] != '/' && a[na - 1u] != '\\';
+ if (na + (sep ? 1u : 0u) + nb + 1u > cap) return 1;
+ memcpy(out, a, na);
+ if (sep) out[na++] = '/';
+ memcpy(out + na, b, nb);
+ out[na + nb] = '\0';
+ return 0;
+}
+
+static int build_parse_config(BuildCli* cli, const char* text) {
+ const char* eq;
+ if (!cli || !text) return 1;
+ eq = driver_strchr(text, '=');
+ if (!eq || eq == text || cli->nconfig >= BUILD_MAX_CONFIG) return 1;
+ cli->config[cli->nconfig].key.s = text;
+ cli->config[cli->nconfig].key.len = (size_t)(eq - text);
+ cli->config[cli->nconfig].value = kit_slice_cstr(eq + 1);
+ ++cli->nconfig;
+ return 0;
+}
+
+static int build_client_glob_print(void* user, KitSlice path) {
+ (void)user;
+ driver_printf("%.*s\n", KIT_SLICE_ARG(path));
+ return 0;
+}
+
+static int build_client_need_parse(BuildCli* cli, int argc, char** argv) {
+ int i;
+ memset(cli, 0, sizeof *cli);
+ for (i = 3; i < argc; ++i) {
+ const char* a = argv[i];
+ if (driver_streq(a, "--config") && i + 1 < argc) {
+ if (build_parse_config(cli, argv[++i]) != 0) {
+ driver_errf(BUILD_TOOL, "bad --config, expected K=V");
+ return 2;
+ }
+ } else if (driver_streq(a, "--arg") && i + 1 < argc) {
+ if (cli->nargs >= BUILD_MAX_ARGS) {
+ driver_errf(BUILD_TOOL, "too many --arg values");
+ return 2;
+ }
+ cli->args[cli->nargs++] = kit_slice_cstr(argv[++i]);
+ } else if (a[0] == '-') {
+ driver_errf(BUILD_TOOL, "unexpected client option: %s", a);
+ return 2;
+ } else if (!cli->target) {
+ cli->target = a;
+ } else {
+ driver_errf(BUILD_TOOL, "unexpected client argument: %s", a);
+ return 2;
+ }
+ }
+ if (!cli->target) {
+ driver_errf(BUILD_TOOL, "missing client target");
+ return 2;
+ }
+ return 0;
+}
+
+static int build_client_mode(int argc, char** argv) {
+ DriverEnv env;
+ DriverBuildHost bh;
+ KitContext ctx;
+ KitBuildClient* client = NULL;
+ KitStatus st;
+ int rc = 1;
+ const char* verb;
+ if (argc < 3) {
+ driver_errf(BUILD_TOOL, "missing client command");
+ return 2;
+ }
+ verb = argv[2];
+ driver_env_init(&env);
+ ctx = driver_env_to_context(&env);
+ if (driver_build_host_init(&bh, &env) != 0) {
+ driver_errf(BUILD_TOOL, "hosted build adapter is unavailable");
+ goto out_env;
+ }
+ st = kit_build_client_open(&ctx, &bh.transport, &client);
+ if (st != KIT_OK) {
+ driver_errf(BUILD_TOOL, "failed to connect to build coordinator: %s",
+ build_status_name(st));
+ goto out_host;
+ }
+ if (driver_streq(verb, "config-get")) {
+ KitSlice value;
+ int present = 0;
+ if (argc != 4) {
+ driver_errf(BUILD_TOOL, "usage: kit build --client config-get KEY");
+ rc = 2;
+ goto out_client;
+ }
+ st = kit_build_client_config_get(client, kit_slice_cstr(argv[3]), &value,
+ &present);
+ if (st != KIT_OK) {
+ driver_errf(BUILD_TOOL, "config-get failed: %s", build_status_name(st));
+ goto out_client;
+ }
+ if (!present) {
+ rc = 1;
+ goto out_client;
+ }
+ driver_printf("%.*s\n", KIT_SLICE_ARG(value));
+ rc = 0;
+ } else if (driver_streq(verb, "source")) {
+ uint8_t blob[KIT_BUILD_HASH_LEN];
+ KitSlice path;
+ if (argc != 4) {
+ driver_errf(BUILD_TOOL, "usage: kit build --client source PATH");
+ rc = 2;
+ goto out_client;
+ }
+ st = kit_build_client_source(client, kit_slice_cstr(argv[3]), blob, &path);
+ if (st != KIT_OK) {
+ driver_errf(BUILD_TOOL, "source failed: %s", build_status_name(st));
+ goto out_client;
+ }
+ driver_printf("%.*s\n", KIT_SLICE_ARG(path));
+ rc = 0;
+ } else if (driver_streq(verb, "glob")) {
+ if (argc != 4) {
+ driver_errf(BUILD_TOOL, "usage: kit build --client glob PATTERN");
+ rc = 2;
+ goto out_client;
+ }
+ st = kit_build_client_glob(client, kit_slice_cstr(argv[3]),
+ build_client_glob_print, NULL);
+ if (st != KIT_OK) {
+ driver_errf(BUILD_TOOL, "glob failed: %s", build_status_name(st));
+ goto out_client;
+ }
+ rc = 0;
+ } else if (driver_streq(verb, "need")) {
+ BuildCli cli;
+ KitBuildRequest req;
+ KitBuildResult result;
+ rc = build_client_need_parse(&cli, argc, argv);
+ if (rc != 0) goto out_client;
+ memset(&req, 0, sizeof req);
+ req.target = kit_slice_cstr(cli.target);
+ req.config = cli.config;
+ req.nconfig = cli.nconfig;
+ req.argv = cli.args;
+ req.argc = cli.nargs;
+ st = kit_build_client_need(client, &req, &result);
+ if (st != KIT_OK) {
+ driver_errf(BUILD_TOOL, "need failed: %s", build_status_name(st));
+ rc = 1;
+ goto out_client;
+ }
+ driver_printf("%s\n", result.path);
+ rc = 0;
+ } else {
+ driver_errf(BUILD_TOOL, "unknown client command: %s", verb);
+ rc = 2;
+ }
+
+out_client:
+ kit_build_client_close(client);
+out_host:
+ driver_build_host_fini(&bh);
+out_env:
+ driver_env_fini(&env);
+ return rc;
+}
+
+static int build_parse_args(BuildCli* cli, int argc, char** argv) {
+ int i;
+ memset(cli, 0, sizeof *cli);
+ cli->root = ".";
+ cli->def = BUILD_DEFAULT_DEF;
+ for (i = 1; i < argc; ++i) {
+ const char* a = argv[i];
+ if (driver_streq(a, "--store") && i + 1 < argc) {
+ cli->store = argv[++i];
+ } else if (driver_streq(a, "--root") && i + 1 < argc) {
+ cli->root = argv[++i];
+ } else if (driver_streq(a, "--def") && i + 1 < argc) {
+ cli->def = argv[++i];
+ } else if (driver_streq(a, "--config") && i + 1 < argc) {
+ if (build_parse_config(cli, argv[++i]) != 0) {
+ driver_errf(BUILD_TOOL, "bad --config, expected K=V");
+ return 2;
+ }
+ } else if (driver_streq(a, "--arg") && i + 1 < argc) {
+ if (cli->nargs >= BUILD_MAX_ARGS) {
+ driver_errf(BUILD_TOOL, "too many --arg values");
+ return 2;
+ }
+ cli->args[cli->nargs++] = kit_slice_cstr(argv[++i]);
+ } else if (driver_streq(a, "--verify")) {
+ cli->verify = 1;
+ } else if (a[0] == '-') {
+ driver_errf(BUILD_TOOL, "unexpected option: %s", a);
+ return 2;
+ } else if (!cli->target) {
+ cli->target = a;
+ } else {
+ driver_errf(BUILD_TOOL, "unexpected argument: %s", a);
+ return 2;
+ }
+ }
+ if (!cli->target) {
+ driver_errf(BUILD_TOOL, "missing target");
+ return 2;
+ }
+ return 0;
+}
+
+static int build_verify_tree(DriverEnv* env, const char* store,
+ const uint8_t tree[KIT_BUILD_HASH_LEN]) {
+ KitContext ctx = driver_env_to_context(env);
+ KitCasHost cas_host = driver_cas_host(env);
+ KitCas* cas = NULL;
+ char cas_root[KIT_BUILD_PATH_MAX];
+ KitStatus st;
+ if (build_join_path(cas_root, sizeof cas_root, store, "cas") != 0) {
+ driver_errf(BUILD_TOOL, "store path is too long");
+ return 1;
+ }
+ st = kit_cas_open(&ctx, &cas_host, cas_root, &cas);
+ if (st == KIT_OK) st = kit_cas_verify_tree(cas, tree);
+ if (cas) kit_cas_close(cas);
+ if (st != KIT_OK) {
+ driver_errf(BUILD_TOOL, "output tree verification failed: %s",
+ build_status_name(st));
+ return 1;
+ }
+ return 0;
+}
+
+int driver_build(int argc, char** argv) {
+ DriverEnv env;
+ DriverBuildHost bh;
+ BuildCli cli;
+ KitContext ctx;
+ KitBuildOptions opts;
+ KitBuildRequest req;
+ KitBuildResult result;
+ KitBuildCoordinator* coord = NULL;
+ KitStatus st;
+ char* default_store = NULL;
+ size_t default_store_size = 0;
+ char* abs_store = NULL;
+ char* abs_root = NULL;
+ size_t abs_store_size = 0;
+ size_t abs_root_size = 0;
+ char hex[2u * KIT_BUILD_HASH_LEN + 1u];
+ int rc;
+
+ if (driver_argv_wants_help(argc, argv, 1)) {
+ driver_help_build();
+ return 0;
+ }
+ if (argc >= 2 && driver_streq(argv[1], "--client"))
+ return build_client_mode(argc, argv);
+
+ rc = build_parse_args(&cli, argc, argv);
+ if (rc != 0) return rc;
+
+ driver_env_init(&env);
+ if (!cli.store) {
+ default_store = driver_path_join(&env, env.cache_dir, "build",
+ &default_store_size);
+ if (!default_store) {
+ driver_errf(BUILD_TOOL, "out of memory");
+ driver_env_fini(&env);
+ return 1;
+ }
+ cli.store = default_store;
+ }
+
+ ctx = driver_env_to_context(&env);
+ if (driver_build_host_init(&bh, &env) != 0) {
+ driver_errf(BUILD_TOOL, "hosted build adapter is unavailable");
+ rc = 1;
+ goto out_env;
+ }
+ abs_store = driver_build_host_abs_path(&env, cli.store, &abs_store_size);
+ abs_root = driver_build_host_abs_path(&env, cli.root, &abs_root_size);
+ if (!abs_store || !abs_root) {
+ driver_errf(BUILD_TOOL, "failed to resolve build paths");
+ rc = 1;
+ goto out_host_only;
+ }
+
+ memset(&opts, 0, sizeof opts);
+ opts.workspace_root = kit_slice_cstr(abs_root);
+ opts.build_def_path = kit_slice_cstr(cli.def);
+ opts.jobs = 1;
+ opts.verify = cli.verify;
+
+ st = kit_build_coordinator_open(&ctx, &bh.host, kit_slice_cstr(abs_store),
+ &opts, &coord);
+ if (st != KIT_OK) {
+ driver_errf(BUILD_TOOL, "failed to open coordinator: %s",
+ build_status_name(st));
+ rc = st == KIT_INVALID || st == KIT_MALFORMED ? 2 : 1;
+ goto out;
+ }
+
+ memset(&req, 0, sizeof req);
+ req.target = kit_slice_cstr(cli.target);
+ req.config = cli.config;
+ req.nconfig = cli.nconfig;
+ req.argv = cli.args;
+ req.argc = cli.nargs;
+ memset(&result, 0, sizeof result);
+ st = kit_build(coord, &req, &result);
+ if (st != KIT_OK) {
+ driver_errf(BUILD_TOOL, "build failed: %s", build_status_name(st));
+ rc = 1;
+ goto out;
+ }
+
+ if (cli.verify && build_verify_tree(&env, abs_store, result.output_tree) !=
+ 0) {
+ rc = 1;
+ goto out;
+ }
+
+ kit_hex_encode(hex, result.output_tree, KIT_BUILD_HASH_LEN);
+ driver_printf("%s %s\n", hex, result.path);
+ rc = 0;
+
+out:
+ if (coord) kit_build_coordinator_close(coord);
+out_host_only:
+ if (abs_store) driver_free(&env, abs_store, abs_store_size);
+ if (abs_root) driver_free(&env, abs_root, abs_root_size);
+ driver_build_host_fini(&bh);
+out_env:
+ if (default_store) driver_free(&env, default_store, default_store_size);
+ driver_env_fini(&env);
+ return rc;
+}
diff --git a/driver/driver.h b/driver/driver.h
@@ -25,6 +25,7 @@ int driver_check(int argc, char** argv);
int driver_build_exe(int argc, char** argv);
int driver_build_lib(int argc, char** argv);
int driver_build_obj(int argc, char** argv);
+int driver_build(int argc, char** argv);
int driver_build_exe_ex(int argc, char** argv, const KitDriverExtension*);
int driver_build_lib_ex(int argc, char** argv, const KitDriverExtension*);
int driver_build_obj_ex(int argc, char** argv, const KitDriverExtension*);
@@ -75,6 +76,7 @@ void driver_help_check(void);
void driver_help_build_exe(void);
void driver_help_build_lib(void);
void driver_help_build_obj(void);
+void driver_help_build(void);
void driver_help_install(void);
void driver_help_cpp(void);
void driver_help_as(void);
diff --git a/driver/env/build_host_posix.c b/driver/env/build_host_posix.c
@@ -0,0 +1,466 @@
+#include "../env_build.h"
+
+#include "dist_host.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define DRIVER_BUILD_FRAME_MAX 65536u
+
+struct KitBuildProc {
+ pid_t pid;
+};
+
+struct KitBuildListener {
+ int fd;
+ char path[sizeof(((struct sockaddr_un*)0)->sun_path)];
+};
+
+struct KitBuildConn {
+ int fd;
+};
+
+static int db_path_copy(char* out, size_t cap, KitSlice s) {
+ if (!out || cap == 0u || !s.s || s.len + 1u > cap) return 1;
+ memcpy(out, s.s, s.len);
+ out[s.len] = '\0';
+ return 0;
+}
+
+static int db_remove_tree(const char* path) {
+ struct stat st;
+ if (lstat(path, &st) != 0) return errno == ENOENT ? 0 : 1;
+ if (S_ISDIR(st.st_mode)) {
+ DIR* d = opendir(path);
+ struct dirent* ent;
+ if (!d) return 1;
+ while ((ent = readdir(d)) != NULL) {
+ char child[KIT_BUILD_PATH_MAX];
+ const char* name = ent->d_name;
+ size_t np = strlen(path), nn = strlen(name);
+ if ((name[0] == '.' && name[1] == '\0') ||
+ (name[0] == '.' && name[1] == '.' && name[2] == '\0'))
+ continue;
+ if (np + 1u + nn + 1u > sizeof child) {
+ closedir(d);
+ return 1;
+ }
+ memcpy(child, path, np);
+ child[np] = '/';
+ memcpy(child + np + 1u, name, nn + 1u);
+ if (db_remove_tree(child) != 0) {
+ closedir(d);
+ return 1;
+ }
+ }
+ closedir(d);
+ return rmdir(path) == 0 ? 0 : 1;
+ }
+ return unlink(path) == 0 ? 0 : 1;
+}
+
+static int db_store_rename(void* user, KitSlice from, KitSlice to) {
+ char f[KIT_BUILD_PATH_MAX], t[KIT_BUILD_PATH_MAX];
+ (void)user;
+ if (db_path_copy(f, sizeof f, from) || db_path_copy(t, sizeof t, to))
+ return 1;
+ return rename(f, t) == 0 ? 0 : 1;
+}
+
+static int db_store_remove(void* user, KitSlice path, int recursive) {
+ char p[KIT_BUILD_PATH_MAX];
+ (void)user;
+ if (db_path_copy(p, sizeof p, path)) return 1;
+ if (recursive) return db_remove_tree(p);
+ return unlink(p) == 0 || rmdir(p) == 0 || errno == ENOENT ? 0 : 1;
+}
+
+static int db_store_make_temp_dir(void* user, KitSlice parent, char* out,
+ size_t cap) {
+ char p[KIT_BUILD_PATH_MAX];
+ size_t n;
+ (void)user;
+ if (!out || cap == 0u || db_path_copy(p, sizeof p, parent)) return 1;
+ n = strlen(p);
+ if (n + sizeof("/tmp.XXXXXX") > cap) return 1;
+ memcpy(out, p, n);
+ out[n++] = '/';
+ memcpy(out + n, "tmp.XXXXXX", sizeof("tmp.XXXXXX"));
+ return mkdtemp(out) ? 0 : 1;
+}
+
+static int db_store_sync_path(void* user, KitSlice path) {
+ char p[KIT_BUILD_PATH_MAX];
+ int fd;
+ int rc = 0;
+ (void)user;
+ if (db_path_copy(p, sizeof p, path)) return 1;
+ fd = open(p, O_RDONLY);
+ if (fd < 0) return 0;
+ if (fsync(fd) != 0) rc = 1;
+ close(fd);
+ return rc;
+}
+
+static int db_store_list_dir(void* user, KitSlice path, KitBuildDirFn cb,
+ void* cb_user) {
+ char p[KIT_BUILD_PATH_MAX];
+ DIR* d;
+ struct dirent* ent;
+ (void)user;
+ if (!cb || db_path_copy(p, sizeof p, path)) return 1;
+ d = opendir(p);
+ if (!d) return 1;
+ while ((ent = readdir(d)) != NULL) {
+ const char* name = ent->d_name;
+ if ((name[0] == '.' && name[1] == '\0') ||
+ (name[0] == '.' && name[1] == '.' && name[2] == '\0'))
+ continue;
+ if (cb(cb_user, kit_slice_cstr(name))) {
+ closedir(d);
+ return 1;
+ }
+ }
+ closedir(d);
+ return 0;
+}
+
+static char* db_slice_dup(DriverEnv* env, KitSlice s) {
+ char* out = (char*)driver_alloc(env, s.len + 1u);
+ if (!out) return NULL;
+ if (s.len) memcpy(out, s.s, s.len);
+ out[s.len] = '\0';
+ return out;
+}
+
+static char* db_cwd_join(DriverEnv* env, KitSlice path) {
+ char cwd[KIT_BUILD_PATH_MAX];
+ char* out;
+ size_t nc, np;
+ if (!path.s || path.len == 0u) return NULL;
+ if (path.s[0] == '/') return db_slice_dup(env, path);
+ if (!getcwd(cwd, sizeof cwd)) return NULL;
+ nc = strlen(cwd);
+ np = path.len;
+ out = (char*)driver_alloc(env, nc + 1u + np + 1u);
+ if (!out) return NULL;
+ memcpy(out, cwd, nc);
+ out[nc] = '/';
+ memcpy(out + nc + 1u, path.s, np);
+ out[nc + 1u + np] = '\0';
+ return out;
+}
+
+static void db_free_strv(DriverEnv* env, char** v, size_t n) {
+ size_t i;
+ if (!v) return;
+ for (i = 0; i < n; ++i) {
+ if (v[i]) driver_free(env, v[i], strlen(v[i]) + 1u);
+ }
+ driver_free(env, v, (n + 1u) * sizeof *v);
+}
+
+static int db_exec_spawn(void* user, const KitSlice* argv, size_t argc,
+ const KitBuildKV* envv, size_t nenv, KitSlice cwd,
+ KitBuildProc** out) {
+ DriverBuildHost* bh = (DriverBuildHost*)user;
+ char** av = NULL;
+ char** ev = NULL;
+ char* cwd_s = NULL;
+ KitBuildProc* proc = NULL;
+ pid_t pid;
+ size_t i;
+ if (!bh || !bh->env || !argv || argc == 0u || !out) return 1;
+ *out = NULL;
+ av = (char**)driver_alloc(bh->env, (argc + 1u) * sizeof *av);
+ ev = (char**)driver_alloc(bh->env, (nenv + 1u) * sizeof *ev);
+ if (!av || !ev) goto err;
+ memset(av, 0, (argc + 1u) * sizeof *av);
+ memset(ev, 0, (nenv + 1u) * sizeof *ev);
+ for (i = 0; i < argc; ++i) {
+ av[i] = i == 0u ? db_cwd_join(bh->env, argv[i])
+ : db_slice_dup(bh->env, argv[i]);
+ if (!av[i]) goto err;
+ }
+ for (i = 0; i < nenv; ++i) {
+ size_t nk = envv[i].key.len, nv = envv[i].value.len;
+ ev[i] = (char*)driver_alloc(bh->env, nk + 1u + nv + 1u);
+ if (!ev[i]) goto err;
+ memcpy(ev[i], envv[i].key.s, nk);
+ ev[i][nk] = '=';
+ memcpy(ev[i] + nk + 1u, envv[i].value.s, nv);
+ ev[i][nk + 1u + nv] = '\0';
+ }
+ cwd_s = db_slice_dup(bh->env, cwd);
+ proc = (KitBuildProc*)driver_alloc_zeroed(bh->env, sizeof *proc);
+ if (!cwd_s || !proc) goto err;
+ pid = fork();
+ if (pid < 0) goto err;
+ if (pid == 0) {
+ if (chdir(cwd_s) != 0) _exit(127);
+ execve(av[0], av, ev);
+ _exit(127);
+ }
+ proc->pid = pid;
+ *out = proc;
+ db_free_strv(bh->env, av, argc);
+ db_free_strv(bh->env, ev, nenv);
+ driver_free(bh->env, cwd_s, strlen(cwd_s) + 1u);
+ return 0;
+
+err:
+ db_free_strv(bh->env, av, argc);
+ db_free_strv(bh->env, ev, nenv);
+ if (cwd_s) driver_free(bh->env, cwd_s, strlen(cwd_s) + 1u);
+ if (proc) driver_free(bh->env, proc, sizeof *proc);
+ return 1;
+}
+
+static int db_exec_wait(void* user, KitBuildProc* proc, int* exit_code) {
+ DriverBuildHost* bh = (DriverBuildHost*)user;
+ int st = 0;
+ if (!bh || !proc || !exit_code) return 1;
+ while (waitpid(proc->pid, &st, 0) < 0) {
+ if (errno != EINTR) return 1;
+ }
+ if (WIFEXITED(st)) {
+ *exit_code = WEXITSTATUS(st);
+ } else {
+ *exit_code = 128;
+ }
+ driver_free(bh->env, proc, sizeof *proc);
+ return 0;
+}
+
+static void db_exec_kill(void* user, KitBuildProc* proc) {
+ (void)user;
+ if (proc) kill(proc->pid, SIGTERM);
+}
+
+static int db_full_write(int fd, const uint8_t* p, size_t n) {
+ size_t off = 0;
+ while (off < n) {
+ ssize_t w = write(fd, p + off, n - off);
+ if (w > 0) {
+ off += (size_t)w;
+ } else if (w < 0 && errno == EINTR) {
+ continue;
+ } else {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int db_full_read(int fd, uint8_t* p, size_t n) {
+ size_t off = 0;
+ while (off < n) {
+ ssize_t r = read(fd, p + off, n - off);
+ if (r > 0) {
+ off += (size_t)r;
+ } else if (r < 0 && errno == EINTR) {
+ continue;
+ } else {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int db_transport_listen(void* user, char* name_out, size_t cap,
+ KitBuildListener** out) {
+ DriverBuildHost* bh = (DriverBuildHost*)user;
+ KitBuildListener* l = NULL;
+ struct sockaddr_un sa;
+ int fd = -1;
+ if (!bh || !bh->env || !name_out || cap == 0u || !out) return 1;
+ *out = NULL;
+ l = (KitBuildListener*)driver_alloc_zeroed(bh->env, sizeof *l);
+ if (!l) return 1;
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) goto err;
+ snprintf(l->path, sizeof l->path, "/tmp/kit-build-%ld-%llu.sock",
+ (long)getpid(), (unsigned long long)bh->next_endpoint++);
+ memset(&sa, 0, sizeof sa);
+ sa.sun_family = AF_UNIX;
+ snprintf(sa.sun_path, sizeof sa.sun_path, "%s", l->path);
+ (void)unlink(l->path);
+ if (bind(fd, (struct sockaddr*)&sa, sizeof sa) != 0 ||
+ listen(fd, 1) != 0)
+ goto err;
+ if (strlen(l->path) + 1u > cap) goto err;
+ memcpy(name_out, l->path, strlen(l->path) + 1u);
+ l->fd = fd;
+ *out = l;
+ return 0;
+
+err:
+ if (fd >= 0) close(fd);
+ if (l) {
+ if (l->path[0]) unlink(l->path);
+ driver_free(bh->env, l, sizeof *l);
+ }
+ return 1;
+}
+
+static int db_transport_accept(void* user, KitBuildListener* lst,
+ KitBuildConn** out) {
+ DriverBuildHost* bh = (DriverBuildHost*)user;
+ KitBuildConn* c;
+ int fd;
+ if (!bh || !lst || !out) return 1;
+ *out = NULL;
+ do {
+ fd = accept(lst->fd, NULL, NULL);
+ } while (fd < 0 && errno == EINTR);
+ if (fd < 0) return 1;
+ c = (KitBuildConn*)driver_alloc_zeroed(bh->env, sizeof *c);
+ if (!c) {
+ close(fd);
+ return 1;
+ }
+ c->fd = fd;
+ *out = c;
+ return 0;
+}
+
+static void db_transport_close_listener(void* user, KitBuildListener* lst) {
+ DriverBuildHost* bh = (DriverBuildHost*)user;
+ if (!bh || !lst) return;
+ if (lst->fd >= 0) close(lst->fd);
+ if (lst->path[0]) unlink(lst->path);
+ driver_free(bh->env, lst, sizeof *lst);
+}
+
+static int db_transport_dial(void* user, KitSlice endpoint, KitBuildConn** out) {
+ DriverBuildHost* bh = (DriverBuildHost*)user;
+ KitBuildConn* c = NULL;
+ char path[sizeof(((struct sockaddr_un*)0)->sun_path)];
+ struct sockaddr_un sa;
+ int fd = -1;
+ if (!bh || !bh->env || !out || db_path_copy(path, sizeof path, endpoint))
+ return 1;
+ *out = NULL;
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) return 1;
+ memset(&sa, 0, sizeof sa);
+ sa.sun_family = AF_UNIX;
+ snprintf(sa.sun_path, sizeof sa.sun_path, "%s", path);
+ if (connect(fd, (struct sockaddr*)&sa, sizeof sa) != 0) {
+ close(fd);
+ return 1;
+ }
+ c = (KitBuildConn*)driver_alloc_zeroed(bh->env, sizeof *c);
+ if (!c) {
+ close(fd);
+ return 1;
+ }
+ c->fd = fd;
+ *out = c;
+ return 0;
+}
+
+static int db_transport_read(void* user, KitBuildConn* conn, uint8_t* buf,
+ size_t cap, size_t* n) {
+ uint8_t hdr[4];
+ uint32_t len;
+ (void)user;
+ if (!conn || !buf || !n) return 1;
+ if (db_full_read(conn->fd, hdr, sizeof hdr) != 0) return 1;
+ len = (uint32_t)hdr[0] | ((uint32_t)hdr[1] << 8) |
+ ((uint32_t)hdr[2] << 16) | ((uint32_t)hdr[3] << 24);
+ if (len > cap || len > DRIVER_BUILD_FRAME_MAX) return 1;
+ if (db_full_read(conn->fd, buf, len) != 0) return 1;
+ *n = len;
+ return 0;
+}
+
+static int db_transport_write(void* user, KitBuildConn* conn,
+ const uint8_t* buf, size_t n) {
+ uint8_t hdr[4];
+ (void)user;
+ if (!conn || (!buf && n) || n > DRIVER_BUILD_FRAME_MAX) return 1;
+ hdr[0] = (uint8_t)(n & 0xffu);
+ hdr[1] = (uint8_t)((n >> 8) & 0xffu);
+ hdr[2] = (uint8_t)((n >> 16) & 0xffu);
+ hdr[3] = (uint8_t)((n >> 24) & 0xffu);
+ return db_full_write(conn->fd, hdr, sizeof hdr) ||
+ db_full_write(conn->fd, buf, n);
+}
+
+static void db_transport_close(void* user, KitBuildConn* conn) {
+ DriverBuildHost* bh = (DriverBuildHost*)user;
+ if (!bh || !conn) return;
+ if (conn->fd >= 0) close(conn->fd);
+ driver_free(bh->env, conn, sizeof *conn);
+}
+
+int driver_build_host_init(DriverBuildHost* out, DriverEnv* env) {
+ if (!out || !env) return 1;
+ memset(out, 0, sizeof *out);
+ out->env = env;
+ out->cas_host = driver_cas_host(env);
+ out->store_io.rename = db_store_rename;
+ out->store_io.remove = db_store_remove;
+ out->store_io.make_temp_dir = db_store_make_temp_dir;
+ out->store_io.sync_path = db_store_sync_path;
+ out->store_io.list_dir = db_store_list_dir;
+ out->store_io.user = out;
+ out->exec.spawn = db_exec_spawn;
+ out->exec.wait = db_exec_wait;
+ out->exec.kill = db_exec_kill;
+ out->exec.user = out;
+ out->transport.listen = db_transport_listen;
+ out->transport.accept = db_transport_accept;
+ out->transport.close_listener = db_transport_close_listener;
+ out->transport.dial = db_transport_dial;
+ out->transport.read_frame = db_transport_read;
+ out->transport.write_frame = db_transport_write;
+ out->transport.close = db_transport_close;
+ out->transport.user = out;
+ out->host.cas_host = &out->cas_host;
+ out->host.store_io = &out->store_io;
+ out->host.exec = &out->exec;
+ out->host.transport = &out->transport;
+ out->next_endpoint = 1;
+ return 0;
+}
+
+void driver_build_host_fini(DriverBuildHost* host) { (void)host; }
+
+char* driver_build_host_abs_path(DriverEnv* env, const char* path,
+ size_t* out_size) {
+ char cwd[KIT_BUILD_PATH_MAX];
+ char* out;
+ size_t nc, np, total;
+ if (!env || !path) return NULL;
+ if (path[0] == '/') {
+ np = strlen(path);
+ out = (char*)driver_alloc(env, np + 1u);
+ if (!out) return NULL;
+ memcpy(out, path, np + 1u);
+ if (out_size) *out_size = np + 1u;
+ return out;
+ }
+ if (!getcwd(cwd, sizeof cwd)) return NULL;
+ nc = strlen(cwd);
+ np = strlen(path);
+ total = nc + 1u + np + 1u;
+ out = (char*)driver_alloc(env, total);
+ if (!out) return NULL;
+ memcpy(out, cwd, nc);
+ out[nc] = '/';
+ memcpy(out + nc + 1u, path, np + 1u);
+ if (out_size) *out_size = total;
+ return out;
+}
diff --git a/driver/env/build_host_stub.c b/driver/env/build_host_stub.c
@@ -0,0 +1,19 @@
+#include "../env_build.h"
+
+#include <string.h>
+
+int driver_build_host_init(DriverBuildHost* out, DriverEnv* env) {
+ if (out) memset(out, 0, sizeof *out);
+ (void)env;
+ return 1;
+}
+
+void driver_build_host_fini(DriverBuildHost* host) { (void)host; }
+
+char* driver_build_host_abs_path(DriverEnv* env, const char* path,
+ size_t* out_size) {
+ (void)env;
+ (void)path;
+ if (out_size) *out_size = 0;
+ return NULL;
+}
diff --git a/driver/env_build.h b/driver/env_build.h
@@ -0,0 +1,23 @@
+#ifndef KIT_DRIVER_ENV_BUILD_H
+#define KIT_DRIVER_ENV_BUILD_H
+
+#include <kit/build_coord.h>
+
+#include "env.h"
+
+typedef struct DriverBuildHost {
+ DriverEnv* env;
+ KitCasHost cas_host;
+ KitBuildStoreIo store_io;
+ KitBuildExec exec;
+ KitBuildTransport transport;
+ KitBuildHost host;
+ uint64_t next_endpoint;
+} DriverBuildHost;
+
+int driver_build_host_init(DriverBuildHost* out, DriverEnv* env);
+void driver_build_host_fini(DriverBuildHost* host);
+char* driver_build_host_abs_path(DriverEnv* env, const char* path,
+ size_t* out_size);
+
+#endif
diff --git a/driver/main.c b/driver/main.c
@@ -50,6 +50,11 @@ static const DriverToolDesc driver_tools[] = {
"Compile sources to an object / asm / C / IR, or check (replaces compile)",
DRIVER_GROUP_TOOLCHAIN},
#endif
+#if KIT_TOOL_BUILD_ENABLED
+ {"build", driver_build, NULL, driver_help_build,
+ "Resolve a target with the content-addressed build coordinator",
+ DRIVER_GROUP_OTHER},
+#endif
#if KIT_TOOL_INSTALL_ENABLED
{"install", driver_install, NULL, driver_help_install,
"Symlink the kit tools into a dir for drop-in toolchain use",
diff --git a/include/kit/config.h b/include/kit/config.h
@@ -81,6 +81,11 @@
#define KIT_CAS_ENABLED 1
#define KIT_PKG_ENABLED 1
+/* Content-addressed build coordinator (<kit/build_coord.h> + `kit build`).
+ * This layers mutable target trace indexes and recipe orchestration on top of
+ * CAS; disabling it keeps the lower-level CAS/package tools available. */
+#define KIT_BUILD_ENABLED 1
+
/* Standalone general-purpose compression (kit/compress.h + the `compress`
* tool): the gzip and LZ4-frame codecs surfaced independently of packaging.
* Reuses the deflate + lz4 codec sources that PKG also pulls in, and adds the
@@ -116,6 +121,7 @@
#define KIT_TOOL_BUILD_EXE_ENABLED 1
#define KIT_TOOL_BUILD_LIB_ENABLED 1
#define KIT_TOOL_BUILD_OBJ_ENABLED 1
+#define KIT_TOOL_BUILD_ENABLED 1
#define KIT_TOOL_INSTALL_ENABLED 1
#define KIT_TOOL_CPP_ENABLED 1
#define KIT_TOOL_AS_ENABLED 1
diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk
@@ -32,6 +32,7 @@ DRIVER_TOOL_SRCS = \
$(call tool-cmd,BUILD_EXE,build) \
$(call tool-cmd,BUILD_LIB,build) \
$(call tool-cmd,BUILD_OBJ,build) \
+ $(call tool-cmd,BUILD,build_coord) \
$(call tool-cmd,INSTALL,install) \
$(call tool-cmd,CPP,cpp) \
$(call tool-cmd,AS,as) \
@@ -84,7 +85,12 @@ DRIVER_SRCS += $(call need-any,CC CHECK AR RANLIB STRIP DBG RUN BUILD_EXE BUILD_
DRIVER_SRCS += $(call need-any,STRIP OBJCOPY,driver/lib/objedit.c)
DRIVER_SRCS += $(call need-any,RUN,driver/lib/wasm_run.c)
DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/link_inputs.c)
-DRIVER_SRCS += $(call need-any,CAS PKG UPDATE,driver/lib/dist_host.c)
+DRIVER_SRCS += $(call need-any,CAS PKG BUILD UPDATE,driver/lib/dist_host.c)
+ifeq ($(HOST_OS),windows)
+DRIVER_SRCS += $(call need-any,BUILD,driver/env/build_host_stub.c)
+else
+DRIVER_SRCS += $(call need-any,BUILD,driver/env/build_host_posix.c)
+endif
DRIVER_SRCS += $(call need-any,INSTALL UPDATE,driver/lib/install_links.c)
DRIVER_SRCS += $(call need-any,UPDATE,driver/release_key.c)
DRIVER_SRCS += $(call need-any,ADDR2LINE SYMBOLIZE,driver/lib/dwarfsym.c)
diff --git a/mk/lib_srcs.mk b/mk/lib_srcs.mk
@@ -194,7 +194,7 @@ endif
ifeq ($(KIT_LINK_ENABLED),1)
LIB_SRCS += $(LIB_SRCS_API_LINK) $(LIB_SRCS_API_BUILD) $(LIB_SRCS_LINK)
endif
-ifeq ($(KIT_CAS_ENABLED),1)
+ifeq ($(KIT_BUILD_ENABLED),1)
LIB_SRCS += $(LIB_SRCS_BUILD_COORD)
endif
ifeq ($(KIT_IMAGE_ENABLED),1)
@@ -210,8 +210,10 @@ ifeq ($(KIT_GRAM_ENABLED),1)
LIB_SRCS += $(LIB_SRCS_GRAM)
endif
ifeq ($(KIT_CAS_ENABLED),1)
-LIB_SRCS += $(LIB_SRCS_API_CAS) $(LIB_SRCS_API_BUILD_COORD) \
- $(LIB_SRCS_DIST_CAS) $(LIB_SRCS_VENDOR_CAS)
+LIB_SRCS += $(LIB_SRCS_API_CAS) $(LIB_SRCS_DIST_CAS) $(LIB_SRCS_VENDOR_CAS)
+endif
+ifeq ($(KIT_BUILD_ENABLED),1)
+LIB_SRCS += $(LIB_SRCS_API_BUILD_COORD)
endif
# Shared compression codecs (deflate + lz4 block): pulled in once if either the
# compress API or the pkg layer needs them.
diff --git a/src/api/build_coord.c b/src/api/build_coord.c
@@ -3,6 +3,7 @@
#include "build/bundle.h"
#include "build/cfg.h"
#include "build/coord.h"
+#include "build/protocol.h"
#include "build/resolve.h"
#include <string.h>
@@ -67,70 +68,236 @@ struct KitBuildClient {
const KitContext* ctx;
const KitBuildTransport* transport;
KitBuildConn* conn;
+ uint8_t frame[BUILD_FRAME_MAX];
};
+static KitStatus build_client_error_status(const BuildResp* resp) {
+ if (!resp || resp->status != BUILD_RESP_ERROR) return KIT_MALFORMED;
+ switch ((KitStatus)resp->error_status) {
+ case KIT_OK:
+ case KIT_ERR:
+ case KIT_NOMEM:
+ case KIT_INVALID:
+ case KIT_UNSUPPORTED:
+ case KIT_MALFORMED:
+ case KIT_IO:
+ case KIT_NOT_FOUND:
+ case KIT_AMBIGUOUS:
+ return (KitStatus)resp->error_status;
+ default:
+ return KIT_ERR;
+ }
+}
+
+static KitStatus build_client_rpc(KitBuildClient* c, const BuildReq* req,
+ BuildResp* resp,
+ BuildProtoGlobFn glob_cb,
+ void* glob_user) {
+ size_t n = 0;
+ if (!c || !c->transport || !c->conn || !req || !resp) return KIT_INVALID;
+ if (build_proto_encode_req(req, c->frame, sizeof c->frame, &n) != BUILD_OK)
+ return KIT_INVALID;
+ if (c->transport->write_frame(c->transport->user, c->conn, c->frame, n) != 0)
+ return KIT_IO;
+ if (c->transport->read_frame(c->transport->user, c->conn, c->frame,
+ sizeof c->frame, &n) != 0)
+ return KIT_IO;
+ if (build_proto_decode_resp(c->frame, n, req->cmd, resp, glob_cb,
+ glob_user) != BUILD_OK)
+ return KIT_MALFORMED;
+ if (resp->status == BUILD_RESP_ERROR) return build_client_error_status(resp);
+ return KIT_OK;
+}
+
+static KitStatus build_client_need_result(KitBuildClient* c, const BuildReq* req,
+ KitBuildResult* out) {
+ BuildResp resp;
+ KitStatus st;
+ if (!out) return KIT_INVALID;
+ memset(out, 0, sizeof *out);
+ st = build_client_rpc(c, req, &resp, NULL, NULL);
+ if (st != KIT_OK) return st;
+ if (resp.status != BUILD_RESP_OK || resp.text.len >= sizeof out->path)
+ return KIT_MALFORMED;
+ memcpy(out->output_tree, resp.id, KIT_BUILD_HASH_LEN);
+ if (resp.text.len) memcpy(out->path, resp.text.s, resp.text.len);
+ out->path[resp.text.len] = '\0';
+ return KIT_OK;
+}
+
+static void build_client_req_from_build_req(BuildReq* req, uint8_t cmd,
+ const KitBuildRequest* in) {
+ memset(req, 0, sizeof *req);
+ req->cmd = cmd;
+ req->arg = in->target;
+ req->overrides = in->config;
+ req->noverrides = in->nconfig;
+ req->argv = in->argv;
+ req->argc = in->argc;
+}
+
KitStatus kit_build_client_open(const KitContext* ctx,
const KitBuildTransport* transport,
KitBuildClient** out) {
- (void)ctx;
- (void)transport;
- if (out) *out = NULL;
- return KIT_UNSUPPORTED;
+ const char* endpoint;
+ KitBuildClient* c;
+ KitBuildConn* conn = NULL;
+ if (!out) return KIT_INVALID;
+ *out = NULL;
+ if (!ctx || !ctx->heap || !transport || !transport->dial ||
+ !transport->read_frame || !transport->write_frame || !transport->close)
+ return KIT_INVALID;
+ endpoint = kit_debug_getenv(KIT_BUILD_ENV_SOCK);
+ if (!endpoint || !endpoint[0]) return KIT_NOT_FOUND;
+ if (transport->dial(transport->user, kit_slice_cstr(endpoint), &conn) != 0 ||
+ !conn)
+ return KIT_IO;
+ c = (KitBuildClient*)ctx->heap->alloc(ctx->heap, sizeof *c,
+ _Alignof(KitBuildClient));
+ if (!c) {
+ transport->close(transport->user, conn);
+ return KIT_NOMEM;
+ }
+ memset(c, 0, sizeof *c);
+ c->ctx = ctx;
+ c->transport = transport;
+ c->conn = conn;
+ *out = c;
+ return KIT_OK;
}
-void kit_build_client_close(KitBuildClient* c) { (void)c; }
+void kit_build_client_close(KitBuildClient* c) {
+ KitHeap* heap;
+ if (!c) return;
+ if (c->transport && c->transport->close && c->conn)
+ c->transport->close(c->transport->user, c->conn);
+ heap = c->ctx ? c->ctx->heap : NULL;
+ if (heap) heap->free(heap, c, sizeof *c);
+}
KitStatus kit_build_client_config_get(KitBuildClient* c, KitSlice key,
KitSlice* value, int* present) {
- (void)c;
- (void)key;
+ BuildReq req;
+ BuildResp resp;
+ KitStatus st;
if (value) *value = KIT_SLICE_NULL;
if (present) *present = 0;
- return KIT_UNSUPPORTED;
+ if (!c || !value || !present) return KIT_INVALID;
+ memset(&req, 0, sizeof req);
+ req.cmd = BUILD_CMD_CONFIG_GET;
+ req.arg = key;
+ st = build_client_rpc(c, &req, &resp, NULL, NULL);
+ if (st != KIT_OK) return st;
+ if (resp.status == BUILD_RESP_UNSET) return KIT_OK;
+ if (resp.status != BUILD_RESP_OK) return KIT_MALFORMED;
+ *value = resp.text;
+ *present = 1;
+ return KIT_OK;
}
KitStatus kit_build_client_source(KitBuildClient* c, KitSlice path,
uint8_t blob[KIT_BUILD_HASH_LEN],
KitSlice* realpath) {
- (void)c;
- (void)path;
+ BuildReq req;
+ BuildResp resp;
+ KitStatus st;
if (blob) memset(blob, 0, KIT_BUILD_HASH_LEN);
if (realpath) *realpath = KIT_SLICE_NULL;
- return KIT_UNSUPPORTED;
+ if (!c || !blob || !realpath) return KIT_INVALID;
+ memset(&req, 0, sizeof req);
+ req.cmd = BUILD_CMD_SOURCE;
+ req.arg = path;
+ st = build_client_rpc(c, &req, &resp, NULL, NULL);
+ if (st != KIT_OK) return st;
+ if (resp.status == BUILD_RESP_ABSENT) return KIT_NOT_FOUND;
+ if (resp.status != BUILD_RESP_OK) return KIT_MALFORMED;
+ memcpy(blob, resp.id, KIT_BUILD_HASH_LEN);
+ *realpath = resp.text;
+ return KIT_OK;
+}
+
+typedef struct BuildClientGlobCb {
+ KitBuildGlobFn cb;
+ void* user;
+ int stopped;
+} BuildClientGlobCb;
+
+static int build_client_glob_cb(void* user, KitSlice path) {
+ BuildClientGlobCb* g = (BuildClientGlobCb*)user;
+ if (!g || g->stopped || !g->cb) return 0;
+ if (g->cb(g->user, path)) {
+ g->stopped = 1;
+ return 1;
+ }
+ return 0;
}
KitStatus kit_build_client_glob(KitBuildClient* c, KitSlice pattern,
KitBuildGlobFn cb, void* cb_user) {
- (void)c;
- (void)pattern;
- (void)cb;
- (void)cb_user;
- return KIT_UNSUPPORTED;
+ BuildReq req;
+ BuildResp resp;
+ BuildClientGlobCb gcb;
+ size_t n = 0;
+ KitStatus st;
+ if (!c) return KIT_INVALID;
+ memset(&req, 0, sizeof req);
+ req.cmd = BUILD_CMD_GLOB;
+ req.arg = pattern;
+ if (build_proto_encode_req(&req, c->frame, sizeof c->frame, &n) != BUILD_OK)
+ return KIT_INVALID;
+ if (c->transport->write_frame(c->transport->user, c->conn, c->frame, n) != 0)
+ return KIT_IO;
+ memset(&gcb, 0, sizeof gcb);
+ gcb.cb = cb;
+ gcb.user = cb_user;
+ for (;;) {
+ if (c->transport->read_frame(c->transport->user, c->conn, c->frame,
+ sizeof c->frame, &n) != 0)
+ return KIT_IO;
+ if (build_proto_decode_resp(c->frame, n, BUILD_CMD_GLOB, &resp,
+ build_client_glob_cb, &gcb) != BUILD_OK)
+ return KIT_MALFORMED;
+ if (resp.status == BUILD_RESP_ERROR) {
+ st = build_client_error_status(&resp);
+ return st == KIT_OK ? KIT_ERR : st;
+ }
+ if (resp.status == BUILD_RESP_GLOB_END) return KIT_OK;
+ if (resp.status != BUILD_RESP_OK) return KIT_MALFORMED;
+ }
}
KitStatus kit_build_client_need(KitBuildClient* c,
const KitBuildRequest* req,
KitBuildResult* out) {
- (void)c;
- (void)req;
- (void)out;
- return KIT_UNSUPPORTED;
+ BuildReq breq;
+ if (!c || !req) return KIT_INVALID;
+ build_client_req_from_build_req(&breq, BUILD_CMD_NEED, req);
+ return build_client_need_result(c, &breq, out);
}
KitStatus kit_build_client_need_submit(KitBuildClient* c,
const KitBuildRequest* req,
KitBuildNeedToken* out_token) {
- (void)c;
- (void)req;
+ BuildReq breq;
+ BuildResp resp;
+ KitStatus st;
if (out_token) out_token->id = 0;
- return KIT_UNSUPPORTED;
+ if (!c || !req || !out_token) return KIT_INVALID;
+ build_client_req_from_build_req(&breq, BUILD_CMD_NEED_SUBMIT, req);
+ st = build_client_rpc(c, &breq, &resp, NULL, NULL);
+ if (st != KIT_OK) return st;
+ if (resp.status != BUILD_RESP_OK) return KIT_MALFORMED;
+ out_token->id = resp.token;
+ return KIT_OK;
}
KitStatus kit_build_client_need_await(KitBuildClient* c,
KitBuildNeedToken token,
KitBuildResult* out) {
- (void)c;
- (void)token;
- (void)out;
- return KIT_UNSUPPORTED;
+ BuildReq req;
+ if (!c) return KIT_INVALID;
+ memset(&req, 0, sizeof req);
+ req.cmd = BUILD_CMD_NEED_AWAIT;
+ req.token = token.id;
+ return build_client_need_result(c, &req, out);
}
diff --git a/src/api/config_stubs.c b/src/api/config_stubs.c
@@ -15,7 +15,7 @@
#include "debug/debug.h"
#include "link/link.h"
-#if !KIT_CAS_ENABLED
+#if !KIT_BUILD_ENABLED
KitStatus kit_build_coordinator_open(const KitContext* ctx,
const KitBuildHost* host,
KitSlice store_root,
diff --git a/src/build/bundle.c b/src/build/bundle.c
@@ -3,6 +3,40 @@
#include <stdio.h>
#include <string.h>
+#define BUILD_BUNDLE_MANIFEST_PATH "manifest"
+#define BUILD_BUNDLE_TRACE_DIR "trace"
+#define BUILD_BUNDLE_BLOB_DIR "blob"
+
+#if defined(__GNUC__) || defined(__clang__)
+#define BUILD_MAYBE_UNUSED __attribute__((unused))
+#else
+#define BUILD_MAYBE_UNUSED
+#endif
+
+typedef struct BuildIdVec {
+ uint8_t (*ids)[BUILD_HASH_LEN];
+ size_t n;
+ size_t cap;
+} BuildIdVec;
+
+typedef struct ParsedTrace {
+ char target[BUILD_TARGET_MAX];
+ uint8_t kind;
+ uint8_t output[BUILD_HASH_LEN];
+} ParsedTrace;
+
+typedef struct RenderedArgv {
+ KitSlice* argv;
+ size_t argc;
+ char* storage;
+ size_t storage_size;
+} RenderedArgv;
+
+typedef struct TraceRenderTokens {
+ const char* target;
+ const char* out;
+} TraceRenderTokens;
+
static int write_cstr(KitWriter* out, const char* s) {
return out && kit_writer_write(out, s, strlen(s)) == KIT_OK ? BUILD_OK
: BUILD_ERR;
@@ -54,6 +88,181 @@ static int valid_token(const char* s, size_t cap) {
return 1;
}
+static KitSlice str_slice(const char* s) { return kit_slice_cstr(s); }
+
+static int BUILD_MAYBE_UNUSED path_set(char* out, size_t cap, KitSlice s) {
+ if (!out || cap == 0u || !s.s || s.len + 1u > cap) return BUILD_ERR;
+ memcpy(out, s.s, s.len);
+ out[s.len] = '\0';
+ return BUILD_OK;
+}
+
+static int path_join2(char* out, size_t cap, const char* a, const char* b) {
+ size_t na, nb;
+ int need_sep;
+ if (!out || cap == 0u || !a || !b) return BUILD_ERR;
+ na = strlen(a);
+ nb = strlen(b);
+ need_sep = na > 0u && a[na - 1u] != '/';
+ if (na + (need_sep ? 1u : 0u) + nb + 1u > cap) return BUILD_ERR;
+ memcpy(out, a, na);
+ if (need_sep) out[na++] = '/';
+ memcpy(out + na, b, nb);
+ out[na + nb] = '\0';
+ return BUILD_OK;
+}
+
+static int mkdir_p_host(const KitBuildCoordinator* c, const char* path) {
+ if (!c || !c->host.cas_host || !c->host.cas_host->mkdir_p || !path)
+ return BUILD_ERR;
+ return c->host.cas_host->mkdir_p(c->host.cas_host->user, path) == 0
+ ? BUILD_OK
+ : BUILD_ERR;
+}
+
+static int BUILD_MAYBE_UNUSED remove_path(const KitBuildCoordinator* c,
+ const char* path, int recursive) {
+ if (!c || !c->host.store_io || !c->host.store_io->remove || !path)
+ return BUILD_ERR;
+ return c->host.store_io->remove(c->host.store_io->user, str_slice(path),
+ recursive) == 0
+ ? BUILD_OK
+ : BUILD_ERR;
+}
+
+static int BUILD_MAYBE_UNUSED make_tmp_dir(const KitBuildCoordinator* c,
+ char* out, size_t cap) {
+ char parent[BUILD_PATH_MAX];
+ if (!c || !c->host.store_io || !c->host.store_io->make_temp_dir)
+ return BUILD_ERR;
+ if (path_join2(parent, sizeof parent, c->store.root, "tmp") != BUILD_OK)
+ return BUILD_ERR;
+ if (mkdir_p_host(c, parent) != BUILD_OK) return BUILD_ERR;
+ return c->host.store_io->make_temp_dir(c->host.store_io->user,
+ str_slice(parent), out, cap) == 0
+ ? BUILD_OK
+ : BUILD_ERR;
+}
+
+static int write_file(const KitBuildCoordinator* c, const char* path,
+ const uint8_t* data, size_t len) {
+ KitWriter* w = NULL;
+ KitStatus st;
+ if (!c || !c->host.cas_host || !c->host.cas_host->file_io ||
+ !c->host.cas_host->file_io->open_writer || !path || (!data && len))
+ return BUILD_ERR;
+ if (c->host.cas_host->file_io->open_writer(
+ c->host.cas_host->file_io->user, path, &w) != KIT_OK ||
+ !w)
+ return BUILD_ERR;
+ st = len ? kit_writer_write(w, data, len) : KIT_OK;
+ if (st == KIT_OK) st = kit_writer_status(w);
+ kit_writer_close(w);
+ return st == KIT_OK ? BUILD_OK : BUILD_ERR;
+}
+
+static int read_file(const KitBuildCoordinator* c, const char* path,
+ KitFileData* out) {
+ if (!c || !c->host.cas_host || !c->host.cas_host->file_io ||
+ !c->host.cas_host->file_io->read_all || !path || !out)
+ return BUILD_ERR;
+ out->data = NULL;
+ out->size = 0u;
+ out->token = NULL;
+ return c->host.cas_host->file_io->read_all(c->host.cas_host->file_io->user,
+ path, out) == KIT_OK
+ ? BUILD_OK
+ : BUILD_ERR;
+}
+
+static void release_file(const KitBuildCoordinator* c, KitFileData* fd) {
+ if (!c || !fd || !c->host.cas_host || !c->host.cas_host->file_io ||
+ !c->host.cas_host->file_io->release)
+ return;
+ if (fd->data) c->host.cas_host->file_io->release(c->host.cas_host->file_io->user, fd);
+ fd->data = NULL;
+ fd->size = 0u;
+ fd->token = NULL;
+}
+
+static int ensure_parent_dir(const KitBuildCoordinator* c, const char* path) {
+ char parent[BUILD_PATH_MAX];
+ size_t n, i;
+ if (!path) return BUILD_ERR;
+ n = strlen(path);
+ for (i = n; i > 0u; --i) {
+ if (path[i - 1u] == '/') {
+ size_t len = i - 1u;
+ if (len == 0u) len = 1u;
+ if (len + 1u > sizeof parent) return BUILD_ERR;
+ memcpy(parent, path, len);
+ parent[len] = '\0';
+ return mkdir_p_host(c, parent);
+ }
+ }
+ return BUILD_OK;
+}
+
+static int payload_id_path(char* out, size_t cap, const char* root,
+ const char* kind,
+ const uint8_t id[BUILD_HASH_LEN]) {
+ char hex[BUILD_HEX_LEN];
+ char rel[BUILD_PATH_MAX];
+ kit_hex_encode(hex, id, BUILD_HASH_LEN);
+ if (!root || !kind || !id) return BUILD_ERR;
+ if (snprintf(rel, sizeof rel, "%s/%c%c/%s", kind, hex[0], hex[1], hex) >=
+ (int)sizeof rel)
+ return BUILD_ERR;
+ return path_join2(out, cap, root, rel);
+}
+
+static size_t count_lines(const uint8_t* data, size_t len) {
+ size_t i, n = 0u;
+ for (i = 0u; i < len; ++i)
+ if (data[i] == '\n') ++n;
+ return n;
+}
+
+static int id_vec_contains(const BuildIdVec* v,
+ const uint8_t id[BUILD_HASH_LEN]) {
+ size_t i;
+ if (!v || !id) return 0;
+ for (i = 0u; i < v->n; ++i)
+ if (build_id_eq(v->ids[i], id)) return 1;
+ return 0;
+}
+
+static int id_vec_add(const KitContext* ctx, BuildIdVec* v,
+ const uint8_t id[BUILD_HASH_LEN]) {
+ uint8_t (*next)[BUILD_HASH_LEN];
+ size_t next_cap;
+ if (!ctx || !ctx->heap || !v || !id) return BUILD_ERR;
+ if (id_vec_contains(v, id)) return BUILD_OK;
+ if (v->n == v->cap) {
+ next_cap = v->cap ? 2u * v->cap : 16u;
+ next = (uint8_t(*)[BUILD_HASH_LEN])ctx->heap->alloc(
+ ctx->heap, next_cap * sizeof *next, _Alignof(uint8_t));
+ if (!next) return BUILD_ERR;
+ if (v->ids) {
+ memcpy(next, v->ids, v->n * sizeof *next);
+ ctx->heap->free(ctx->heap, v->ids, v->cap * sizeof *v->ids);
+ }
+ v->ids = next;
+ v->cap = next_cap;
+ }
+ memcpy(v->ids[v->n++], id, BUILD_HASH_LEN);
+ return BUILD_OK;
+}
+
+static void BUILD_MAYBE_UNUSED id_vec_free(const KitContext* ctx,
+ BuildIdVec* v) {
+ if (!ctx || !ctx->heap || !v) return;
+ if (v->ids) ctx->heap->free(ctx->heap, v->ids, v->cap * sizeof *v->ids);
+ v->ids = NULL;
+ v->n = 0u;
+ v->cap = 0u;
+}
+
static int kind_valid(uint8_t kind) {
return kind == (uint8_t)BUILD_TRACE_DEEP ||
kind == (uint8_t)BUILD_TRACE_SHALLOW;
@@ -77,6 +286,81 @@ static int kind_parse(const char* s, uint8_t* out) {
return BUILD_ERR;
}
+static void* heap_array(const KitContext* ctx, size_t n, size_t elem,
+ size_t align) {
+ if (!ctx || !ctx->heap || elem == 0u) return NULL;
+ if (n == 0u) n = 1u;
+ if (n > (size_t)-1 / elem) return NULL;
+ return ctx->heap->alloc(ctx->heap, n * elem, align);
+}
+
+static void heap_free_array(const KitContext* ctx, void* p, size_t n,
+ size_t elem) {
+ if (!ctx || !ctx->heap || !p || elem == 0u) return;
+ if (n == 0u) n = 1u;
+ ctx->heap->free(ctx->heap, p, n * elem);
+}
+
+static int BUILD_MAYBE_UNUSED parse_trace_header(const KitContext* ctx,
+ const uint8_t* data,
+ size_t len,
+ ParsedTrace* out) {
+ char err[160];
+ if (!ctx || !data || !out) return BUILD_ERR;
+ memset(out, 0, sizeof *out);
+ if (len >= sizeof BUILD_DEEP_MAGIC &&
+ memcmp(data, BUILD_DEEP_MAGIC "\n", sizeof BUILD_DEEP_MAGIC) == 0) {
+ BuildDeepTrace t;
+ if (build_deep_parse(data, len, &t, err, sizeof err) != BUILD_OK)
+ return BUILD_ERR;
+ snprintf(out->target, sizeof out->target, "%s", t.target);
+ out->kind = (uint8_t)BUILD_TRACE_DEEP;
+ memcpy(out->output, t.output, BUILD_HASH_LEN);
+ return BUILD_OK;
+ }
+ if (len >= sizeof BUILD_SHALLOW_MAGIC &&
+ memcmp(data, BUILD_SHALLOW_MAGIC "\n", sizeof BUILD_SHALLOW_MAGIC) == 0) {
+ BuildShallowTrace t;
+ size_t rows = count_lines(data, len);
+ memset(&t, 0, sizeof t);
+ t.config_keys = (BuildConfigKey*)heap_array(
+ ctx, rows, sizeof *t.config_keys, _Alignof(BuildConfigKey));
+ t.sources = (BuildSourceLeaf*)heap_array(ctx, rows, sizeof *t.sources,
+ _Alignof(BuildSourceLeaf));
+ t.globs = (BuildGlobLeaf*)heap_array(ctx, rows, sizeof *t.globs,
+ _Alignof(BuildGlobLeaf));
+ t.deps = (BuildDepEdge*)heap_array(ctx, rows, sizeof *t.deps,
+ _Alignof(BuildDepEdge));
+ if (!t.config_keys || !t.sources || !t.globs || !t.deps) {
+ heap_free_array(ctx, t.config_keys, rows, sizeof *t.config_keys);
+ heap_free_array(ctx, t.sources, rows, sizeof *t.sources);
+ heap_free_array(ctx, t.globs, rows, sizeof *t.globs);
+ heap_free_array(ctx, t.deps, rows, sizeof *t.deps);
+ return BUILD_ERR;
+ }
+ t.cap_config_keys = rows;
+ t.cap_sources = rows;
+ t.cap_globs = rows;
+ t.cap_deps = rows;
+ if (build_shallow_parse(data, len, &t, err, sizeof err) != BUILD_OK) {
+ heap_free_array(ctx, t.config_keys, rows, sizeof *t.config_keys);
+ heap_free_array(ctx, t.sources, rows, sizeof *t.sources);
+ heap_free_array(ctx, t.globs, rows, sizeof *t.globs);
+ heap_free_array(ctx, t.deps, rows, sizeof *t.deps);
+ return BUILD_ERR;
+ }
+ snprintf(out->target, sizeof out->target, "%s", t.target);
+ out->kind = (uint8_t)BUILD_TRACE_SHALLOW;
+ memcpy(out->output, t.output, BUILD_HASH_LEN);
+ heap_free_array(ctx, t.config_keys, rows, sizeof *t.config_keys);
+ heap_free_array(ctx, t.sources, rows, sizeof *t.sources);
+ heap_free_array(ctx, t.globs, rows, sizeof *t.globs);
+ heap_free_array(ctx, t.deps, rows, sizeof *t.deps);
+ return BUILD_OK;
+ }
+ return BUILD_ERR;
+}
+
static int claim_cmp(const BuildTraceClaim* a, const BuildTraceClaim* b) {
int c = strcmp(a->target, b->target);
if (c != 0) return c;
@@ -198,23 +482,770 @@ int build_bundle_manifest_parse(const uint8_t* data, size_t len,
return BUILD_OK;
}
+static int payload_write_blob_from_cas(KitBuildCoordinator* c,
+ const uint8_t id[BUILD_HASH_LEN],
+ const char* root, BuildIdVec* seen) {
+ KitFileData fd;
+ char path[BUILD_PATH_MAX];
+ int ok = BUILD_ERR;
+ if (!c || !id || !root || !seen) return BUILD_ERR;
+ if (id_vec_contains(seen, id)) return BUILD_OK;
+ if (kit_cas_get_blob(c->cas, id, &fd) != KIT_OK) return BUILD_ERR;
+ if (payload_id_path(path, sizeof path, root, BUILD_BUNDLE_BLOB_DIR, id) !=
+ BUILD_OK ||
+ ensure_parent_dir(c, path) != BUILD_OK ||
+ write_file(c, path, fd.data, fd.size) != BUILD_OK)
+ goto out;
+ ok = id_vec_add(c->ctx, seen, id);
+out:
+ kit_cas_release(c->cas, &fd);
+ return ok;
+}
+
+static int payload_install_blob(KitBuildCoordinator* c,
+ const uint8_t id[BUILD_HASH_LEN],
+ const char* root, BuildIdVec* seen,
+ const uint8_t** data_out, size_t* len_out,
+ KitFileData* fd_out) {
+ KitBlobInfo info;
+ char path[BUILD_PATH_MAX];
+ if (!c || !id || !root || !seen || !fd_out) return BUILD_ERR;
+ if (data_out) *data_out = NULL;
+ if (len_out) *len_out = 0u;
+ fd_out->data = NULL;
+ fd_out->size = 0u;
+ fd_out->token = NULL;
+ if (payload_id_path(path, sizeof path, root, BUILD_BUNDLE_BLOB_DIR, id) !=
+ BUILD_OK ||
+ read_file(c, path, fd_out) != BUILD_OK)
+ return BUILD_ERR;
+ if (kit_cas_add_blob(c->cas, fd_out->data, fd_out->size, &info) != KIT_OK ||
+ !build_id_eq(info.id, id)) {
+ release_file(c, fd_out);
+ return BUILD_ERR;
+ }
+ if (id_vec_add(c->ctx, seen, id) != BUILD_OK) {
+ release_file(c, fd_out);
+ return BUILD_ERR;
+ }
+ if (data_out) *data_out = fd_out->data;
+ if (len_out) *len_out = fd_out->size;
+ return BUILD_OK;
+}
+
+static int export_deepset_closure(KitBuildCoordinator* c,
+ const uint8_t id[BUILD_HASH_LEN],
+ const char* root, BuildIdVec* seen) {
+ KitFileData fd;
+ BuildDeepSet ds;
+ size_t rows;
+ size_t i;
+ char err[160];
+ int ok = BUILD_ERR;
+ if (id_vec_contains(seen, id)) return BUILD_OK;
+ if (payload_write_blob_from_cas(c, id, root, seen) != BUILD_OK)
+ return BUILD_ERR;
+ if (kit_cas_get_blob(c->cas, id, &fd) != KIT_OK) return BUILD_ERR;
+ rows = count_lines(fd.data, fd.size);
+ memset(&ds, 0, sizeof ds);
+ ds.sources = (BuildSourceLeaf*)heap_array(c->ctx, rows, sizeof *ds.sources,
+ _Alignof(BuildSourceLeaf));
+ ds.globs = (BuildGlobLeaf*)heap_array(c->ctx, rows, sizeof *ds.globs,
+ _Alignof(BuildGlobLeaf));
+ ds.children = (uint8_t(*)[BUILD_HASH_LEN])heap_array(
+ c->ctx, rows, sizeof *ds.children, _Alignof(uint8_t));
+ if (!ds.sources || !ds.globs || !ds.children) goto out;
+ ds.cap_sources = rows;
+ ds.cap_globs = rows;
+ ds.cap_children = rows;
+ if (build_deepset_parse(fd.data, fd.size, &ds, err, sizeof err) != BUILD_OK)
+ goto out;
+ for (i = 0u; i < ds.n_children; ++i)
+ if (export_deepset_closure(c, ds.children[i], root, seen) != BUILD_OK)
+ goto out;
+ ok = BUILD_OK;
+out:
+ heap_free_array(c->ctx, ds.sources, rows, sizeof *ds.sources);
+ heap_free_array(c->ctx, ds.globs, rows, sizeof *ds.globs);
+ heap_free_array(c->ctx, ds.children, rows, sizeof *ds.children);
+ kit_cas_release(c->cas, &fd);
+ return ok;
+}
+
+static int import_deepset_closure(KitBuildCoordinator* c,
+ const uint8_t id[BUILD_HASH_LEN],
+ const char* root, BuildIdVec* seen) {
+ KitFileData fd;
+ const uint8_t* data = NULL;
+ size_t len = 0u;
+ BuildDeepSet ds;
+ size_t rows;
+ size_t i;
+ char err[160];
+ int ok = BUILD_ERR;
+ if (id_vec_contains(seen, id)) return BUILD_OK;
+ if (payload_install_blob(c, id, root, seen, &data, &len, &fd) != BUILD_OK)
+ return BUILD_ERR;
+ rows = count_lines(data, len);
+ memset(&ds, 0, sizeof ds);
+ ds.sources = (BuildSourceLeaf*)heap_array(c->ctx, rows, sizeof *ds.sources,
+ _Alignof(BuildSourceLeaf));
+ ds.globs = (BuildGlobLeaf*)heap_array(c->ctx, rows, sizeof *ds.globs,
+ _Alignof(BuildGlobLeaf));
+ ds.children = (uint8_t(*)[BUILD_HASH_LEN])heap_array(
+ c->ctx, rows, sizeof *ds.children, _Alignof(uint8_t));
+ if (!ds.sources || !ds.globs || !ds.children) goto out;
+ ds.cap_sources = rows;
+ ds.cap_globs = rows;
+ ds.cap_children = rows;
+ if (build_deepset_parse(data, len, &ds, err, sizeof err) != BUILD_OK)
+ goto out;
+ for (i = 0u; i < ds.n_children; ++i)
+ if (import_deepset_closure(c, ds.children[i], root, seen) != BUILD_OK)
+ goto out;
+ ok = BUILD_OK;
+out:
+ heap_free_array(c->ctx, ds.sources, rows, sizeof *ds.sources);
+ heap_free_array(c->ctx, ds.globs, rows, sizeof *ds.globs);
+ heap_free_array(c->ctx, ds.children, rows, sizeof *ds.children);
+ release_file(c, &fd);
+ return ok;
+}
+
+static int BUILD_MAYBE_UNUSED export_trace_refs(KitBuildCoordinator* c,
+ const uint8_t* data,
+ size_t len, const char* root,
+ BuildIdVec* seen) {
+ char err[160];
+ if (len >= sizeof BUILD_DEEP_MAGIC &&
+ memcmp(data, BUILD_DEEP_MAGIC "\n", sizeof BUILD_DEEP_MAGIC) == 0) {
+ BuildDeepTrace t;
+ if (build_deep_parse(data, len, &t, err, sizeof err) != BUILD_OK)
+ return BUILD_ERR;
+ if (payload_write_blob_from_cas(c, t.root_config, root, seen) !=
+ BUILD_OK ||
+ payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK ||
+ export_deepset_closure(c, t.deepset, root, seen) != BUILD_OK)
+ return BUILD_ERR;
+ return BUILD_OK;
+ }
+ if (len >= sizeof BUILD_SHALLOW_MAGIC &&
+ memcmp(data, BUILD_SHALLOW_MAGIC "\n", sizeof BUILD_SHALLOW_MAGIC) == 0) {
+ BuildShallowTrace t;
+ size_t rows = count_lines(data, len);
+ size_t i;
+ int ok = BUILD_ERR;
+ memset(&t, 0, sizeof t);
+ t.config_keys = (BuildConfigKey*)heap_array(
+ c->ctx, rows, sizeof *t.config_keys, _Alignof(BuildConfigKey));
+ t.sources = (BuildSourceLeaf*)heap_array(c->ctx, rows, sizeof *t.sources,
+ _Alignof(BuildSourceLeaf));
+ t.globs = (BuildGlobLeaf*)heap_array(c->ctx, rows, sizeof *t.globs,
+ _Alignof(BuildGlobLeaf));
+ t.deps = (BuildDepEdge*)heap_array(c->ctx, rows, sizeof *t.deps,
+ _Alignof(BuildDepEdge));
+ if (!t.config_keys || !t.sources || !t.globs || !t.deps) goto shallow_out;
+ t.cap_config_keys = rows;
+ t.cap_sources = rows;
+ t.cap_globs = rows;
+ t.cap_deps = rows;
+ if (build_shallow_parse(data, len, &t, err, sizeof err) != BUILD_OK)
+ goto shallow_out;
+ if (payload_write_blob_from_cas(c, t.config, root, seen) != BUILD_OK ||
+ payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK)
+ goto shallow_out;
+ for (i = 0u; i < t.n_deps; ++i) {
+ if (payload_write_blob_from_cas(c, t.deps[i].config_id, root, seen) !=
+ BUILD_OK ||
+ payload_write_blob_from_cas(c, t.deps[i].argv_id, root, seen) !=
+ BUILD_OK)
+ goto shallow_out;
+ }
+ ok = BUILD_OK;
+shallow_out:
+ heap_free_array(c->ctx, t.config_keys, rows, sizeof *t.config_keys);
+ heap_free_array(c->ctx, t.sources, rows, sizeof *t.sources);
+ heap_free_array(c->ctx, t.globs, rows, sizeof *t.globs);
+ heap_free_array(c->ctx, t.deps, rows, sizeof *t.deps);
+ return ok;
+ }
+ return BUILD_ERR;
+}
+
+static int BUILD_MAYBE_UNUSED import_trace_refs(KitBuildCoordinator* c,
+ const uint8_t* data,
+ size_t len, const char* root,
+ BuildIdVec* seen) {
+ char err[160];
+ if (len >= sizeof BUILD_DEEP_MAGIC &&
+ memcmp(data, BUILD_DEEP_MAGIC "\n", sizeof BUILD_DEEP_MAGIC) == 0) {
+ BuildDeepTrace t;
+ KitFileData fd;
+ if (build_deep_parse(data, len, &t, err, sizeof err) != BUILD_OK)
+ return BUILD_ERR;
+ if (payload_install_blob(c, t.root_config, root, seen, NULL, NULL, &fd) !=
+ BUILD_OK)
+ return BUILD_ERR;
+ release_file(c, &fd);
+ if (payload_install_blob(c, t.argv, root, seen, NULL, NULL, &fd) !=
+ BUILD_OK)
+ return BUILD_ERR;
+ release_file(c, &fd);
+ return import_deepset_closure(c, t.deepset, root, seen);
+ }
+ if (len >= sizeof BUILD_SHALLOW_MAGIC &&
+ memcmp(data, BUILD_SHALLOW_MAGIC "\n", sizeof BUILD_SHALLOW_MAGIC) == 0) {
+ BuildShallowTrace t;
+ size_t rows = count_lines(data, len);
+ size_t i;
+ int ok = BUILD_ERR;
+ KitFileData fd;
+ memset(&t, 0, sizeof t);
+ memset(&fd, 0, sizeof fd);
+ t.config_keys = (BuildConfigKey*)heap_array(
+ c->ctx, rows, sizeof *t.config_keys, _Alignof(BuildConfigKey));
+ t.sources = (BuildSourceLeaf*)heap_array(c->ctx, rows, sizeof *t.sources,
+ _Alignof(BuildSourceLeaf));
+ t.globs = (BuildGlobLeaf*)heap_array(c->ctx, rows, sizeof *t.globs,
+ _Alignof(BuildGlobLeaf));
+ t.deps = (BuildDepEdge*)heap_array(c->ctx, rows, sizeof *t.deps,
+ _Alignof(BuildDepEdge));
+ if (!t.config_keys || !t.sources || !t.globs || !t.deps) goto shallow_out;
+ t.cap_config_keys = rows;
+ t.cap_sources = rows;
+ t.cap_globs = rows;
+ t.cap_deps = rows;
+ if (build_shallow_parse(data, len, &t, err, sizeof err) != BUILD_OK)
+ goto shallow_out;
+ if (payload_install_blob(c, t.config, root, seen, NULL, NULL, &fd) !=
+ BUILD_OK)
+ goto shallow_out;
+ release_file(c, &fd);
+ if (payload_install_blob(c, t.argv, root, seen, NULL, NULL, &fd) !=
+ BUILD_OK)
+ goto shallow_out;
+ release_file(c, &fd);
+ for (i = 0u; i < t.n_deps; ++i) {
+ if (payload_install_blob(c, t.deps[i].config_id, root, seen, NULL, NULL,
+ &fd) != BUILD_OK)
+ goto shallow_out;
+ release_file(c, &fd);
+ if (payload_install_blob(c, t.deps[i].argv_id, root, seen, NULL, NULL,
+ &fd) != BUILD_OK)
+ goto shallow_out;
+ release_file(c, &fd);
+ }
+ ok = BUILD_OK;
+shallow_out:
+ release_file(c, &fd);
+ heap_free_array(c->ctx, t.config_keys, rows, sizeof *t.config_keys);
+ heap_free_array(c->ctx, t.sources, rows, sizeof *t.sources);
+ heap_free_array(c->ctx, t.globs, rows, sizeof *t.globs);
+ heap_free_array(c->ctx, t.deps, rows, sizeof *t.deps);
+ return ok;
+ }
+ return BUILD_ERR;
+}
+
+static int gather_export_claims(KitBuildCoordinator* c,
+ const KitBuildExportOptions* opts,
+ BuildTraceClaim* claims, size_t cap,
+ size_t* nclaims, const char* root,
+ BuildIdVec* seen_blobs) {
+ size_t ti;
+ if (!c || !opts || !claims || !nclaims || !root || !seen_blobs)
+ return BUILD_ERR;
+ *nclaims = 0u;
+ for (ti = 0u; ti < opts->ntargets; ++ti) {
+ uint8_t key[BUILD_HASH_LEN];
+ BuildRecordRow rows[2u * KIT_BUILD_RECORD_CAP];
+ BuildTargetRecord rec;
+ size_t ri;
+ if (build_target_key(opts->targets[ti], key) != BUILD_OK)
+ return BUILD_ERR;
+ memset(&rec, 0, sizeof rec);
+ rec.rows = rows;
+ rec.cap_rows = sizeof rows / sizeof rows[0];
+ if (build_store_record_load(&c->store, key, opts->targets[ti], &rec) !=
+ BUILD_OK)
+ return BUILD_ERR;
+ for (ri = 0u; ri < rec.n_rows; ++ri) {
+ KitFileData fd;
+ ParsedTrace pt;
+ BuildTraceClaim* cl;
+ char path[BUILD_PATH_MAX];
+ if (*nclaims >= cap) return BUILD_ERR;
+ if (build_store_get_trace(&c->store, rec.rows[ri].trace_id, &fd) !=
+ BUILD_OK)
+ continue;
+ if (parse_trace_header(c->ctx, fd.data, fd.size, &pt) != BUILD_OK ||
+ pt.kind != rec.rows[ri].kind ||
+ !kit_slice_eq_cstr(opts->targets[ti], pt.target)) {
+ build_store_release(&c->store, &fd);
+ continue;
+ }
+ if (payload_id_path(path, sizeof path, root, BUILD_BUNDLE_TRACE_DIR,
+ rec.rows[ri].trace_id) != BUILD_OK ||
+ ensure_parent_dir(c, path) != BUILD_OK ||
+ write_file(c, path, fd.data, fd.size) != BUILD_OK ||
+ export_trace_refs(c, fd.data, fd.size, root, seen_blobs) !=
+ BUILD_OK) {
+ build_store_release(&c->store, &fd);
+ return BUILD_ERR;
+ }
+ cl = &claims[(*nclaims)++];
+ snprintf(cl->target, sizeof cl->target, "%s", pt.target);
+ cl->kind = pt.kind;
+ memcpy(cl->trace_id, rec.rows[ri].trace_id, BUILD_HASH_LEN);
+ memcpy(cl->output_tree, pt.output, BUILD_HASH_LEN);
+ build_store_release(&c->store, &fd);
+ }
+ }
+ return BUILD_OK;
+}
+
int build_bundle_export(KitBuildCoordinator* c,
const KitBuildExportOptions* opts) {
- (void)c;
- (void)opts;
- return BUILD_ERR;
+ BuildTraceClaim* claims = NULL;
+ size_t cap, nclaims = 0u;
+ KitWriter* manw = NULL;
+ const uint8_t* man;
+ size_t man_len;
+ char root[BUILD_PATH_MAX];
+ char manifest_path[BUILD_PATH_MAX];
+ char out_path[BUILD_PATH_MAX];
+ KitPkgCreateOptions popts;
+ KitPkgCreateResult pres;
+ BuildIdVec seen_blobs;
+ int ok = BUILD_ERR;
+
+ memset(&seen_blobs, 0, sizeof seen_blobs);
+ root[0] = '\0';
+ if (!c || !opts || !opts->targets || opts->ntargets == 0u || !opts->sk ||
+ !opts->keyid || !opts->out_path.s)
+ return BUILD_ERR;
+ if (opts->ntargets > (size_t)-1 / (2u * KIT_BUILD_RECORD_CAP))
+ return BUILD_ERR;
+ if (opts->format == KIT_PKG_FORMAT_AUTO) {
+ build_diagf(c->ctx, "trace bundle export: explicit package format required");
+ return BUILD_ERR;
+ }
+ if (opts->include_outputs) {
+ build_diagf(c->ctx,
+ "trace bundle export: include_outputs needs public CAS tree "
+ "entry enumeration");
+ return BUILD_ERR;
+ }
+ if (!c->host.cas_host || !c->host.cas_host->walk_regular_files) {
+ build_diagf(c->ctx,
+ "trace bundle export: package root walk is unavailable");
+ return BUILD_ERR;
+ }
+ if (path_set(out_path, sizeof out_path, opts->out_path) != BUILD_OK)
+ return BUILD_ERR;
+ cap = opts->ntargets * 2u * KIT_BUILD_RECORD_CAP;
+ claims = (BuildTraceClaim*)heap_array(c->ctx, cap, sizeof *claims,
+ _Alignof(BuildTraceClaim));
+ if (!claims) return BUILD_ERR;
+ if (make_tmp_dir(c, root, sizeof root) != BUILD_OK) goto out;
+ if (gather_export_claims(c, opts, claims, cap, &nclaims, root,
+ &seen_blobs) != BUILD_OK)
+ goto out;
+ if (kit_writer_mem(c->ctx->heap, &manw) != KIT_OK || !manw) goto out;
+ if (build_bundle_manifest_emit(claims, nclaims, manw) != BUILD_OK ||
+ kit_writer_status(manw) != KIT_OK)
+ goto out;
+ man = kit_writer_mem_bytes(manw, &man_len);
+ if (path_join2(manifest_path, sizeof manifest_path, root,
+ BUILD_BUNDLE_MANIFEST_PATH) != BUILD_OK ||
+ write_file(c, manifest_path, man, man_len) != BUILD_OK)
+ goto out;
+
+ memset(&popts, 0, sizeof popts);
+ memset(&pres, 0, sizeof pres);
+ popts.name = "kit-build-traces";
+ popts.version = "1";
+ popts.description = "kit build trace bundle";
+ popts.format = opts->format;
+ popts.native_shape = KIT_PKG_SHAPE_FAT;
+ popts.compression = KIT_PKG_COMPRESSION_NONE;
+ popts.root_dir = root;
+ popts.sk = opts->sk;
+ popts.keyid = opts->keyid;
+ popts.out_path = out_path;
+ if (kit_pkg_create(c->ctx, c->host.cas_host, &popts, &pres) != KIT_OK)
+ goto out;
+ ok = BUILD_OK;
+out:
+ if (manw) kit_writer_close(manw);
+ if (root[0]) (void)remove_path(c, root, 1);
+ id_vec_free(c->ctx, &seen_blobs);
+ heap_free_array(c->ctx, claims, cap, sizeof *claims);
+ if (ok != BUILD_OK)
+ build_diagf(c->ctx, "trace bundle export: failed to create bundle");
+ return ok;
+}
+
+static int read_payload_manifest(KitBuildCoordinator* c, const char* root,
+ KitFileData* out) {
+ char path[BUILD_PATH_MAX];
+ if (path_join2(path, sizeof path, root, BUILD_BUNDLE_MANIFEST_PATH) !=
+ BUILD_OK)
+ return BUILD_ERR;
+ return read_file(c, path, out);
+}
+
+static int validate_import_claim(KitBuildCoordinator* c, const char* root,
+ const BuildTraceClaim* cl,
+ BuildIdVec* seen_blobs) {
+ KitFileData fd;
+ ParsedTrace pt;
+ uint8_t got[BUILD_HASH_LEN];
+ char path[BUILD_PATH_MAX];
+ int ok = BUILD_ERR;
+ if (!c || !root || !cl || !seen_blobs) return BUILD_ERR;
+ if (payload_id_path(path, sizeof path, root, BUILD_BUNDLE_TRACE_DIR,
+ cl->trace_id) != BUILD_OK ||
+ read_file(c, path, &fd) != BUILD_OK)
+ return BUILD_ERR;
+ build_trace_id(fd.data, fd.size, got);
+ if (!build_id_eq(got, cl->trace_id)) goto out;
+ if (parse_trace_header(c->ctx, fd.data, fd.size, &pt) != BUILD_OK)
+ goto out;
+ if (strcmp(pt.target, cl->target) != 0 || pt.kind != cl->kind ||
+ !build_id_eq(pt.output, cl->output_tree))
+ goto out;
+ if (import_trace_refs(c, fd.data, fd.size, root, seen_blobs) != BUILD_OK)
+ goto out;
+ if (build_store_put_trace(&c->store, fd.data, fd.size, got) != BUILD_OK ||
+ !build_id_eq(got, cl->trace_id))
+ goto out;
+ ok = BUILD_OK;
+out:
+ release_file(c, &fd);
+ return ok;
}
int build_bundle_import(KitBuildCoordinator* c, const KitBuildImportOptions* opts,
KitBuildImportResult* result) {
- (void)c;
- (void)opts;
- (void)result;
- return BUILD_ERR;
+ KitPkgVerifyOptions vopts;
+ KitPkgVerifyResult vres;
+ KitFileData manfd;
+ BuildTraceClaim* claims = NULL;
+ size_t nclaims = 0u, cap = 0u;
+ char unpack[BUILD_PATH_MAX];
+ char err[160];
+ BuildIdVec seen_blobs;
+ size_t i;
+ int ok = BUILD_ERR;
+
+ memset(&seen_blobs, 0, sizeof seen_blobs);
+ memset(&manfd, 0, sizeof manfd);
+ unpack[0] = '\0';
+ if (result) memset(result, 0, sizeof *result);
+ if (!c || !opts || !opts->pkg_data || opts->pkg_len == 0u ||
+ opts->format == KIT_PKG_FORMAT_AUTO)
+ return BUILD_ERR;
+ if (make_tmp_dir(c, unpack, sizeof unpack) != BUILD_OK) goto out;
+ memset(&vopts, 0, sizeof vopts);
+ memset(&vres, 0, sizeof vres);
+ vopts.pkg_data = opts->pkg_data;
+ vopts.pkg_len = opts->pkg_len;
+ vopts.format = opts->format;
+ vopts.unpack_dir = unpack;
+ vopts.pubkey_bytes = opts->pubkey_bytes;
+ vopts.pubkey_len = opts->pubkey_len;
+ vopts.tofu = opts->tofu;
+ vopts.trusted_keys = opts->trusted_keys;
+ vopts.trusted_keys_len = opts->trusted_keys_len;
+ if (kit_pkg_verify(c->ctx, c->host.cas_host, &vopts, &vres) != KIT_OK)
+ goto out;
+ if (strcmp(vres.name, "kit-build-traces") != 0 ||
+ strcmp(vres.version, "1") != 0) {
+ build_diagf(c->ctx, "trace bundle import: package is not a trace bundle");
+ goto out;
+ }
+ if (read_payload_manifest(c, unpack, &manfd) != BUILD_OK) goto out;
+ cap = count_lines(manfd.data, manfd.size);
+ claims = (BuildTraceClaim*)heap_array(c->ctx, cap, sizeof *claims,
+ _Alignof(BuildTraceClaim));
+ if (!claims) goto out;
+ if (build_bundle_manifest_parse(manfd.data, manfd.size, claims, cap,
+ &nclaims, err, sizeof err) != BUILD_OK) {
+ build_diagf(c->ctx, "trace bundle import: %s", err);
+ goto out;
+ }
+ for (i = 0u; i < nclaims; ++i)
+ if (validate_import_claim(c, unpack, &claims[i], &seen_blobs) != BUILD_OK)
+ goto out;
+ if (nclaims > (uint32_t)-1) goto out;
+ for (i = 0u; i < nclaims; ++i) {
+ uint8_t key[BUILD_HASH_LEN];
+ if (build_target_key(str_slice(claims[i].target), key) != BUILD_OK ||
+ build_store_record_update(&c->store, key, str_slice(claims[i].target),
+ (BuildTraceKind)claims[i].kind,
+ claims[i].trace_id) != BUILD_OK)
+ goto out;
+ }
+ if (result) {
+ result->n_traces = (uint32_t)nclaims;
+ memcpy(result->keyid, vres.keyid, KIT_PKG_KEYID_LEN);
+ result->tofu_pin = vres.tofu_pin;
+ memcpy(result->tofu_pk, vres.tofu_pk, KIT_PKG_PK_LEN);
+ }
+ ok = BUILD_OK;
+out:
+ release_file(c, &manfd);
+ if (unpack[0]) (void)remove_path(c, unpack, 1);
+ id_vec_free(c->ctx, &seen_blobs);
+ heap_free_array(c->ctx, claims, cap, sizeof *claims);
+ if (ok != BUILD_OK)
+ build_diagf(c->ctx, "trace bundle import: failed to verify/install bundle");
+ return ok;
+}
+
+static int ascii_space(char c) {
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' ||
+ c == '\v';
+}
+
+static int add_size(size_t* v, size_t n) {
+ if (!v || *v > (size_t)-1 - n) return BUILD_ERR;
+ *v += n;
+ return BUILD_OK;
+}
+
+static int trace_token_match(const char* p, const char* end, const char* lit,
+ const char* repl, const char** repl_out,
+ size_t* lit_len_out, size_t* repl_len_out) {
+ size_t n;
+ if (!lit || !repl) return 0;
+ n = strlen(lit);
+ if ((size_t)(end - p) < n || memcmp(p, lit, n) != 0) return 0;
+ *repl_out = repl;
+ *lit_len_out = n;
+ *repl_len_out = strlen(repl);
+ return 1;
+}
+
+static int trace_find_token(const char* p, const char* end,
+ const TraceRenderTokens* toks,
+ const char** repl_out, size_t* lit_len_out,
+ size_t* repl_len_out) {
+ return trace_token_match(p, end, "{target}", toks->target, repl_out,
+ lit_len_out, repl_len_out) ||
+ trace_token_match(p, end, "{out}", toks->out, repl_out, lit_len_out,
+ repl_len_out);
+}
+
+static int render_emit_bytes(char* storage, size_t* off, size_t cap,
+ const char* data, size_t len) {
+ if (!off || (!data && len)) return BUILD_ERR;
+ if (storage) {
+ if (*off > cap || len > cap - *off) return BUILD_ERR;
+ if (len) memcpy(storage + *off, data, len);
+ }
+ return add_size(off, len);
+}
+
+static int trace_render_pass(KitSlice tmpl, const TraceRenderTokens* toks,
+ KitSlice* argv, size_t* argc_io, char* storage,
+ size_t storage_cap, size_t* storage_len_out) {
+ const char* p;
+ const char* end;
+ size_t argc = 0u;
+ size_t off = 0u;
+ size_t word_start = 0u;
+ size_t word_len = 0u;
+ int in_word = 0;
+ char quote = 0;
+ if (!tmpl.s || !toks || !argc_io || !storage_len_out) return BUILD_ERR;
+ p = tmpl.s;
+ end = tmpl.s + tmpl.len;
+ while (p < end) {
+ char ch = *p;
+ if (quote == 0 && ascii_space(ch)) {
+ if (in_word) {
+ if (argv) {
+ if (argc >= *argc_io || off >= storage_cap) return BUILD_ERR;
+ storage[off] = '\0';
+ argv[argc].s = storage + word_start;
+ argv[argc].len = word_len;
+ }
+ if (add_size(&off, 1u) != BUILD_OK) return BUILD_ERR;
+ ++argc;
+ in_word = 0;
+ word_len = 0u;
+ }
+ ++p;
+ continue;
+ }
+ if (!in_word) {
+ in_word = 1;
+ word_start = off;
+ word_len = 0u;
+ }
+ if (quote == 0 && (ch == '\'' || ch == '"')) {
+ quote = ch;
+ ++p;
+ continue;
+ }
+ if (quote != 0 && ch == quote) {
+ quote = 0;
+ ++p;
+ continue;
+ }
+ if ((quote == 0 || quote == '"') && ch == '\\') {
+ if (p + 1 >= end) return BUILD_ERR;
+ if (render_emit_bytes(storage, &off, storage_cap, p + 1, 1u) !=
+ BUILD_OK)
+ return BUILD_ERR;
+ ++word_len;
+ p += 2;
+ continue;
+ }
+ {
+ const char* repl = NULL;
+ size_t lit_len = 0u;
+ size_t repl_len = 0u;
+ if (trace_find_token(p, end, toks, &repl, &lit_len, &repl_len)) {
+ if (render_emit_bytes(storage, &off, storage_cap, repl, repl_len) !=
+ BUILD_OK)
+ return BUILD_ERR;
+ if (add_size(&word_len, repl_len) != BUILD_OK) return BUILD_ERR;
+ p += lit_len;
+ } else {
+ if (render_emit_bytes(storage, &off, storage_cap, p, 1u) != BUILD_OK)
+ return BUILD_ERR;
+ ++word_len;
+ ++p;
+ }
+ }
+ }
+ if (quote != 0) return BUILD_ERR;
+ if (in_word) {
+ if (argv) {
+ if (argc >= *argc_io || off >= storage_cap) return BUILD_ERR;
+ storage[off] = '\0';
+ argv[argc].s = storage + word_start;
+ argv[argc].len = word_len;
+ }
+ if (add_size(&off, 1u) != BUILD_OK) return BUILD_ERR;
+ ++argc;
+ }
+ if (argv && argc != *argc_io) return BUILD_ERR;
+ *argc_io = argc;
+ *storage_len_out = off;
+ return BUILD_OK;
+}
+
+static void rendered_argv_free(const KitContext* ctx, RenderedArgv* r) {
+ if (!ctx || !ctx->heap || !r) return;
+ if (r->storage) ctx->heap->free(ctx->heap, r->storage, r->storage_size);
+ if (r->argv) ctx->heap->free(ctx->heap, r->argv, r->argc * sizeof *r->argv);
+ r->argv = NULL;
+ r->argc = 0u;
+ r->storage = NULL;
+ r->storage_size = 0u;
+}
+
+static int trace_render_argv(const KitContext* ctx, KitSlice tmpl,
+ const TraceRenderTokens* toks,
+ RenderedArgv* out) {
+ size_t argc = 0u;
+ size_t storage_len = 0u;
+ if (!ctx || !ctx->heap || !out) return BUILD_ERR;
+ memset(out, 0, sizeof *out);
+ if (trace_render_pass(tmpl, toks, NULL, &argc, NULL, 0u, &storage_len) !=
+ BUILD_OK ||
+ argc == 0u)
+ return BUILD_ERR;
+ out->argv = (KitSlice*)ctx->heap->alloc(ctx->heap, argc * sizeof *out->argv,
+ _Alignof(KitSlice));
+ out->storage =
+ (char*)ctx->heap->alloc(ctx->heap, storage_len, _Alignof(char));
+ if (!out->argv || !out->storage) {
+ rendered_argv_free(ctx, out);
+ return BUILD_ERR;
+ }
+ out->argc = argc;
+ out->storage_size = storage_len;
+ if (trace_render_pass(tmpl, toks, out->argv, &argc, out->storage,
+ storage_len, &storage_len) != BUILD_OK) {
+ rendered_argv_free(ctx, out);
+ return BUILD_ERR;
+ }
+ return BUILD_OK;
+}
+
+static int run_fetch(const KitBuildExec* exec, const KitSlice* argv,
+ size_t argc, KitSlice cwd) {
+ KitBuildProc* proc = NULL;
+ int exit_code = 1;
+ if (!exec || !exec->spawn || !exec->wait || !argv || argc == 0u)
+ return BUILD_ERR;
+ if (exec->spawn(exec->user, argv, argc, NULL, 0u, cwd, &proc) != 0 || !proc)
+ return BUILD_ERR;
+ if (exec->wait(exec->user, proc, &exit_code) != 0) return BUILD_ERR;
+ return exit_code == 0 ? BUILD_OK : BUILD_ERR;
}
int build_trace_remote_pull(KitBuildCoordinator* c, KitSlice target) {
- (void)c;
- (void)target;
- return BUILD_ERR;
+ size_t i;
+ char tmp[BUILD_PATH_MAX];
+ char out_path[BUILD_PATH_MAX];
+ int ok = BUILD_ERR;
+ if (!c || !target.s || target.len == 0u || !c->opts.trace_remotes ||
+ c->opts.n_trace_remotes == 0u || !c->host.exec) {
+ build_diagf(c ? c->ctx : NULL,
+ "trace remote pull: no trace remote/exec configured");
+ return BUILD_ERR;
+ }
+ if (make_tmp_dir(c, tmp, sizeof tmp) != BUILD_OK) return BUILD_ERR;
+ for (i = 0u; i < c->opts.n_trace_remotes; ++i) {
+ const KitBuildTraceRemote* r = &c->opts.trace_remotes[i];
+ RenderedArgv argv;
+ TraceRenderTokens toks;
+ KitFileData fd;
+ KitBuildImportOptions iopts;
+ KitBuildImportResult ires;
+ char target_buf[BUILD_TARGET_MAX];
+ char leaf[64];
+ memset(&argv, 0, sizeof argv);
+ memset(&fd, 0, sizeof fd);
+ if (path_set(target_buf, sizeof target_buf, target) != BUILD_OK) break;
+ snprintf(leaf, sizeof leaf, "trace-%llu.kpkg", (unsigned long long)i);
+ if (path_join2(out_path, sizeof out_path, tmp, leaf) != BUILD_OK) break;
+ toks.target = target_buf;
+ toks.out = out_path;
+ if (trace_render_argv(c->ctx, r->fetch_argv_template, &toks, &argv) !=
+ BUILD_OK) {
+ build_diagf(c->ctx, "trace remote pull: bad argv template");
+ continue;
+ }
+ if (run_fetch(c->host.exec, argv.argv, argv.argc, str_slice(tmp)) !=
+ BUILD_OK) {
+ rendered_argv_free(c->ctx, &argv);
+ continue;
+ }
+ rendered_argv_free(c->ctx, &argv);
+ if (read_file(c, out_path, &fd) != BUILD_OK) continue;
+ memset(&iopts, 0, sizeof iopts);
+ memset(&ires, 0, sizeof ires);
+ iopts.pkg_data = fd.data;
+ iopts.pkg_len = fd.size;
+ iopts.format = KIT_PKG_FORMAT_KPKG;
+ iopts.trusted_keys = r->trusted_keys.data;
+ iopts.trusted_keys_len = r->trusted_keys.len;
+ iopts.tofu = r->tofu;
+ if (build_bundle_import(c, &iopts, &ires) == BUILD_OK && ires.n_traces) {
+ build_coord_stat_bump(c, BUILD_STAT_TRACE_PULL);
+ release_file(c, &fd);
+ ok = BUILD_OK;
+ break;
+ }
+ release_file(c, &fd);
+ }
+ (void)remove_path(c, tmp, 1);
+ return ok;
}
diff --git a/src/build/coord.c b/src/build/coord.c
@@ -4,13 +4,46 @@
#include <stdio.h>
#include <string.h>
+#include <stdlib.h>
struct BuildTargetFuture {
+ char target[BUILD_TARGET_MAX];
+ uint8_t config_id[BUILD_HASH_LEN];
+ uint8_t argv_id[BUILD_HASH_LEN];
int done;
int failed;
BuildResolved result;
+ struct BuildTargetFuture* next;
};
+struct BuildTargetTable {
+ BuildTargetFuture* futures;
+};
+
+struct BuildPulledSet {
+ char target[BUILD_TARGET_MAX];
+ struct BuildPulledSet* next;
+};
+
+typedef struct BuildDeepSetMemo {
+ BuildLeafSet leaf;
+ BuildSourceLeaf* sources;
+ BuildGlobLeaf* globs;
+ const BuildLeafSet** children;
+ int valid_known;
+ int valid;
+ struct BuildDeepSetMemo* next;
+} BuildDeepSetMemo;
+
+typedef struct BuildGlobExpand {
+ KitBuildCoordinator* c;
+ KitSlice pattern;
+ BuildPathBlob* entries;
+ size_t n;
+ size_t cap;
+ int failed;
+} BuildGlobExpand;
+
static int path_set(char* out, size_t cap, KitSlice s) {
if (!out || cap == 0u || !s.s || s.len + 1u > cap) return BUILD_ERR;
memcpy(out, s.s, s.len);
@@ -51,6 +84,108 @@ static int rel_path_safe(KitSlice s) {
return 1;
}
+static int glob_pattern_safe(KitSlice s) {
+ size_t i, start = 0;
+ if (!s.s || s.len == 0u || s.len >= BUILD_PATTERN_MAX) return 0;
+ if (s.s[0] == '/') return 0;
+ for (i = 0; i <= s.len; ++i) {
+ if (i == s.len || s.s[i] == '/') {
+ size_t n = i - start;
+ if (n == 0u) return 0;
+ if (n == 1u && s.s[start] == '.') return 0;
+ if (n == 2u && s.s[start] == '.' && s.s[start + 1u] == '.') return 0;
+ start = i + 1u;
+ } else if (s.s[i] == '\0' || s.s[i] == '\\' || s.s[i] == ':' ||
+ s.s[i] == '[' || s.s[i] == ']') {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int glob_match_range(const char* pat, size_t pn, const char* text,
+ size_t tn) {
+ size_t pi = 0, ti = 0;
+ size_t star = (size_t)-1, mark = 0;
+ while (ti < tn) {
+ if (pi < pn && pat[pi] == '*') {
+ star = pi++;
+ mark = ti;
+ } else if (pi < pn && (pat[pi] == '?' || pat[pi] == text[ti])) {
+ ++pi;
+ ++ti;
+ } else if (star != (size_t)-1) {
+ pi = star + 1u;
+ ti = ++mark;
+ } else {
+ return 0;
+ }
+ }
+ while (pi < pn && pat[pi] == '*') ++pi;
+ return pi == pn;
+}
+
+static int glob_match_path(KitSlice pattern, const char* path) {
+ size_t pp = 0, tp = 0, tlen;
+ if (!pattern.s || !path) return 0;
+ tlen = strlen(path);
+ for (;;) {
+ size_t pe = pp, te = tp;
+ while (pe < pattern.len && pattern.s[pe] != '/') ++pe;
+ while (te < tlen && path[te] != '/') ++te;
+ if (!glob_match_range(pattern.s + pp, pe - pp, path + tp, te - tp))
+ return 0;
+ if (pe == pattern.len || te == tlen) return pe == pattern.len && te == tlen;
+ pp = pe + 1u;
+ tp = te + 1u;
+ }
+}
+
+static int path_blob_cmp_qsort(const void* a, const void* b) {
+ const BuildPathBlob* pa = (const BuildPathBlob*)a;
+ const BuildPathBlob* pb = (const BuildPathBlob*)b;
+ return strcmp(pa->path, pb->path);
+}
+
+static int glob_entries_push(BuildGlobExpand* g, const char* rel,
+ const uint8_t blob[BUILD_HASH_LEN]) {
+ BuildPathBlob* next;
+ size_t new_cap;
+ if (!g || !rel || strlen(rel) >= BUILD_PATH_MAX) return BUILD_ERR;
+ if (g->n == g->cap) {
+ new_cap = g->cap ? g->cap * 2u : 16u;
+ next = (BuildPathBlob*)g->c->ctx->heap->realloc(
+ g->c->ctx->heap, g->entries, g->cap * sizeof *g->entries,
+ new_cap * sizeof *g->entries, _Alignof(BuildPathBlob));
+ if (!next) return BUILD_ERR;
+ g->entries = next;
+ g->cap = new_cap;
+ }
+ snprintf(g->entries[g->n].path, sizeof g->entries[g->n].path, "%s", rel);
+ memcpy(g->entries[g->n].blob, blob, BUILD_HASH_LEN);
+ ++g->n;
+ return BUILD_OK;
+}
+
+static int glob_walk_cb(void* user, const char* source_path,
+ const char* tree_path, int executable) {
+ BuildGlobExpand* g = (BuildGlobExpand*)user;
+ uint8_t blob[BUILD_HASH_LEN];
+ int present = 0;
+ (void)source_path;
+ (void)executable;
+ if (!g || !tree_path) return 1;
+ if (!glob_match_path(g->pattern, tree_path)) return 0;
+ if (build_coord_source_hash(g->c, kit_slice_cstr(tree_path), blob,
+ &present) != BUILD_OK ||
+ !present ||
+ glob_entries_push(g, tree_path, blob) != BUILD_OK) {
+ g->failed = 1;
+ return 1;
+ }
+ return 0;
+}
+
static size_t count_targets(const uint8_t* data, size_t len) {
size_t i, n = 0;
static const char marker[] = "[target ";
@@ -72,6 +207,54 @@ static void release_defn(KitBuildCoordinator* c) {
c->defn_bytes.token = NULL;
}
+static void free_deepsets(KitBuildCoordinator* c) {
+ BuildDeepSetMemo* n;
+ if (!c || !c->ctx || !c->ctx->heap) return;
+ n = c->deepsets;
+ while (n) {
+ BuildDeepSetMemo* next = n->next;
+ if (n->sources)
+ c->ctx->heap->free(c->ctx->heap, n->sources,
+ n->leaf.n_sources * sizeof *n->sources);
+ if (n->globs)
+ c->ctx->heap->free(c->ctx->heap, n->globs,
+ n->leaf.n_globs * sizeof *n->globs);
+ if (n->children)
+ c->ctx->heap->free(c->ctx->heap, n->children,
+ n->leaf.n_children * sizeof *n->children);
+ c->ctx->heap->free(c->ctx->heap, n, sizeof *n);
+ n = next;
+ }
+ c->deepsets = NULL;
+}
+
+static void free_targets(KitBuildCoordinator* c) {
+ BuildTargetFuture* f;
+ BuildTargetTable* t;
+ if (!c || !c->ctx || !c->ctx->heap || !c->targets) return;
+ t = c->targets;
+ f = t->futures;
+ while (f) {
+ BuildTargetFuture* next = f->next;
+ c->ctx->heap->free(c->ctx->heap, f, sizeof *f);
+ f = next;
+ }
+ c->ctx->heap->free(c->ctx->heap, t, sizeof *t);
+ c->targets = NULL;
+}
+
+static void free_pulled(KitBuildCoordinator* c) {
+ BuildPulledSet* n;
+ if (!c || !c->ctx || !c->ctx->heap) return;
+ n = c->pulled;
+ while (n) {
+ BuildPulledSet* next = n->next;
+ c->ctx->heap->free(c->ctx->heap, n, sizeof *n);
+ n = next;
+ }
+ c->pulled = NULL;
+}
+
void build_coord_stat_bump(KitBuildCoordinator* c, BuildStatField f) {
if (!c) return;
switch (f) {
@@ -192,6 +375,9 @@ void build_coord_close(KitBuildCoordinator* c) {
KitHeap* h;
if (!c) return;
h = c->ctx->heap;
+ free_targets(c);
+ free_pulled(c);
+ free_deepsets(c);
release_defn(c);
if (c->defn.targets)
h->free(h, c->defn.targets,
@@ -233,11 +419,44 @@ int build_coord_source_hash(KitBuildCoordinator* c, KitSlice path,
int build_coord_glob(KitBuildCoordinator* c, KitSlice pattern,
uint8_t out_result_hash[BUILD_HASH_LEN],
BuildCoordGlobFn cb, void* cb_user) {
- (void)c;
- (void)pattern;
- (void)out_result_hash;
- (void)cb;
- (void)cb_user;
+ BuildGlobExpand g;
+ size_t i, out_n = 0;
+ if (!c || !out_result_hash || !glob_pattern_safe(pattern) ||
+ !c->host.cas_host || !c->host.cas_host->walk_regular_files)
+ return BUILD_ERR;
+ memset(&g, 0, sizeof g);
+ g.c = c;
+ g.pattern = pattern;
+ if (c->host.cas_host->walk_regular_files(c->host.cas_host->user,
+ c->workspace_root, glob_walk_cb,
+ &g) != 0 ||
+ g.failed)
+ goto err;
+ if (g.n) qsort(g.entries, g.n, sizeof *g.entries, path_blob_cmp_qsort);
+ for (i = 0; i < g.n; ++i) {
+ if (out_n && strcmp(g.entries[out_n - 1u].path, g.entries[i].path) == 0) {
+ if (!build_id_eq(g.entries[out_n - 1u].blob, g.entries[i].blob))
+ goto err;
+ continue;
+ }
+ if (out_n != i) g.entries[out_n] = g.entries[i];
+ ++out_n;
+ }
+ if (build_glob_result_hash(c->ctx->heap, g.entries, out_n,
+ out_result_hash) != BUILD_OK)
+ goto err;
+ if (cb) {
+ for (i = 0; i < out_n; ++i) {
+ if (cb(cb_user, g.entries[i].path, g.entries[i].blob)) break;
+ }
+ }
+ if (g.entries)
+ c->ctx->heap->free(c->ctx->heap, g.entries, g.cap * sizeof *g.entries);
+ return BUILD_OK;
+
+err:
+ if (g.entries)
+ c->ctx->heap->free(c->ctx->heap, g.entries, g.cap * sizeof *g.entries);
return BUILD_ERR;
}
@@ -302,48 +521,225 @@ int build_coord_recipe_id(KitBuildCoordinator* c, KitSlice target,
int build_coord_leafset_intern(KitBuildCoordinator* c, const BuildLeafSet* in,
const BuildLeafSet** out) {
- BuildLeafSet* copy;
+ BuildDeepSetMemo* n;
if (!c || !in || !out) return BUILD_ERR;
- copy = (BuildLeafSet*)c->ctx->heap->alloc(c->ctx->heap, sizeof *copy,
- _Alignof(BuildLeafSet));
- if (!copy) return BUILD_ERR;
- *copy = *in;
- *out = copy;
+ for (n = c->deepsets; n; n = n->next) {
+ if (build_id_eq(n->leaf.id, in->id)) {
+ *out = &n->leaf;
+ return BUILD_OK;
+ }
+ }
+ n = (BuildDeepSetMemo*)c->ctx->heap->alloc(c->ctx->heap, sizeof *n,
+ _Alignof(BuildDeepSetMemo));
+ if (!n) return BUILD_ERR;
+ memset(n, 0, sizeof *n);
+ n->leaf = *in;
+ if (in->n_sources) {
+ n->sources = (BuildSourceLeaf*)c->ctx->heap->alloc(
+ c->ctx->heap, in->n_sources * sizeof *n->sources,
+ _Alignof(BuildSourceLeaf));
+ if (!n->sources) goto oom;
+ memcpy(n->sources, in->sources, in->n_sources * sizeof *n->sources);
+ }
+ if (in->n_globs) {
+ n->globs = (BuildGlobLeaf*)c->ctx->heap->alloc(
+ c->ctx->heap, in->n_globs * sizeof *n->globs, _Alignof(BuildGlobLeaf));
+ if (!n->globs) goto oom;
+ memcpy(n->globs, in->globs, in->n_globs * sizeof *n->globs);
+ }
+ if (in->n_children) {
+ n->children = (const BuildLeafSet**)c->ctx->heap->alloc(
+ c->ctx->heap, in->n_children * sizeof *n->children,
+ _Alignof(const BuildLeafSet*));
+ if (!n->children) goto oom;
+ memcpy(n->children, in->children, in->n_children * sizeof *n->children);
+ }
+ n->leaf.sources = n->sources;
+ n->leaf.globs = n->globs;
+ n->leaf.children = n->children;
+ n->next = c->deepsets;
+ c->deepsets = n;
+ *out = &n->leaf;
return BUILD_OK;
+
+oom:
+ if (n->sources)
+ c->ctx->heap->free(c->ctx->heap, n->sources,
+ in->n_sources * sizeof *n->sources);
+ if (n->globs)
+ c->ctx->heap->free(c->ctx->heap, n->globs,
+ in->n_globs * sizeof *n->globs);
+ if (n->children)
+ c->ctx->heap->free(c->ctx->heap, n->children,
+ in->n_children * sizeof *n->children);
+ c->ctx->heap->free(c->ctx->heap, n, sizeof *n);
+ return BUILD_ERR;
+}
+
+static void count_deepset_rows(const uint8_t* data, size_t len, size_t* ns,
+ size_t* ng, size_t* nc) {
+ size_t pos = 0;
+ int sec = 0;
+ *ns = 0;
+ *ng = 0;
+ *nc = 0;
+ while (pos < len) {
+ size_t start = pos;
+ while (pos < len && data[pos] != '\n') ++pos;
+ if (pos == len) break;
+ if (pos > start) {
+ size_t n = pos - start;
+ if (n == sizeof("[source]") - 1u &&
+ memcmp(data + start, "[source]", n) == 0) {
+ sec = 1;
+ } else if (n == sizeof("[glob]") - 1u &&
+ memcmp(data + start, "[glob]", n) == 0) {
+ sec = 2;
+ } else if (n == sizeof("[child]") - 1u &&
+ memcmp(data + start, "[child]", n) == 0) {
+ sec = 3;
+ } else if (data[start] != '[' && data[start] != 'k') {
+ if (sec == 1) ++*ns;
+ if (sec == 2) ++*ng;
+ if (sec == 3) ++*nc;
+ }
+ }
+ ++pos;
+ }
}
int build_coord_deepset_load(KitBuildCoordinator* c,
const uint8_t deepset_id[BUILD_HASH_LEN],
const BuildLeafSet** out) {
- (void)c;
- (void)deepset_id;
- (void)out;
- return BUILD_ERR;
+ BuildDeepSetMemo* memo;
+ KitFileData fd;
+ BuildDeepSet ds;
+ BuildLeafSet leaf;
+ BuildSourceLeaf* sources = NULL;
+ BuildGlobLeaf* globs = NULL;
+ uint8_t(*child_ids)[BUILD_HASH_LEN] = NULL;
+ const BuildLeafSet** children = NULL;
+ size_t ns, ng, nc, i;
+ int ok = BUILD_ERR;
+ if (!c || !deepset_id || !out) return BUILD_ERR;
+ for (memo = c->deepsets; memo; memo = memo->next) {
+ if (build_id_eq(memo->leaf.id, deepset_id)) {
+ *out = &memo->leaf;
+ return BUILD_OK;
+ }
+ }
+ fd.data = NULL;
+ fd.size = 0;
+ fd.token = NULL;
+ if (kit_cas_get_blob(c->cas, deepset_id, &fd) != KIT_OK) return BUILD_ERR;
+ count_deepset_rows(fd.data, fd.size, &ns, &ng, &nc);
+ if (ns) {
+ sources = (BuildSourceLeaf*)c->ctx->heap->alloc(
+ c->ctx->heap, ns * sizeof *sources, _Alignof(BuildSourceLeaf));
+ if (!sources) goto out_release;
+ }
+ if (ng) {
+ globs = (BuildGlobLeaf*)c->ctx->heap->alloc(
+ c->ctx->heap, ng * sizeof *globs, _Alignof(BuildGlobLeaf));
+ if (!globs) goto out_release;
+ }
+ if (nc) {
+ child_ids = (uint8_t(*)[BUILD_HASH_LEN])c->ctx->heap->alloc(
+ c->ctx->heap, nc * sizeof *child_ids, _Alignof(uint8_t));
+ children = (const BuildLeafSet**)c->ctx->heap->alloc(
+ c->ctx->heap, nc * sizeof *children, _Alignof(const BuildLeafSet*));
+ if (!child_ids || !children) goto out_release;
+ }
+ memset(&ds, 0, sizeof ds);
+ ds.sources = sources;
+ ds.cap_sources = ns;
+ ds.globs = globs;
+ ds.cap_globs = ng;
+ ds.children = child_ids;
+ ds.cap_children = nc;
+ if (build_deepset_parse(fd.data, fd.size, &ds, NULL, 0) != BUILD_OK)
+ goto out_release;
+ for (i = 0; i < ds.n_children; ++i) {
+ if (build_coord_deepset_load(c, ds.children[i], &children[i]) != BUILD_OK)
+ goto out_release;
+ }
+ memset(&leaf, 0, sizeof leaf);
+ memcpy(leaf.id, deepset_id, BUILD_HASH_LEN);
+ memcpy(leaf.recipe, ds.recipe, BUILD_HASH_LEN);
+ snprintf(leaf.target, sizeof leaf.target, "%s", ds.target);
+ leaf.sources = sources;
+ leaf.n_sources = ds.n_sources;
+ leaf.globs = globs;
+ leaf.n_globs = ds.n_globs;
+ leaf.children = children;
+ leaf.n_children = ds.n_children;
+ if (build_coord_leafset_intern(c, &leaf, out) != BUILD_OK) goto out_release;
+ ok = BUILD_OK;
+
+out_release:
+ kit_cas_release(c->cas, &fd);
+ if (sources)
+ c->ctx->heap->free(c->ctx->heap, sources, ns * sizeof *sources);
+ if (globs) c->ctx->heap->free(c->ctx->heap, globs, ng * sizeof *globs);
+ if (child_ids)
+ c->ctx->heap->free(c->ctx->heap, child_ids, nc * sizeof *child_ids);
+ if (children)
+ c->ctx->heap->free(c->ctx->heap, children, nc * sizeof *children);
+ return ok;
}
int build_coord_deepset_valid_get(KitBuildCoordinator* c,
const uint8_t deepset_id[BUILD_HASH_LEN],
int* known, int* valid) {
- (void)c;
- (void)deepset_id;
+ BuildDeepSetMemo* n;
if (known) *known = 0;
if (valid) *valid = 0;
+ if (!c || !deepset_id) return BUILD_ERR;
+ for (n = c->deepsets; n; n = n->next) {
+ if (build_id_eq(n->leaf.id, deepset_id)) {
+ if (known) *known = n->valid_known;
+ if (valid) *valid = n->valid;
+ return BUILD_OK;
+ }
+ }
return BUILD_OK;
}
void build_coord_deepset_valid_set(KitBuildCoordinator* c,
const uint8_t deepset_id[BUILD_HASH_LEN],
int valid) {
- (void)c;
- (void)deepset_id;
- (void)valid;
+ BuildDeepSetMemo* n;
+ if (!c || !deepset_id) return;
+ for (n = c->deepsets; n; n = n->next) {
+ if (build_id_eq(n->leaf.id, deepset_id)) {
+ n->valid_known = 1;
+ n->valid = valid ? 1 : 0;
+ return;
+ }
+ }
}
int build_coord_trace_remote_pull_once(KitBuildCoordinator* c, KitSlice target,
int* pulled_now) {
+ BuildPulledSet* n;
if (!c || !pulled_now) return BUILD_ERR;
*pulled_now = 0;
if (!c->opts.n_trace_remotes) return BUILD_OK;
+ if (!target.s || target.len == 0u || target.len >= BUILD_TARGET_MAX)
+ return BUILD_ERR;
+ for (n = c->pulled; n; n = n->next) {
+ if (strlen(n->target) == target.len &&
+ memcmp(n->target, target.s, target.len) == 0)
+ return BUILD_OK;
+ }
+ n = (BuildPulledSet*)c->ctx->heap->alloc(c->ctx->heap, sizeof *n,
+ _Alignof(BuildPulledSet));
+ if (!n) return BUILD_ERR;
+ memset(n, 0, sizeof *n);
+ memcpy(n->target, target.s, target.len);
+ n->target[target.len] = '\0';
+ n->next = c->pulled;
+ c->pulled = n;
if (build_trace_remote_pull(c, target) == BUILD_OK) {
*pulled_now = 1;
build_coord_stat_bump(c, BUILD_STAT_TRACE_PULL);
@@ -367,15 +763,38 @@ int build_coord_target_intern(KitBuildCoordinator* c, KitSlice target,
const uint8_t config_id[BUILD_HASH_LEN],
const uint8_t argv_id[BUILD_HASH_LEN],
BuildTargetFuture** out, int* is_fresh) {
+ BuildTargetTable* t;
BuildTargetFuture* f;
- (void)target;
- (void)config_id;
- (void)argv_id;
- if (!c || !out || !is_fresh) return BUILD_ERR;
+ if (!c || !out || !is_fresh || !target.s || target.len == 0u ||
+ target.len >= BUILD_TARGET_MAX || !config_id || !argv_id)
+ return BUILD_ERR;
+ if (!c->targets) {
+ c->targets = (BuildTargetTable*)c->ctx->heap->alloc(
+ c->ctx->heap, sizeof *c->targets, _Alignof(BuildTargetTable));
+ if (!c->targets) return BUILD_ERR;
+ memset(c->targets, 0, sizeof *c->targets);
+ }
+ t = c->targets;
+ for (f = t->futures; f; f = f->next) {
+ if (strlen(f->target) == target.len &&
+ memcmp(f->target, target.s, target.len) == 0 &&
+ build_id_eq(f->config_id, config_id) &&
+ build_id_eq(f->argv_id, argv_id)) {
+ *out = f;
+ *is_fresh = 0;
+ return BUILD_OK;
+ }
+ }
f = (BuildTargetFuture*)c->ctx->heap->alloc(c->ctx->heap, sizeof *f,
_Alignof(BuildTargetFuture));
if (!f) return BUILD_ERR;
memset(f, 0, sizeof *f);
+ memcpy(f->target, target.s, target.len);
+ f->target[target.len] = '\0';
+ memcpy(f->config_id, config_id, BUILD_HASH_LEN);
+ memcpy(f->argv_id, argv_id, BUILD_HASH_LEN);
+ f->next = t->futures;
+ t->futures = f;
*out = f;
*is_fresh = 1;
return BUILD_OK;
diff --git a/src/build/resolve.c b/src/build/resolve.c
@@ -1,6 +1,7 @@
#include "resolve.h"
#include "remote.h"
+#include "runner.h"
#include <stdio.h>
#include <string.h>
@@ -28,6 +29,120 @@ static int target_copy(KitSlice target, char out[BUILD_TARGET_MAX]) {
return BUILD_OK;
}
+static size_t count_lines(const uint8_t* data, size_t len) {
+ size_t i, n = 0;
+ for (i = 0; i < len; ++i)
+ if (data[i] == '\n') ++n;
+ return n;
+}
+
+static int config_values_match(const BuildConfig* a, const BuildConfig* b,
+ const char* key) {
+ KitSlice av, bv;
+ int ap = 0, bp = 0;
+ if (build_config_get(a, kit_slice_cstr(key), &av, &ap) != BUILD_OK ||
+ build_config_get(b, kit_slice_cstr(key), &bv, &bp) != BUILD_OK)
+ return 0;
+ if (ap != bp) return 0;
+ if (!ap) return 1;
+ return kit_slice_eq(av, bv) ? 1 : 0;
+}
+
+static int shallow_direct_match(KitBuildCoordinator* c,
+ const BuildShallowTrace* st,
+ const BuildConfig* cfg,
+ const uint8_t argv_id[BUILD_HASH_LEN]) {
+ BuildConfigEntry old_entries[128];
+ BuildConfig old_cfg;
+ uint8_t recipe[BUILD_HASH_LEN];
+ size_t i;
+ build_config_init(&old_cfg, old_entries,
+ sizeof old_entries / sizeof old_entries[0]);
+ if (!build_id_eq(st->argv, argv_id)) return 0;
+ if (build_coord_recipe_id(c, kit_slice_cstr(st->target), recipe) !=
+ BUILD_OK ||
+ !build_id_eq(recipe, st->recipe))
+ return 0;
+ if (build_coord_config_by_id(c, st->config, &old_cfg) != BUILD_OK) return 0;
+ for (i = 0; i < st->n_config_keys; ++i) {
+ if (!config_values_match(cfg, &old_cfg, st->config_keys[i].name)) return 0;
+ }
+ for (i = 0; i < st->n_sources; ++i) {
+ uint8_t blob[BUILD_HASH_LEN];
+ int present = 0;
+ if (build_coord_source_hash(c, kit_slice_cstr(st->sources[i].path), blob,
+ &present) != BUILD_OK)
+ return 0;
+ if (st->sources[i].absent) {
+ if (present) return 0;
+ } else if (!present || !build_id_eq(blob, st->sources[i].blob)) {
+ return 0;
+ }
+ }
+ for (i = 0; i < st->n_globs; ++i) {
+ uint8_t hash[BUILD_HASH_LEN];
+ if (build_coord_glob(c, kit_slice_cstr(st->globs[i].pattern), hash, NULL,
+ NULL) != BUILD_OK)
+ return 0;
+ if (!build_id_eq(hash, st->globs[i].result_hash)) return 0;
+ }
+ return 1;
+}
+
+static int try_shallow_trace(KitBuildCoordinator* c, KitSlice target,
+ const BuildShallowTrace* st,
+ const BuildConfig* cfg, const BuildArgv* argv,
+ const uint8_t argv_id[BUILD_HASH_LEN],
+ const BuildChainFrame* chain,
+ BuildResolved* out) {
+ BuildDepEdge deps_copy[128];
+ const BuildLeafSet* child_leafsets[128];
+ BuildDepLog log;
+ size_t i;
+ if (!shallow_direct_match(c, st, cfg, argv_id)) return BUILD_ERR;
+ if (st->n_deps > sizeof deps_copy / sizeof deps_copy[0] ||
+ st->n_deps > sizeof child_leafsets / sizeof child_leafsets[0])
+ return BUILD_ERR;
+ for (i = 0; i < st->n_deps; ++i) {
+ BuildConfigEntry dep_cfg_entries[128];
+ char dep_argv_entries[64][BUILD_VAL_MAX];
+ BuildConfig dep_cfg;
+ BuildArgv dep_argv;
+ BuildResolved dep_r;
+ build_config_init(&dep_cfg, dep_cfg_entries,
+ sizeof dep_cfg_entries / sizeof dep_cfg_entries[0]);
+ build_argv_init(&dep_argv, dep_argv_entries,
+ sizeof dep_argv_entries / sizeof dep_argv_entries[0]);
+ if (build_coord_config_by_id(c, st->deps[i].config_id, &dep_cfg) !=
+ BUILD_OK ||
+ build_coord_argv_by_id(c, st->deps[i].argv_id, &dep_argv) !=
+ BUILD_OK ||
+ build_resolve(c, kit_slice_cstr(st->deps[i].name), &dep_cfg, &dep_argv,
+ chain, &dep_r) != BUILD_OK ||
+ !build_id_eq(dep_r.output_tree, st->deps[i].output_tree) ||
+ !dep_r.leafset)
+ return BUILD_ERR;
+ deps_copy[i] = st->deps[i];
+ child_leafsets[i] = dep_r.leafset;
+ }
+ if (build_materialize(c, st->output, out->path, sizeof out->path) != BUILD_OK)
+ return BUILD_ERR;
+ memcpy(out->output_tree, st->output, BUILD_HASH_LEN);
+ memset(&log, 0, sizeof log);
+ log.config_keys = st->config_keys;
+ log.n_config_keys = st->n_config_keys;
+ log.sources = st->sources;
+ log.n_sources = st->n_sources;
+ log.globs = st->globs;
+ log.n_globs = st->n_globs;
+ log.deps = deps_copy;
+ log.n_deps = st->n_deps;
+ log.child_leafsets = child_leafsets;
+ log.n_children = st->n_deps;
+ return build_runner_record_traces(c, target, cfg, argv, &log,
+ out->output_tree, &out->leafset);
+}
+
static int chain_has(const BuildChainFrame* f, KitSlice target,
const uint8_t config_id[BUILD_HASH_LEN],
const uint8_t argv_id[BUILD_HASH_LEN]) {
@@ -95,10 +210,13 @@ int build_resolve(KitBuildCoordinator* c, KitSlice target, const BuildConfig* cf
const BuildArgv* argv, const BuildChainFrame* chain,
BuildResolved* out) {
uint8_t config_id[BUILD_HASH_LEN], argv_id[BUILD_HASH_LEN];
+ uint8_t target_key[BUILD_HASH_LEN];
const BuildChainFrame* child;
+ BuildRecordRow rows[2u * KIT_BUILD_RECORD_CAP];
+ BuildTargetRecord rec;
+ size_t i;
char err[128];
- (void)out;
- if (!c || !cfg || !argv) return BUILD_ERR;
+ if (!c || !cfg || !argv || !out) return BUILD_ERR;
if (build_config_id(c->ctx->heap, cfg, config_id) != BUILD_OK ||
build_argv_id(c->ctx->heap, argv, argv_id) != BUILD_OK)
return BUILD_ERR;
@@ -107,9 +225,82 @@ int build_resolve(KitBuildCoordinator* c, KitSlice target, const BuildConfig* cf
build_diagf(c->ctx, "build: %s", err);
return BUILD_ERR;
}
- (void)child;
- build_diagf(c->ctx, "build: resolver/runner is not implemented yet");
- return BUILD_ERR;
+ if (build_target_key(target, target_key) == BUILD_OK) {
+ memset(&rec, 0, sizeof rec);
+ rec.rows = rows;
+ rec.cap_rows = sizeof rows / sizeof rows[0];
+ if (build_store_record_load(&c->store, target_key, target, &rec) ==
+ BUILD_OK) {
+ for (i = 0; i < rec.n_rows; ++i) {
+ KitFileData fd;
+ BuildDeepTrace dt;
+ const BuildLeafSet* leaf = NULL;
+ int match = 0;
+ if (rec.rows[i].kind != (uint8_t)BUILD_TRACE_DEEP) continue;
+ fd.data = NULL;
+ fd.size = 0;
+ fd.token = NULL;
+ if (build_store_get_trace(&c->store, rec.rows[i].trace_id, &fd) !=
+ BUILD_OK)
+ continue;
+ memset(&dt, 0, sizeof dt);
+ if (build_deep_parse(fd.data, fd.size, &dt, NULL, 0) == BUILD_OK &&
+ strlen(dt.target) == target.len &&
+ memcmp(dt.target, target.s, target.len) == 0 &&
+ build_id_eq(dt.root_config, config_id) &&
+ build_id_eq(dt.argv, argv_id) &&
+ build_coord_deepset_load(c, dt.deepset, &leaf) == BUILD_OK &&
+ build_leafset_refresh(c, leaf, &match) == BUILD_OK && match &&
+ build_materialize(c, dt.output, out->path, sizeof out->path) ==
+ BUILD_OK) {
+ memcpy(out->output_tree, dt.output, BUILD_HASH_LEN);
+ out->leafset = leaf;
+ build_store_release(&c->store, &fd);
+ build_coord_stat_bump(c, BUILD_STAT_DEEP_HIT);
+ return BUILD_OK;
+ }
+ build_store_release(&c->store, &fd);
+ }
+ for (i = 0; i < rec.n_rows; ++i) {
+ KitFileData fd;
+ BuildConfigKey keys[128];
+ BuildSourceLeaf sources[128];
+ BuildGlobLeaf globs[128];
+ BuildDepEdge deps[128];
+ BuildShallowTrace st;
+ size_t rows;
+ if (rec.rows[i].kind != (uint8_t)BUILD_TRACE_SHALLOW) continue;
+ fd.data = NULL;
+ fd.size = 0;
+ fd.token = NULL;
+ if (build_store_get_trace(&c->store, rec.rows[i].trace_id, &fd) !=
+ BUILD_OK)
+ continue;
+ rows = count_lines(fd.data, fd.size);
+ memset(&st, 0, sizeof st);
+ st.config_keys = keys;
+ st.cap_config_keys = sizeof keys / sizeof keys[0];
+ st.sources = sources;
+ st.cap_sources = sizeof sources / sizeof sources[0];
+ st.globs = globs;
+ st.cap_globs = sizeof globs / sizeof globs[0];
+ st.deps = deps;
+ st.cap_deps = sizeof deps / sizeof deps[0];
+ if (rows <= sizeof keys / sizeof keys[0] &&
+ build_shallow_parse(fd.data, fd.size, &st, NULL, 0) == BUILD_OK &&
+ strlen(st.target) == target.len &&
+ memcmp(st.target, target.s, target.len) == 0 &&
+ try_shallow_trace(c, target, &st, cfg, argv, argv_id, child, out) ==
+ BUILD_OK) {
+ build_store_release(&c->store, &fd);
+ build_coord_stat_bump(c, BUILD_STAT_SHALLOW_HIT);
+ return BUILD_OK;
+ }
+ build_store_release(&c->store, &fd);
+ }
+ }
+ }
+ return build_run_recipe(c, target, cfg, argv, child, out);
}
int build_dispatch(KitBuildCoordinator* c, KitSlice target,
@@ -117,36 +308,147 @@ int build_dispatch(KitBuildCoordinator* c, KitSlice target,
const BuildChainFrame* chain,
BuildTargetFuture** out_future, char* err, size_t errcap) {
uint8_t config_id[BUILD_HASH_LEN], argv_id[BUILD_HASH_LEN];
+ const BuildChainFrame* child;
+ BuildResolved r;
int fresh;
- (void)chain;
if (!c || !cfg || !argv || !out_future) return BUILD_ERR;
if (build_config_id(c->ctx->heap, cfg, config_id) != BUILD_OK ||
build_argv_id(c->ctx->heap, argv, argv_id) != BUILD_OK)
return BUILD_ERR;
+ if (build_chain_extend(c, chain, target, config_id, argv_id, &child, err,
+ errcap) != BUILD_OK)
+ return BUILD_ERR;
if (build_coord_target_intern(c, target, config_id, argv_id, out_future,
&fresh) != BUILD_OK)
return BUILD_ERR;
- (void)fresh;
+ if (!fresh) return BUILD_OK;
+ memset(&r, 0, sizeof r);
+ if (build_resolve(c, target, cfg, argv, chain, &r) == BUILD_OK) {
+ (void)child;
+ build_coord_target_complete(c, *out_future, &r);
+ return BUILD_OK;
+ }
build_coord_target_fail(c, *out_future);
- if (err && errcap) snprintf(err, errcap, "resolver/runner is not implemented");
+ if (err && errcap) snprintf(err, errcap, "build dispatch failed");
return BUILD_ERR;
}
int build_leafset_union(KitBuildCoordinator* c, const BuildLeafSet* direct,
const BuildLeafSet* const* children, size_t nchildren,
const BuildLeafSet** out) {
- (void)c;
- (void)direct;
- (void)children;
- (void)nchildren;
- (void)out;
- return BUILD_ERR;
+ BuildDeepSet ds;
+ BuildLeafSet leaf;
+ uint8_t(*child_ids)[BUILD_HASH_LEN] = NULL;
+ const BuildLeafSet** child_ptrs = NULL;
+ KitWriter* w = NULL;
+ const uint8_t* bytes;
+ size_t len, i;
+ KitBlobInfo bi;
+ char err[128];
+ int ok = BUILD_ERR;
+ if (!c || !direct || !out) return BUILD_ERR;
+ if (nchildren && !children) return BUILD_ERR;
+ if (nchildren) {
+ child_ids = (uint8_t(*)[BUILD_HASH_LEN])c->ctx->heap->alloc(
+ c->ctx->heap, nchildren * sizeof *child_ids, _Alignof(uint8_t));
+ child_ptrs = (const BuildLeafSet**)c->ctx->heap->alloc(
+ c->ctx->heap, nchildren * sizeof *child_ptrs,
+ _Alignof(const BuildLeafSet*));
+ if (!child_ids || !child_ptrs) goto out;
+ for (i = 0; i < nchildren; ++i) {
+ if (!children[i]) goto out;
+ memcpy(child_ids[i], children[i]->id, BUILD_HASH_LEN);
+ child_ptrs[i] = children[i];
+ }
+ }
+ memset(&ds, 0, sizeof ds);
+ snprintf(ds.target, sizeof ds.target, "%s", direct->target);
+ memcpy(ds.recipe, direct->recipe, BUILD_HASH_LEN);
+ ds.sources = direct->sources;
+ ds.n_sources = direct->n_sources;
+ ds.globs = direct->globs;
+ ds.n_globs = direct->n_globs;
+ ds.children = child_ids;
+ ds.n_children = nchildren;
+ if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) goto out;
+ if (build_deepset_emit(&ds, w, err, sizeof err) != BUILD_OK ||
+ kit_writer_status(w) != KIT_OK)
+ goto out;
+ bytes = kit_writer_mem_bytes(w, &len);
+ build_deepset_id(bytes, len, leaf.id);
+ if (kit_cas_add_blob(c->cas, bytes, len, &bi) != KIT_OK ||
+ !build_id_eq(bi.id, leaf.id))
+ goto out;
+ memset(&leaf, 0, sizeof leaf);
+ build_deepset_id(bytes, len, leaf.id);
+ snprintf(leaf.target, sizeof leaf.target, "%s", direct->target);
+ memcpy(leaf.recipe, direct->recipe, BUILD_HASH_LEN);
+ leaf.sources = direct->sources;
+ leaf.n_sources = direct->n_sources;
+ leaf.globs = direct->globs;
+ leaf.n_globs = direct->n_globs;
+ leaf.children = child_ptrs;
+ leaf.n_children = nchildren;
+ if (build_coord_leafset_intern(c, &leaf, out) != BUILD_OK) goto out;
+ ok = BUILD_OK;
+out:
+ if (w) kit_writer_close(w);
+ if (child_ids)
+ c->ctx->heap->free(c->ctx->heap, child_ids,
+ nchildren * sizeof *child_ids);
+ if (child_ptrs)
+ c->ctx->heap->free(c->ctx->heap, child_ptrs,
+ nchildren * sizeof *child_ptrs);
+ return ok;
}
int build_leafset_refresh(KitBuildCoordinator* c, const BuildLeafSet* leafset,
int* all_match) {
- (void)c;
- (void)leafset;
- if (all_match) *all_match = 0;
+ size_t i;
+ KitSlice target;
+ uint8_t recipe[BUILD_HASH_LEN];
+ int known = 0, valid = 0;
+ if (!c || !leafset || !all_match) return BUILD_ERR;
+ *all_match = 0;
+ if (build_coord_deepset_valid_get(c, leafset->id, &known, &valid) !=
+ BUILD_OK)
+ return BUILD_ERR;
+ if (known) {
+ *all_match = valid;
+ return BUILD_OK;
+ }
+ target = kit_slice_cstr(leafset->target);
+ if (build_coord_recipe_id(c, target, recipe) != BUILD_OK ||
+ !build_id_eq(recipe, leafset->recipe))
+ goto done;
+ for (i = 0; i < leafset->n_sources; ++i) {
+ uint8_t blob[BUILD_HASH_LEN];
+ int present = 0;
+ if (build_coord_source_hash(c, kit_slice_cstr(leafset->sources[i].path),
+ blob, &present) != BUILD_OK)
+ return BUILD_ERR;
+ if (leafset->sources[i].absent) {
+ if (present) goto done;
+ } else if (!present || !build_id_eq(blob, leafset->sources[i].blob)) {
+ goto done;
+ }
+ }
+ for (i = 0; i < leafset->n_globs; ++i) {
+ uint8_t hash[BUILD_HASH_LEN];
+ if (build_coord_glob(c, kit_slice_cstr(leafset->globs[i].pattern), hash,
+ NULL, NULL) != BUILD_OK)
+ return BUILD_ERR;
+ if (!build_id_eq(hash, leafset->globs[i].result_hash)) goto done;
+ }
+ for (i = 0; i < leafset->n_children; ++i) {
+ int child_match = 0;
+ if (build_leafset_refresh(c, leafset->children[i], &child_match) !=
+ BUILD_OK)
+ return BUILD_ERR;
+ if (!child_match) goto done;
+ }
+ *all_match = 1;
+done:
+ build_coord_deepset_valid_set(c, leafset->id, *all_match);
return BUILD_OK;
}
diff --git a/src/build/runner.c b/src/build/runner.c
@@ -1,27 +1,451 @@
#include "runner.h"
+#include "protocol.h"
+
+#include <stdio.h>
+#include <string.h>
+
+static int path_join2(char* out, size_t cap, const char* a, const char* b) {
+ size_t na, nb;
+ int need_sep;
+ if (!out || cap == 0u || !a || !b) return BUILD_ERR;
+ na = strlen(a);
+ nb = strlen(b);
+ need_sep = na > 0u && a[na - 1u] != '/';
+ if (na + (need_sep ? 1u : 0u) + nb + 1u > cap) return BUILD_ERR;
+ memcpy(out, a, na);
+ if (need_sep) out[na++] = '/';
+ memcpy(out + na, b, nb);
+ out[na + nb] = '\0';
+ return BUILD_OK;
+}
+
+static int target_copy(KitSlice target, char out[BUILD_TARGET_MAX]) {
+ if (!target.s || target.len == 0u || target.len >= BUILD_TARGET_MAX)
+ return BUILD_ERR;
+ memcpy(out, target.s, target.len);
+ out[target.len] = '\0';
+ return BUILD_OK;
+}
+
+static int append_config_key(BuildDepLog* log, KitSlice key) {
+ BuildConfigKey* row;
+ if (!log || !key.s || key.len == 0u || key.len >= BUILD_KEY_MAX)
+ return BUILD_ERR;
+ if (log->n_config_keys >= log->cap_config_keys) return BUILD_ERR;
+ row = &log->config_keys[log->n_config_keys++];
+ memcpy(row->name, key.s, key.len);
+ row->name[key.len] = '\0';
+ return BUILD_OK;
+}
+
+static int append_source(BuildDepLog* log, KitSlice path,
+ const uint8_t blob[BUILD_HASH_LEN], int present) {
+ BuildSourceLeaf* row;
+ if (!log || !path.s || path.len == 0u || path.len >= BUILD_PATH_MAX)
+ return BUILD_ERR;
+ if (log->n_sources >= log->cap_sources) return BUILD_ERR;
+ row = &log->sources[log->n_sources++];
+ memcpy(row->path, path.s, path.len);
+ row->path[path.len] = '\0';
+ row->absent = present ? 0 : 1;
+ if (present)
+ memcpy(row->blob, blob, BUILD_HASH_LEN);
+ else
+ memset(row->blob, 0, BUILD_HASH_LEN);
+ return BUILD_OK;
+}
+
+static int append_glob(BuildDepLog* log, KitSlice pattern,
+ const uint8_t result_hash[BUILD_HASH_LEN]) {
+ BuildGlobLeaf* row;
+ if (!log || !pattern.s || pattern.len == 0u ||
+ pattern.len >= BUILD_PATTERN_MAX || !result_hash)
+ return BUILD_ERR;
+ if (log->n_globs >= log->cap_globs) return BUILD_ERR;
+ row = &log->globs[log->n_globs++];
+ memcpy(row->pattern, pattern.s, pattern.len);
+ row->pattern[pattern.len] = '\0';
+ memcpy(row->result_hash, result_hash, BUILD_HASH_LEN);
+ return BUILD_OK;
+}
+
+static int append_dep(BuildDepLog* log, KitSlice target,
+ const uint8_t config_id[BUILD_HASH_LEN],
+ const uint8_t argv_id[BUILD_HASH_LEN],
+ const BuildResolved* r) {
+ BuildDepEdge* row;
+ if (!log || !target.s || target.len == 0u || target.len >= BUILD_TARGET_MAX ||
+ !config_id || !argv_id || !r || !r->leafset)
+ return BUILD_ERR;
+ if (log->n_deps >= log->cap_deps ||
+ log->n_children >= log->cap_children)
+ return BUILD_ERR;
+ row = &log->deps[log->n_deps++];
+ memcpy(row->name, target.s, target.len);
+ row->name[target.len] = '\0';
+ memcpy(row->config_id, config_id, BUILD_HASH_LEN);
+ memcpy(row->argv_id, argv_id, BUILD_HASH_LEN);
+ memcpy(row->output_tree, r->output_tree, BUILD_HASH_LEN);
+ log->child_leafsets[log->n_children++] = r->leafset;
+ return BUILD_OK;
+}
+
+static int write_resp(KitBuildCoordinator* c, KitBuildConn* conn,
+ const BuildReq* req, const BuildResp* resp);
+
+typedef struct BuildGlobStream {
+ KitBuildCoordinator* c;
+ KitBuildConn* conn;
+ BuildReq req;
+ int failed;
+} BuildGlobStream;
+
+static int glob_stream_cb(void* user, const char* path,
+ const uint8_t blob[BUILD_HASH_LEN]) {
+ BuildGlobStream* s = (BuildGlobStream*)user;
+ BuildResp resp;
+ (void)blob;
+ if (!s || !path) return 1;
+ memset(&resp, 0, sizeof resp);
+ resp.status = BUILD_RESP_OK;
+ resp.text = kit_slice_cstr(path);
+ if (write_resp(s->c, s->conn, &s->req, &resp) != BUILD_OK) {
+ s->failed = 1;
+ return 1;
+ }
+ return 0;
+}
+
+static BuildPendingNeed* find_pending(BuildDepLog* log, uint64_t token) {
+ size_t i;
+ if (!log) return NULL;
+ for (i = 0; i < log->n_pending; ++i)
+ if (log->pending[i].token == token) return &log->pending[i];
+ return NULL;
+}
+
+static int write_resp(KitBuildCoordinator* c, KitBuildConn* conn,
+ const BuildReq* req, const BuildResp* resp) {
+ uint8_t frame[BUILD_FRAME_MAX];
+ size_t n = 0;
+ if (!c || !conn || !req || !resp) return BUILD_ERR;
+ if (build_proto_encode_resp(req, resp, frame, sizeof frame, &n) != BUILD_OK)
+ return BUILD_ERR;
+ return c->host.transport->write_frame(c->host.transport->user, conn, frame,
+ n) == 0
+ ? BUILD_OK
+ : BUILD_ERR;
+}
+
+static void resp_error(BuildResp* resp, KitStatus st, const char* msg) {
+ memset(resp, 0, sizeof *resp);
+ resp->status = BUILD_RESP_ERROR;
+ resp->error_status = (uint16_t)st;
+ resp->text = kit_slice_cstr(msg ? msg : "build request failed");
+}
+
+static int store_config_blob(KitBuildCoordinator* c, const BuildConfig* cfg,
+ uint8_t out[BUILD_HASH_LEN]) {
+ KitWriter* w = NULL;
+ const uint8_t* bytes;
+ size_t len;
+ KitBlobInfo bi;
+ int ok = BUILD_ERR;
+ if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR;
+ if (build_config_emit(cfg, w) != BUILD_OK || kit_writer_status(w) != KIT_OK)
+ goto out_close;
+ bytes = kit_writer_mem_bytes(w, &len);
+ if (kit_cas_add_blob(c->cas, bytes, len, &bi) != KIT_OK) goto out_close;
+ memcpy(out, bi.id, BUILD_HASH_LEN);
+ ok = BUILD_OK;
+out_close:
+ kit_writer_close(w);
+ return ok;
+}
+
+static int store_argv_blob(KitBuildCoordinator* c, const BuildArgv* argv,
+ uint8_t out[BUILD_HASH_LEN]) {
+ KitWriter* w = NULL;
+ const uint8_t* bytes;
+ size_t len;
+ KitBlobInfo bi;
+ int ok = BUILD_ERR;
+ if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR;
+ if (build_argv_emit(argv, w) != BUILD_OK || kit_writer_status(w) != KIT_OK)
+ goto out_close;
+ bytes = kit_writer_mem_bytes(w, &len);
+ if (kit_cas_add_blob(c->cas, bytes, len, &bi) != KIT_OK) goto out_close;
+ memcpy(out, bi.id, BUILD_HASH_LEN);
+ ok = BUILD_OK;
+out_close:
+ kit_writer_close(w);
+ return ok;
+}
+
int build_run_recipe(KitBuildCoordinator* c, KitSlice target,
const BuildConfig* cfg, const BuildArgv* argv,
const BuildChainFrame* chain, BuildResolved* out) {
- (void)target;
- (void)cfg;
- (void)argv;
- (void)chain;
- (void)out;
- if (c) build_diagf(c->ctx, "build: recipe runner is not implemented yet");
- return BUILD_ERR;
+ const BuildTargetDefn* defn;
+ KitBuildListener* listener = NULL;
+ KitBuildConn* conn = NULL;
+ KitBuildProc* proc = NULL;
+ char endpoint[BUILD_PATH_MAX];
+ char sandbox[BUILD_PATH_MAX];
+ char out_dir[BUILD_PATH_MAX];
+ char recipe_path[BUILD_PATH_MAX];
+ KitSlice proc_argv[129];
+ KitBuildKV env[131];
+ char env_keys[128][BUILD_KEY_MAX];
+ size_t argc = 0, nenv = 0, i;
+ int exit_code = 1;
+ int ok = BUILD_ERR;
+ BuildConfigKey config_keys[128];
+ BuildSourceLeaf sources[128];
+ BuildGlobLeaf globs[128];
+ BuildDepEdge deps[128];
+ const BuildLeafSet* child_leafsets[128];
+ BuildPendingNeed pending[128];
+ BuildDepLog log;
+
+ if (!c || !cfg || !argv || !out || !c->host.exec || !c->host.transport)
+ return BUILD_ERR;
+ defn = build_defn_find(&c->defn, target);
+ if (!defn) {
+ build_diagf(c->ctx, "build: unknown target %.*s", KIT_SLICE_ARG(target));
+ return BUILD_ERR;
+ }
+ if (path_join2(recipe_path, sizeof recipe_path, c->workspace_root,
+ defn->recipe_path) != BUILD_OK)
+ return BUILD_ERR;
+ if (c->host.transport->listen(c->host.transport->user, endpoint,
+ sizeof endpoint, &listener) != 0 ||
+ !listener)
+ return BUILD_ERR;
+ if (build_store_sandbox_new(&c->store, sandbox, sizeof sandbox, out_dir,
+ sizeof out_dir) != BUILD_OK)
+ goto out_cleanup;
+
+ proc_argv[argc++] = kit_slice_cstr(recipe_path);
+ for (i = 0; i < argv->n && argc < sizeof proc_argv / sizeof proc_argv[0];
+ ++i)
+ proc_argv[argc++] = kit_slice_cstr(argv->args[i]);
+ if (i != argv->n) goto out_cleanup;
+
+ env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_SOCK);
+ env[nenv++].value = kit_slice_cstr(endpoint);
+ env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_OUT);
+ env[nenv++].value = kit_slice_cstr(out_dir);
+ env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_TARGET);
+ env[nenv++].value = target;
+ for (i = 0; i < cfg->n && nenv < sizeof env / sizeof env[0]; ++i) {
+ const char* key = cfg->entries[i].key;
+ size_t prefix_len = sizeof(KIT_BUILD_ENV_PREFIX) - 1u;
+ if (strncmp(key, KIT_BUILD_ENV_PREFIX, prefix_len) != 0) continue;
+ snprintf(env_keys[nenv - 3u], sizeof env_keys[nenv - 3u], "%s",
+ key + prefix_len);
+ env[nenv].key = kit_slice_cstr(env_keys[nenv - 3u]);
+ env[nenv].value = kit_slice_cstr(cfg->entries[i].value);
+ ++nenv;
+ }
+
+ if (c->host.exec->spawn(c->host.exec->user, proc_argv, argc, env, nenv,
+ kit_slice_cstr(c->workspace_root), &proc) != 0 ||
+ !proc)
+ goto out_cleanup;
+ build_coord_stat_bump(c, BUILD_STAT_RECIPE_RUN);
+ if (c->host.transport->accept(c->host.transport->user, listener, &conn) != 0 ||
+ !conn)
+ goto out_kill;
+
+ memset(&log, 0, sizeof log);
+ log.config_keys = config_keys;
+ log.cap_config_keys = sizeof config_keys / sizeof config_keys[0];
+ log.sources = sources;
+ log.cap_sources = sizeof sources / sizeof sources[0];
+ log.globs = globs;
+ log.cap_globs = sizeof globs / sizeof globs[0];
+ log.deps = deps;
+ log.cap_deps = sizeof deps / sizeof deps[0];
+ log.child_leafsets = child_leafsets;
+ log.cap_children = sizeof child_leafsets / sizeof child_leafsets[0];
+ log.pending = pending;
+ log.cap_pending = sizeof pending / sizeof pending[0];
+ log.next_token = 1;
+ if (build_runner_service(c, conn, target, cfg, chain, &log) != BUILD_OK)
+ goto out_kill;
+ c->host.transport->close(c->host.transport->user, conn);
+ conn = NULL;
+ if (c->host.exec->wait(c->host.exec->user, proc, &exit_code) != 0)
+ goto out_cleanup;
+ proc = NULL;
+ if (exit_code != 0) goto out_cleanup;
+ if (build_store_ingest_output(&c->store, out_dir, out->output_tree, out->path,
+ sizeof out->path) != BUILD_OK)
+ goto out_cleanup;
+ if (build_runner_record_traces(c, target, cfg, argv, &log, out->output_tree,
+ &out->leafset) != BUILD_OK)
+ goto out_cleanup;
+ ok = BUILD_OK;
+ goto out_cleanup;
+
+out_kill:
+ if (proc && c->host.exec->kill) c->host.exec->kill(c->host.exec->user, proc);
+ if (proc && c->host.exec->wait)
+ (void)c->host.exec->wait(c->host.exec->user, proc, &exit_code);
+ proc = NULL;
+out_cleanup:
+ if (conn) c->host.transport->close(c->host.transport->user, conn);
+ if (listener)
+ c->host.transport->close_listener(c->host.transport->user, listener);
+ if (sandbox[0]) build_store_sandbox_done(&c->store, sandbox);
+ return ok;
}
int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
KitSlice target, const BuildConfig* cfg,
const BuildChainFrame* chain, BuildDepLog* log) {
- (void)c;
- (void)conn;
+ uint8_t frame[BUILD_FRAME_MAX];
+ size_t n = 0;
(void)target;
- (void)cfg;
- (void)chain;
- (void)log;
- return BUILD_ERR;
+ if (!c || !conn || !cfg || !log) return BUILD_ERR;
+ for (;;) {
+ BuildReq req;
+ KitBuildKV overrides[64];
+ KitSlice argv_slices[64];
+ BuildResp resp;
+ memset(&req, 0, sizeof req);
+ if (c->host.transport->read_frame(c->host.transport->user, conn, frame,
+ sizeof frame, &n) != 0)
+ return BUILD_OK;
+ if (build_proto_decode_req(frame, n, &req, overrides,
+ sizeof overrides / sizeof overrides[0],
+ argv_slices,
+ sizeof argv_slices / sizeof argv_slices[0]) !=
+ BUILD_OK)
+ return BUILD_ERR;
+ memset(&resp, 0, sizeof resp);
+ resp.status = BUILD_RESP_OK;
+ if (req.cmd == BUILD_CMD_CONFIG_GET) {
+ KitSlice value;
+ int present = 0;
+ if (build_config_get(cfg, req.arg, &value, &present) != BUILD_OK ||
+ append_config_key(log, req.arg) != BUILD_OK) {
+ resp_error(&resp, KIT_ERR, "config-get failed");
+ } else if (!present) {
+ resp.status = BUILD_RESP_UNSET;
+ } else {
+ resp.text = value;
+ }
+ if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
+ } else if (req.cmd == BUILD_CMD_SOURCE) {
+ uint8_t blob[BUILD_HASH_LEN];
+ int present = 0;
+ char full[BUILD_PATH_MAX];
+ if (build_coord_source_hash(c, req.arg, blob, &present) != BUILD_OK ||
+ append_source(log, req.arg, blob, present) != BUILD_OK) {
+ resp_error(&resp, KIT_ERR, "source failed");
+ } else if (!present) {
+ resp.status = BUILD_RESP_ABSENT;
+ } else if (path_join2(full, sizeof full, c->workspace_root, req.arg.s) !=
+ BUILD_OK) {
+ resp_error(&resp, KIT_ERR, "source path failed");
+ } else {
+ memcpy(resp.id, blob, BUILD_HASH_LEN);
+ resp.text = kit_slice_cstr(full);
+ }
+ if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
+ } else if (req.cmd == BUILD_CMD_GLOB) {
+ BuildGlobStream stream;
+ uint8_t result_hash[BUILD_HASH_LEN];
+ memset(&stream, 0, sizeof stream);
+ stream.c = c;
+ stream.conn = conn;
+ stream.req = req;
+ if (build_coord_glob(c, req.arg, result_hash, glob_stream_cb,
+ &stream) != BUILD_OK ||
+ stream.failed ||
+ append_glob(log, req.arg, result_hash) != BUILD_OK) {
+ resp_error(&resp, KIT_ERR, "glob failed");
+ if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
+ } else {
+ resp.status = BUILD_RESP_GLOB_END;
+ if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
+ }
+ } else if (req.cmd == BUILD_CMD_NEED) {
+ BuildConfigEntry cfg_entries[128];
+ char argv_entries[64][BUILD_VAL_MAX];
+ BuildConfig dep_cfg;
+ BuildArgv dep_argv;
+ BuildResolved r;
+ uint8_t cfg_id[BUILD_HASH_LEN], argv_id[BUILD_HASH_LEN];
+ build_config_init(&dep_cfg, cfg_entries,
+ sizeof cfg_entries / sizeof cfg_entries[0]);
+ build_argv_init(&dep_argv, argv_entries,
+ sizeof argv_entries / sizeof argv_entries[0]);
+ if (build_config_overlay(cfg, req.overrides, req.noverrides, &dep_cfg) !=
+ BUILD_OK ||
+ build_argv_set(&dep_argv, req.argv, req.argc) != BUILD_OK ||
+ build_config_id(c->ctx->heap, &dep_cfg, cfg_id) != BUILD_OK ||
+ build_argv_id(c->ctx->heap, &dep_argv, argv_id) != BUILD_OK ||
+ build_resolve(c, req.arg, &dep_cfg, &dep_argv, chain, &r) !=
+ BUILD_OK ||
+ append_dep(log, req.arg, cfg_id, argv_id, &r) != BUILD_OK) {
+ resp_error(&resp, KIT_ERR, "need failed");
+ } else {
+ memcpy(resp.id, r.output_tree, BUILD_HASH_LEN);
+ resp.text = kit_slice_cstr(r.path);
+ }
+ if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
+ } else if (req.cmd == BUILD_CMD_NEED_SUBMIT) {
+ BuildConfigEntry cfg_entries[128];
+ char argv_entries[64][BUILD_VAL_MAX];
+ BuildConfig dep_cfg;
+ BuildArgv dep_argv;
+ BuildTargetFuture* f = NULL;
+ BuildPendingNeed* p;
+ uint8_t cfg_id[BUILD_HASH_LEN], argv_id[BUILD_HASH_LEN];
+ char err[128];
+ build_config_init(&dep_cfg, cfg_entries,
+ sizeof cfg_entries / sizeof cfg_entries[0]);
+ build_argv_init(&dep_argv, argv_entries,
+ sizeof argv_entries / sizeof argv_entries[0]);
+ if (log->n_pending >= log->cap_pending ||
+ build_config_overlay(cfg, req.overrides, req.noverrides, &dep_cfg) !=
+ BUILD_OK ||
+ build_argv_set(&dep_argv, req.argv, req.argc) != BUILD_OK ||
+ build_config_id(c->ctx->heap, &dep_cfg, cfg_id) != BUILD_OK ||
+ build_argv_id(c->ctx->heap, &dep_argv, argv_id) != BUILD_OK ||
+ build_dispatch(c, req.arg, &dep_cfg, &dep_argv, chain, &f, err,
+ sizeof err) != BUILD_OK) {
+ resp_error(&resp, KIT_ERR, "need-submit failed");
+ } else {
+ p = &log->pending[log->n_pending++];
+ memset(p, 0, sizeof *p);
+ p->token = log->next_token++;
+ target_copy(req.arg, p->dep);
+ memcpy(p->config_id, cfg_id, BUILD_HASH_LEN);
+ memcpy(p->argv_id, argv_id, BUILD_HASH_LEN);
+ p->future = f;
+ resp.token = p->token;
+ }
+ if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
+ } else if (req.cmd == BUILD_CMD_NEED_AWAIT) {
+ BuildPendingNeed* p = find_pending(log, req.token);
+ BuildResolved r;
+ if (!p || build_coord_target_await(c, p->future, &r) != BUILD_OK ||
+ append_dep(log, kit_slice_cstr(p->dep), p->config_id, p->argv_id,
+ &r) != BUILD_OK) {
+ resp_error(&resp, KIT_ERR, "need-await failed");
+ } else {
+ memcpy(resp.id, r.output_tree, BUILD_HASH_LEN);
+ resp.text = kit_slice_cstr(r.path);
+ }
+ if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
+ } else {
+ return BUILD_ERR;
+ }
+ }
}
int build_runner_record_traces(KitBuildCoordinator* c, KitSlice target,
@@ -29,12 +453,82 @@ int build_runner_record_traces(KitBuildCoordinator* c, KitSlice target,
const BuildDepLog* log,
const uint8_t output[BUILD_HASH_LEN],
const BuildLeafSet** out_leafset) {
- (void)c;
- (void)target;
- (void)cfg;
- (void)argv;
- (void)log;
- (void)output;
- (void)out_leafset;
- return BUILD_ERR;
+ BuildLeafSet direct;
+ BuildShallowTrace shallow;
+ BuildDeepTrace deep;
+ KitWriter* w = NULL;
+ const uint8_t* bytes;
+ size_t len;
+ uint8_t trace_id[BUILD_HASH_LEN];
+ uint8_t target_key[BUILD_HASH_LEN];
+ char err[128];
+ int ok = BUILD_ERR;
+
+ if (!c || !cfg || !argv || !log || !output || !out_leafset)
+ return BUILD_ERR;
+ memset(&direct, 0, sizeof direct);
+ if (target_copy(target, direct.target) != BUILD_OK) return BUILD_ERR;
+ if (build_coord_recipe_id(c, target, direct.recipe) != BUILD_OK)
+ return BUILD_ERR;
+ direct.sources = log->sources;
+ direct.n_sources = log->n_sources;
+ direct.globs = log->globs;
+ direct.n_globs = log->n_globs;
+ direct.children = log->child_leafsets;
+ direct.n_children = log->n_children;
+ if (build_leafset_union(c, &direct, log->child_leafsets, log->n_children,
+ out_leafset) != BUILD_OK)
+ return BUILD_ERR;
+
+ memset(&shallow, 0, sizeof shallow);
+ if (target_copy(target, shallow.target) != BUILD_OK) return BUILD_ERR;
+ memcpy(shallow.recipe, direct.recipe, BUILD_HASH_LEN);
+ memcpy(shallow.output, output, BUILD_HASH_LEN);
+ if (store_config_blob(c, cfg, shallow.config) != BUILD_OK ||
+ store_argv_blob(c, argv, shallow.argv) != BUILD_OK)
+ return BUILD_ERR;
+ shallow.config_keys = log->config_keys;
+ shallow.n_config_keys = log->n_config_keys;
+ shallow.sources = log->sources;
+ shallow.n_sources = log->n_sources;
+ shallow.globs = log->globs;
+ shallow.n_globs = log->n_globs;
+ shallow.deps = log->deps;
+ shallow.n_deps = log->n_deps;
+
+ if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR;
+ if (build_shallow_emit(&shallow, w, err, sizeof err) != BUILD_OK ||
+ kit_writer_status(w) != KIT_OK)
+ goto out;
+ bytes = kit_writer_mem_bytes(w, &len);
+ if (build_store_put_trace(&c->store, bytes, len, trace_id) != BUILD_OK)
+ goto out;
+ if (build_target_key(target, target_key) != BUILD_OK ||
+ build_store_record_update(&c->store, target_key, target,
+ BUILD_TRACE_SHALLOW, trace_id) != BUILD_OK)
+ goto out;
+ kit_writer_close(w);
+ w = NULL;
+
+ memset(&deep, 0, sizeof deep);
+ if (target_copy(target, deep.target) != BUILD_OK) return BUILD_ERR;
+ memcpy(deep.recipe, direct.recipe, BUILD_HASH_LEN);
+ memcpy(deep.output, output, BUILD_HASH_LEN);
+ memcpy(deep.root_config, shallow.config, BUILD_HASH_LEN);
+ memcpy(deep.argv, shallow.argv, BUILD_HASH_LEN);
+ memcpy(deep.deepset, (*out_leafset)->id, BUILD_HASH_LEN);
+ if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR;
+ if (build_deep_emit(&deep, w, err, sizeof err) != BUILD_OK ||
+ kit_writer_status(w) != KIT_OK)
+ goto out;
+ bytes = kit_writer_mem_bytes(w, &len);
+ if (build_store_put_trace(&c->store, bytes, len, trace_id) != BUILD_OK)
+ goto out;
+ if (build_store_record_update(&c->store, target_key, target, BUILD_TRACE_DEEP,
+ trace_id) != BUILD_OK)
+ goto out;
+ ok = BUILD_OK;
+out:
+ if (w) kit_writer_close(w);
+ return ok;
}
diff --git a/src/core/config_assert.c b/src/core/config_assert.c
@@ -45,6 +45,7 @@ KIT_ASSERT_BOOL(KIT_DBG_ENABLED);
KIT_ASSERT_BOOL(KIT_EMU_ENABLED);
KIT_ASSERT_BOOL(KIT_CAS_ENABLED);
KIT_ASSERT_BOOL(KIT_PKG_ENABLED);
+KIT_ASSERT_BOOL(KIT_BUILD_ENABLED);
KIT_ASSERT_BOOL(KIT_COMPRESS_ENABLED);
KIT_ASSERT_BOOL(KIT_GRAM_ENABLED);
KIT_ASSERT_BOOL(KIT_INTERP_ENABLED);
@@ -55,6 +56,7 @@ KIT_ASSERT_BOOL(KIT_TOOL_CHECK_ENABLED);
KIT_ASSERT_BOOL(KIT_TOOL_BUILD_EXE_ENABLED);
KIT_ASSERT_BOOL(KIT_TOOL_BUILD_LIB_ENABLED);
KIT_ASSERT_BOOL(KIT_TOOL_BUILD_OBJ_ENABLED);
+KIT_ASSERT_BOOL(KIT_TOOL_BUILD_ENABLED);
KIT_ASSERT_BOOL(KIT_TOOL_INSTALL_ENABLED);
KIT_ASSERT_BOOL(KIT_TOOL_CPP_ENABLED);
KIT_ASSERT_BOOL(KIT_TOOL_AS_ENABLED);
@@ -103,6 +105,8 @@ _Static_assert(!KIT_INTERP_ENABLED || KIT_OPT_ENABLED,
"KIT_INTERP_ENABLED requires KIT_OPT_ENABLED");
_Static_assert(!KIT_PKG_ENABLED || KIT_CAS_ENABLED,
"KIT_PKG_ENABLED requires KIT_CAS_ENABLED");
+_Static_assert(!KIT_BUILD_ENABLED || KIT_CAS_ENABLED,
+ "KIT_BUILD_ENABLED requires KIT_CAS_ENABLED");
_Static_assert(!KIT_TOOL_CC_ENABLED ||
(KIT_LANG_C_ENABLED && KIT_LINK_ENABLED && KIT_AR_ENABLED),
@@ -120,6 +124,8 @@ _Static_assert(!KIT_TOOL_BUILD_LIB_ENABLED ||
_Static_assert(!KIT_TOOL_BUILD_OBJ_ENABLED ||
(KIT_LINK_ENABLED && KIT_AR_ENABLED),
"KIT_TOOL_BUILD_OBJ_ENABLED requires link and ar support");
+_Static_assert(!KIT_TOOL_BUILD_ENABLED || KIT_BUILD_ENABLED,
+ "KIT_TOOL_BUILD_ENABLED requires build coordinator support");
_Static_assert(!KIT_TOOL_CPP_ENABLED || KIT_LANG_CPP_ENABLED,
"KIT_TOOL_CPP_ENABLED requires C preprocessor support");
_Static_assert(!KIT_TOOL_LD_ENABLED || KIT_LINK_ENABLED,
diff --git a/test/build/build_public_link_test.c b/test/build/build_public_link_test.c
@@ -8,6 +8,233 @@ static KitUnit g_u;
#define EXPECT(c, ...) CU_EXPECT(&g_u, c, __VA_ARGS__)
+typedef struct TestConn {
+ int unused;
+} TestConn;
+
+typedef struct TestTransport {
+ TestConn conn;
+ const uint8_t* replies[16];
+ size_t reply_lens[16];
+ size_t nreplies;
+ size_t next_reply;
+ uint8_t writes[16][256];
+ size_t write_lens[16];
+ size_t nwrites;
+ int dialed;
+ int closed;
+} TestTransport;
+
+const char* kit_debug_getenv(const char* name) {
+ if (kit_slice_eq_cstr(kit_slice_cstr(name), KIT_BUILD_ENV_SOCK))
+ return "client-test";
+ return NULL;
+}
+
+static void put_u8(uint8_t* buf, size_t* n, uint8_t v) { buf[(*n)++] = v; }
+
+static void put_u16(uint8_t* buf, size_t* n, uint16_t v) {
+ put_u8(buf, n, (uint8_t)(v & 0xffu));
+ put_u8(buf, n, (uint8_t)((v >> 8) & 0xffu));
+}
+
+static void put_u64(uint8_t* buf, size_t* n, uint64_t v) {
+ size_t i;
+ for (i = 0; i < 8u; ++i) put_u8(buf, n, (uint8_t)(v >> (8u * i)));
+}
+
+static void put_bytes(uint8_t* buf, size_t* n, const void* data, size_t len) {
+ if (len) memcpy(buf + *n, data, len);
+ *n += len;
+}
+
+static void put_slice(uint8_t* buf, size_t* n, KitSlice s) {
+ put_u16(buf, n, (uint16_t)s.len);
+ put_bytes(buf, n, s.data, s.len);
+}
+
+static void fill_id(uint8_t id[KIT_BUILD_HASH_LEN], uint8_t seed) {
+ size_t i;
+ for (i = 0; i < KIT_BUILD_HASH_LEN; ++i) id[i] = (uint8_t)(seed + i);
+}
+
+static size_t resp_config(uint8_t* buf, KitSlice value) {
+ size_t n = 0;
+ put_u8(buf, &n, 0);
+ put_slice(buf, &n, value);
+ return n;
+}
+
+static size_t resp_source(uint8_t* buf, uint8_t seed, KitSlice path) {
+ uint8_t id[KIT_BUILD_HASH_LEN];
+ size_t n = 0;
+ fill_id(id, seed);
+ put_u8(buf, &n, 0);
+ put_bytes(buf, &n, id, sizeof id);
+ put_slice(buf, &n, path);
+ return n;
+}
+
+static size_t resp_glob_path(uint8_t* buf, KitSlice path) {
+ size_t n = 0;
+ put_u8(buf, &n, 0);
+ put_u16(buf, &n, 1);
+ put_slice(buf, &n, path);
+ return n;
+}
+
+static size_t resp_status(uint8_t* buf, uint8_t status) {
+ size_t n = 0;
+ put_u8(buf, &n, status);
+ return n;
+}
+
+static size_t resp_need(uint8_t* buf, uint8_t seed, KitSlice path) {
+ uint8_t id[KIT_BUILD_HASH_LEN];
+ size_t n = 0;
+ fill_id(id, seed);
+ put_u8(buf, &n, 0);
+ put_bytes(buf, &n, id, sizeof id);
+ put_slice(buf, &n, path);
+ return n;
+}
+
+static size_t resp_submit(uint8_t* buf, uint64_t token) {
+ size_t n = 0;
+ put_u8(buf, &n, 0);
+ put_u64(buf, &n, token);
+ return n;
+}
+
+static int t_dial(void* user, KitSlice endpoint, KitBuildConn** out) {
+ TestTransport* t = (TestTransport*)user;
+ if (!kit_slice_eq(endpoint, KIT_SLICE_LIT("client-test")) || !out) return 1;
+ t->dialed++;
+ *out = (KitBuildConn*)&t->conn;
+ return 0;
+}
+
+static int t_read(void* user, KitBuildConn* conn, uint8_t* buf, size_t cap,
+ size_t* n) {
+ TestTransport* t = (TestTransport*)user;
+ (void)conn;
+ if (!buf || !n || t->next_reply >= t->nreplies ||
+ t->reply_lens[t->next_reply] > cap)
+ return 1;
+ memcpy(buf, t->replies[t->next_reply], t->reply_lens[t->next_reply]);
+ *n = t->reply_lens[t->next_reply];
+ t->next_reply++;
+ return 0;
+}
+
+static int t_write(void* user, KitBuildConn* conn, const uint8_t* buf,
+ size_t n) {
+ TestTransport* t = (TestTransport*)user;
+ (void)conn;
+ if (!buf || t->nwrites >= 16 || n > sizeof t->writes[0]) return 1;
+ memcpy(t->writes[t->nwrites], buf, n);
+ t->write_lens[t->nwrites] = n;
+ t->nwrites++;
+ return 0;
+}
+
+static void t_close(void* user, KitBuildConn* conn) {
+ TestTransport* t = (TestTransport*)user;
+ (void)conn;
+ t->closed++;
+}
+
+static int glob_count(void* user, KitSlice path) {
+ int* n = (int*)user;
+ if (kit_slice_eq(path, KIT_SLICE_LIT("src/a.c"))) ++*n;
+ return 0;
+}
+
+static void test_client_transport(void) {
+ TestTransport tt;
+ KitBuildTransport tr;
+ KitBuildClient* client = NULL;
+ uint8_t r0[64], r1[128], r2[64], r3[4], r4[128], r5[32], r6[128];
+ KitSlice value;
+ int present = 0;
+ uint8_t blob[KIT_BUILD_HASH_LEN];
+ KitBuildKV kv;
+ KitSlice argv;
+ KitBuildRequest req;
+ KitBuildResult result;
+ KitBuildNeedToken token;
+ int nglob = 0;
+
+ memset(&tt, 0, sizeof tt);
+ memset(&tr, 0, sizeof tr);
+ tr.dial = t_dial;
+ tr.read_frame = t_read;
+ tr.write_frame = t_write;
+ tr.close = t_close;
+ tr.user = &tt;
+ tt.replies[tt.nreplies] = r0;
+ tt.reply_lens[tt.nreplies++] = resp_config(r0, KIT_SLICE_LIT("debug"));
+ tt.replies[tt.nreplies] = r1;
+ tt.reply_lens[tt.nreplies++] =
+ resp_source(r1, 0x10, KIT_SLICE_LIT("/work/src/main.c"));
+ tt.replies[tt.nreplies] = r2;
+ tt.reply_lens[tt.nreplies++] = resp_glob_path(r2, KIT_SLICE_LIT("src/a.c"));
+ tt.replies[tt.nreplies] = r3;
+ tt.reply_lens[tt.nreplies++] = resp_status(r3, 3);
+ tt.replies[tt.nreplies] = r4;
+ tt.reply_lens[tt.nreplies++] =
+ resp_need(r4, 0x20, KIT_SLICE_LIT("/out/lib"));
+ tt.replies[tt.nreplies] = r5;
+ tt.reply_lens[tt.nreplies++] = resp_submit(r5, 42);
+ tt.replies[tt.nreplies] = r6;
+ tt.reply_lens[tt.nreplies++] =
+ resp_need(r6, 0x30, KIT_SLICE_LIT("/out/tool"));
+
+ EXPECT(kit_build_client_open(&g_u.ctx, &tr, &client) == KIT_OK && client &&
+ tt.dialed == 1,
+ "client open dials env endpoint");
+ EXPECT(kit_build_client_config_get(client, KIT_SLICE_LIT("mode"), &value,
+ &present) == KIT_OK &&
+ present && kit_slice_eq(value, KIT_SLICE_LIT("debug")),
+ "client config get");
+ EXPECT(kit_build_client_source(client, KIT_SLICE_LIT("src/main.c"), blob,
+ &value) == KIT_OK &&
+ blob[0] == 0x10 &&
+ kit_slice_eq(value, KIT_SLICE_LIT("/work/src/main.c")),
+ "client source");
+ EXPECT(kit_build_client_glob(client, KIT_SLICE_LIT("src/*.c"), glob_count,
+ &nglob) == KIT_OK &&
+ nglob == 1,
+ "client glob stream");
+
+ kv.key = KIT_SLICE_LIT("opt");
+ kv.value = KIT_SLICE_LIT("2");
+ argv = KIT_SLICE_LIT("--fast");
+ memset(&req, 0, sizeof req);
+ req.target = KIT_SLICE_LIT("//lib:core");
+ req.config = &kv;
+ req.nconfig = 1;
+ req.argv = &argv;
+ req.argc = 1;
+ EXPECT(kit_build_client_need(client, &req, &result) == KIT_OK &&
+ result.output_tree[0] == 0x20 &&
+ strcmp(result.path, "/out/lib") == 0,
+ "client need");
+ EXPECT(kit_build_client_need_submit(client, &req, &token) == KIT_OK &&
+ token.id == 42,
+ "client need submit");
+ EXPECT(kit_build_client_need_await(client, token, &result) == KIT_OK &&
+ result.output_tree[0] == 0x30 &&
+ strcmp(result.path, "/out/tool") == 0,
+ "client need await");
+ EXPECT(tt.nwrites == 6 && tt.writes[0][0] == 1 && tt.writes[1][0] == 2 &&
+ tt.writes[2][0] == 3 && tt.writes[3][0] == 4 &&
+ tt.writes[4][0] == 5 && tt.writes[5][0] == 6,
+ "client request command order");
+ kit_build_client_close(client);
+ EXPECT(tt.closed == 1, "client close");
+}
+
static void test_public_symbols(void) {
KitBuildStats stats;
KitBuildClient* client = (KitBuildClient*)1;
@@ -31,33 +258,34 @@ static void test_public_symbols(void) {
EXPECT(kit_build_traces_import(NULL, NULL, NULL) == KIT_ERR,
"trace import fails explicitly");
- EXPECT(kit_build_client_open(NULL, NULL, &client) == KIT_UNSUPPORTED &&
+ EXPECT(kit_build_client_open(NULL, NULL, &client) == KIT_INVALID &&
client == NULL,
- "client open unsupported");
+ "client open rejects invalid args");
EXPECT(kit_build_client_config_get(NULL, KIT_SLICE_LIT("x"), &value,
- &present) == KIT_UNSUPPORTED &&
+ &present) == KIT_INVALID &&
value.s == NULL && value.len == 0 && present == 0,
- "client config unsupported");
+ "client config rejects invalid args");
EXPECT(kit_build_client_source(NULL, KIT_SLICE_LIT("x"), result.output_tree,
- &value) == KIT_UNSUPPORTED,
- "client source unsupported");
+ &value) == KIT_INVALID,
+ "client source rejects invalid args");
EXPECT(kit_build_client_glob(NULL, KIT_SLICE_LIT("*"), NULL, NULL) ==
- KIT_UNSUPPORTED,
- "client glob unsupported");
- EXPECT(kit_build_client_need(NULL, NULL, &result) == KIT_UNSUPPORTED,
- "client need unsupported");
- EXPECT(kit_build_client_need_submit(NULL, NULL, &token) == KIT_UNSUPPORTED &&
+ KIT_INVALID,
+ "client glob rejects invalid args");
+ EXPECT(kit_build_client_need(NULL, NULL, &result) == KIT_INVALID,
+ "client need rejects invalid args");
+ EXPECT(kit_build_client_need_submit(NULL, NULL, &token) == KIT_INVALID &&
token.id == 0,
- "client need-submit unsupported");
+ "client need-submit rejects invalid args");
token.id = 7;
- EXPECT(kit_build_client_need_await(NULL, token, &result) == KIT_UNSUPPORTED,
- "client need-await unsupported");
+ EXPECT(kit_build_client_need_await(NULL, token, &result) == KIT_INVALID,
+ "client need-await rejects invalid args");
kit_build_client_close(NULL);
}
int main(void) {
kit_unit_init(&g_u);
test_public_symbols();
+ test_client_transport();
kit_unit_summary(&g_u, "build_public_link_test");
return kit_unit_status(&g_u);
}