kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Adriver/cmd/build_coord.c | 411+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/driver.h | 2++
Adriver/env/build_host_posix.c | 466+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/env/build_host_stub.c | 19+++++++++++++++++++
Adriver/env_build.h | 23+++++++++++++++++++++++
Mdriver/main.c | 5+++++
Minclude/kit/config.h | 6++++++
Mmk/driver_srcs.mk | 8+++++++-
Mmk/lib_srcs.mk | 8+++++---
Msrc/api/build_coord.c | 221+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/api/config_stubs.c | 2+-
Msrc/build/bundle.c | 1051++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/build/coord.c | 467++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/build/resolve.c | 336+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/build/runner.c | 536+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/core/config_assert.c | 6++++++
Mtest/build/build_public_link_test.c | 256++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
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); }