commit 22a683dab70880013e144eb59abf7c28003abaf6
parent b81292122a781a186745cc54ba9135ae1b92e006
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 22 Jun 2026 19:30:04 -0700
Complete build coordinator coverage and CLI gaps
Diffstat:
3 files changed, 1116 insertions(+), 18 deletions(-)
diff --git a/driver/cmd/build_coord.c b/driver/cmd/build_coord.c
@@ -5,8 +5,11 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
+#include "../../src/build/defn.h"
+#include "../../src/build/workspace.h"
#include "dist_host.h"
#include "driver.h"
#include "env.h"
@@ -46,6 +49,10 @@ typedef struct BuildCli {
BuildClientFormat format;
} BuildCli;
+typedef struct BuildListTarget {
+ char label[BUILD_TARGET_MAX];
+} BuildListTarget;
+
void driver_help_build(void) {
driver_printf(
"kit build - content-addressed build coordinator\n"
@@ -118,6 +125,302 @@ static int build_join_path(char* out, size_t cap, const char* a,
return 0;
}
+static int build_def_name_valid(const char* s) {
+ size_t i;
+ if (!s || !s[0]) return 0;
+ for (i = 0; s[i]; ++i) {
+ unsigned char c = (unsigned char)s[i];
+ if (c == '/' || c == '\\' || c == ':' || c == '\n' || c == '\r')
+ return 0;
+ }
+ return 1;
+}
+
+static int build_load_file(DriverEnv* env, const char* path, DriverLoad* load,
+ KitSlice* bytes) {
+ return driver_load_bytes(&env->file_io, BUILD_TOOL, path, load, bytes);
+}
+
+static int build_load_workspace(DriverEnv* env, const char* root,
+ BuildWorkspace* ws, int* present) {
+ char path[KIT_BUILD_PATH_MAX];
+ DriverLoad load;
+ KitSlice bytes;
+ char err[256];
+ if (present) *present = 0;
+ if (build_join_path(path, sizeof path, root, "WORKSPACE.kit") != 0) return 1;
+ if (!driver_path_exists(path)) {
+ build_workspace_init(ws);
+ return 0;
+ }
+ if (present) *present = 1;
+ if (build_load_file(env, path, &load, &bytes) != 0) return 1;
+ if (build_workspace_parse(bytes.data, bytes.len, ws, err, sizeof err) !=
+ BUILD_OK) {
+ driver_errf(BUILD_TOOL, "bad WORKSPACE.kit: %s", err);
+ driver_release_bytes(&env->file_io, &load);
+ return 1;
+ }
+ driver_release_bytes(&env->file_io, &load);
+ return 0;
+}
+
+static int build_file_write(DriverEnv* env, const char* path, const char* text) {
+ KitWriter* w = NULL;
+ size_t n;
+ if (!env || !path || !text) return 1;
+ if (env->file_io.open_writer(env->file_io.user, path, &w) != KIT_OK || !w)
+ return 1;
+ n = driver_strlen(text);
+ if ((n && kit_writer_write(w, text, n) != KIT_OK) ||
+ kit_writer_status(w) != KIT_OK) {
+ kit_writer_close(w);
+ return 1;
+ }
+ kit_writer_close(w);
+ return 0;
+}
+
+static int build_file_append(DriverEnv* env, const char* path,
+ const char* text) {
+ DriverLoad load;
+ KitSlice old = KIT_SLICE_NULL;
+ KitWriter* w = NULL;
+ size_t n;
+ int had_old = 0;
+ if (!env || !path || !text) return 1;
+ if (driver_path_exists(path)) {
+ if (build_load_file(env, path, &load, &old) != 0) return 1;
+ had_old = 1;
+ }
+ if (env->file_io.open_writer(env->file_io.user, path, &w) != KIT_OK || !w) {
+ if (had_old) driver_release_bytes(&env->file_io, &load);
+ return 1;
+ }
+ n = driver_strlen(text);
+ if ((old.len && kit_writer_write(w, old.data, old.len) != KIT_OK) ||
+ (n && kit_writer_write(w, text, n) != KIT_OK) ||
+ kit_writer_status(w) != KIT_OK) {
+ kit_writer_close(w);
+ if (had_old) driver_release_bytes(&env->file_io, &load);
+ return 1;
+ }
+ kit_writer_close(w);
+ if (had_old) driver_release_bytes(&env->file_io, &load);
+ return 0;
+}
+
+static int build_file_url_path(const char* url, const char** out) {
+ if (!url || !out || !driver_strneq(url, "file://", 7u)) return 1;
+ *out = url + 7u;
+ return 0;
+}
+
+static int build_list_cmp(const void* a, const void* b) {
+ const BuildListTarget* la = (const BuildListTarget*)a;
+ const BuildListTarget* lb = (const BuildListTarget*)b;
+ return strcmp(la->label, lb->label);
+}
+
+static int build_list_add(BuildListTarget* out, size_t* n, size_t cap,
+ const char* label) {
+ size_t i;
+ if (!out || !n || !label || !label[0]) return 1;
+ for (i = 0; i < *n; ++i)
+ if (driver_streq(out[i].label, label)) return 0;
+ if (*n >= cap || driver_strlen(label) >= sizeof out[0].label) return 1;
+ snprintf(out[*n].label, sizeof out[*n].label, "%s", label);
+ ++*n;
+ return 0;
+}
+
+static void build_pkg_label(char* out, size_t cap, const char* pkg,
+ const char* local) {
+ if (pkg && pkg[0])
+ snprintf(out, cap, "//%s:%s", pkg, local);
+ else
+ snprintf(out, cap, "//:%s", local);
+}
+
+static int build_line_next(KitSlice bytes, size_t* pos, KitSlice* line) {
+ size_t start = *pos;
+ size_t end = start;
+ while (end < bytes.len && bytes.s[end] != '\n') ++end;
+ if (start >= bytes.len) return 1;
+ line->s = bytes.s + start;
+ line->len = end - start;
+ *pos = end < bytes.len ? end + 1u : end;
+ return 0;
+}
+
+static int build_slice_starts(KitSlice s, const char* prefix) {
+ size_t n = driver_strlen(prefix);
+ return s.len >= n && memcmp(s.s, prefix, n) == 0;
+}
+
+static void build_copy_slice0(char* out, size_t cap, KitSlice s) {
+ size_t n = s.len < cap - 1u ? s.len : cap - 1u;
+ if (n) memcpy(out, s.s, n);
+ out[n] = '\0';
+}
+
+static int build_list_generated_from_projection(DriverEnv* env,
+ const char* root,
+ const char* pkg,
+ KitSlice line,
+ BuildListTarget* out,
+ size_t* n, size_t cap) {
+ char pat[BUILD_PATH_MAX], tmpl[BUILD_TARGET_MAX];
+ char dir_rel[BUILD_PATH_MAX], dir_abs[KIT_BUILD_PATH_MAX];
+ char prefix[128], suffix[128], label[BUILD_TARGET_MAX], local[BUILD_TARGET_MAX];
+ DriverDirHandle* dh;
+ size_t i, arrow = (size_t)-1, star = (size_t)-1, slash = (size_t)-1;
+ if (!build_slice_starts(line, "list ")) return 0;
+ line.s += 5u;
+ line.len -= 5u;
+ for (i = 0; i + 3u <= line.len; ++i) {
+ if (line.s[i] == ' ' && line.s[i + 1u] == '-' && line.s[i + 2u] == '>' &&
+ line.s[i + 3u] == ' ') {
+ arrow = i;
+ break;
+ }
+ }
+ if (arrow == (size_t)-1) return 0;
+ build_copy_slice0(pat, sizeof pat, (KitSlice){.s = line.s, .len = arrow});
+ build_copy_slice0(tmpl, sizeof tmpl,
+ (KitSlice){.s = line.s + arrow + 4u,
+ .len = line.len - arrow - 4u});
+ for (i = 0; pat[i]; ++i) {
+ if (pat[i] == '*') star = i;
+ if (pat[i] == '/') slash = i;
+ }
+ if (star == (size_t)-1) return 0;
+ if (slash != (size_t)-1) {
+ snprintf(dir_rel, sizeof dir_rel, "%s/%.*s", pkg,
+ (int)slash, pat);
+ snprintf(prefix, sizeof prefix, "%.*s", (int)(star - slash - 1u),
+ pat + slash + 1u);
+ } else {
+ snprintf(dir_rel, sizeof dir_rel, "%s", pkg);
+ snprintf(prefix, sizeof prefix, "%.*s", (int)star, pat);
+ }
+ snprintf(suffix, sizeof suffix, "%s", pat + star + 1u);
+ if (build_join_path(dir_abs, sizeof dir_abs, root, dir_rel) != 0) return 1;
+ dh = driver_open_dir(env, dir_abs);
+ if (!dh) return 0;
+ for (i = 0;; ++i) {
+ const char* name;
+ uint32_t name_len;
+ uint64_t ino, size, mtime;
+ uint8_t ftype;
+ size_t pn = driver_strlen(prefix), sn = driver_strlen(suffix);
+ char stem[BUILD_TARGET_MAX];
+ if (driver_read_dir_entry(dh, i, &name, &name_len, &ino, &size, &mtime,
+ &ftype) != 0)
+ break;
+ (void)ino;
+ (void)size;
+ (void)mtime;
+ if (ftype != 4u || name_len < pn + sn ||
+ memcmp(name, prefix, pn) != 0 ||
+ (sn && memcmp(name + name_len - sn, suffix, sn) != 0))
+ continue;
+ snprintf(stem, sizeof stem, "%.*s", (int)(name_len - pn - sn),
+ name + pn);
+ if (driver_streq(tmpl, "{stem}.o"))
+ snprintf(local, sizeof local, "%s.o", stem);
+ else
+ snprintf(local, sizeof local, "%s", stem);
+ build_pkg_label(label, sizeof label, pkg, local);
+ if (build_list_add(out, n, cap, label) != 0) {
+ driver_close_dir(env, dh);
+ return 1;
+ }
+ }
+ driver_close_dir(env, dh);
+ return 0;
+}
+
+static int build_list_package(DriverEnv* env, const char* root,
+ const char* def_name, const char* pkg,
+ int recursive, BuildListTarget* out, size_t* n,
+ size_t cap) {
+ char pkg_dir[KIT_BUILD_PATH_MAX], def_path[KIT_BUILD_PATH_MAX];
+ DriverLoad load;
+ KitSlice bytes, line;
+ size_t pos = 0;
+ int in_targets = 0;
+ if (build_join_path(pkg_dir, sizeof pkg_dir, root, pkg) != 0 ||
+ build_join_path(def_path, sizeof def_path, pkg_dir, def_name) != 0)
+ return 1;
+ if (driver_path_exists(def_path) &&
+ build_load_file(env, def_path, &load, &bytes) == 0) {
+ while (build_line_next(bytes, &pos, &line) == 0) {
+ if (build_slice_starts(line, "[target ") && line.len > 9u &&
+ line.s[line.len - 1u] == ']') {
+ char local[BUILD_TARGET_MAX], label[BUILD_TARGET_MAX];
+ build_copy_slice0(local, sizeof local,
+ (KitSlice){.s = line.s + 8u,
+ .len = line.len - 9u});
+ build_pkg_label(label, sizeof label, pkg, local);
+ if (build_list_add(out, n, cap, label) != 0) {
+ driver_release_bytes(&env->file_io, &load);
+ return 1;
+ }
+ in_targets = 0;
+ } else if (build_slice_starts(line, "[targets ")) {
+ in_targets = 1;
+ } else if (line.len && line.s[0] == '[') {
+ in_targets = 0;
+ } else if (in_targets) {
+ if (build_list_generated_from_projection(env, root, pkg, line, out, n,
+ cap) != 0) {
+ driver_release_bytes(&env->file_io, &load);
+ return 1;
+ }
+ }
+ }
+ driver_release_bytes(&env->file_io, &load);
+ }
+ if (recursive) {
+ DriverDirHandle* dh = driver_open_dir(env, pkg_dir);
+ uint64_t idx;
+ if (!dh) return 0;
+ for (idx = 0;; ++idx) {
+ const char* name;
+ uint32_t name_len;
+ uint64_t ino, size, mtime;
+ uint8_t ftype;
+ char child_pkg[BUILD_PATH_MAX], child_dir[KIT_BUILD_PATH_MAX],
+ child_def[KIT_BUILD_PATH_MAX];
+ if (driver_read_dir_entry(dh, idx, &name, &name_len, &ino, &size, &mtime,
+ &ftype) != 0)
+ break;
+ (void)ino;
+ (void)size;
+ (void)mtime;
+ if (ftype != 3u) continue;
+ if (pkg && pkg[0])
+ snprintf(child_pkg, sizeof child_pkg, "%s/%.*s", pkg, (int)name_len,
+ name);
+ else
+ snprintf(child_pkg, sizeof child_pkg, "%.*s", (int)name_len, name);
+ if (build_join_path(child_dir, sizeof child_dir, root, child_pkg) != 0 ||
+ build_join_path(child_def, sizeof child_def, child_dir, def_name) != 0)
+ continue;
+ if (driver_path_exists(child_def)) {
+ if (build_list_package(env, root, def_name, child_pkg, 1, out, n,
+ cap) != 0) {
+ driver_close_dir(env, dh);
+ return 1;
+ }
+ }
+ }
+ driver_close_dir(env, dh);
+ }
+ return 0;
+}
+
static int build_add_config(BuildCli* cli, KitSlice key, KitSlice value) {
if (!cli || !key.s || key.len == 0u || cli->nconfig >= BUILD_MAX_CONFIG)
return 1;
@@ -224,7 +527,8 @@ static void build_client_print_value(BuildClientFormat fmt,
static int build_client_verb(const char* s) {
return driver_streq(s, "config-get") || driver_streq(s, "source") ||
driver_streq(s, "fetch") || driver_streq(s, "depfile") ||
- driver_streq(s, "glob") ||
+ driver_streq(s, "glob") || driver_streq(s, "export-set") ||
+ driver_streq(s, "export-get") || driver_streq(s, "export-collect") ||
driver_streq(s, "need") ||
driver_streq(s, "need-submit") || driver_streq(s, "need-await");
}
@@ -301,6 +605,95 @@ static int build_client_need_parse(BuildCli* cli, int argc, char** argv,
return 0;
}
+static int build_export_set(DriverEnv* env, int argc, char** argv,
+ int first_arg) {
+ const char* out_dir = driver_getenv(KIT_BUILD_ENV_OUT);
+ char kit_dir[KIT_BUILD_PATH_MAX], exports_path[KIT_BUILD_PATH_MAX];
+ size_t i;
+ if (!out_dir || argc < first_arg + 3 ||
+ !driver_streq(argv[first_arg + 1], "--")) {
+ driver_errf(BUILD_TOOL, "usage: kit build export-set VAR -- VALUE...");
+ return 2;
+ }
+ if (build_join_path(kit_dir, sizeof kit_dir, out_dir, ".kit") != 0 ||
+ build_join_path(exports_path, sizeof exports_path, kit_dir, "exports") !=
+ 0) {
+ driver_errf(BUILD_TOOL, "export path too long");
+ return 1;
+ }
+ if (driver_mkdir_p(env, kit_dir) != 0) {
+ driver_errf(BUILD_TOOL, "failed to create exports directory");
+ return 1;
+ }
+ if (!driver_path_exists(exports_path) &&
+ build_file_write(env, exports_path, "kit-build-exports 1\n") != 0) {
+ driver_errf(BUILD_TOOL, "failed to write exports file");
+ return 1;
+ }
+ {
+ char line[BUILD_VAL_MAX + 32u];
+ snprintf(line, sizeof line, "[var %s]\n", argv[first_arg]);
+ if (build_file_append(env, exports_path, line) != 0) {
+ driver_errf(BUILD_TOOL, "failed to append exports variable");
+ return 1;
+ }
+ }
+ for (i = (size_t)first_arg + 2u; i < (size_t)argc; ++i) {
+ char line[BUILD_VAL_MAX + 2u];
+ snprintf(line, sizeof line, "%s\n", argv[i]);
+ if (build_file_append(env, exports_path, line) != 0) {
+ driver_errf(BUILD_TOOL, "failed to append exports value");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int build_exports_print_var(DriverEnv* env, const char* path,
+ const char* var) {
+ DriverLoad load;
+ KitSlice bytes, line;
+ size_t pos = 0;
+ int active = 0, found = 0;
+ if (build_load_file(env, path, &load, &bytes) != 0) return 1;
+ while (build_line_next(bytes, &pos, &line) == 0) {
+ if (line.len >= 6u && build_slice_starts(line, "[var ") &&
+ line.s[line.len - 1u] == ']') {
+ KitSlice name = {.s = line.s + 5u, .len = line.len - 6u};
+ active = name.len == driver_strlen(var) &&
+ memcmp(name.s, var, name.len) == 0;
+ if (active) found = 1;
+ } else if (active && line.len && line.s[0] != '[') {
+ driver_printf("%.*s\n", KIT_SLICE_ARG(line));
+ }
+ }
+ driver_release_bytes(&env->file_io, &load);
+ return found ? 0 : 1;
+}
+
+static int build_export_get_one(DriverEnv* env, KitBuildClient* client,
+ const char* target, const char* var) {
+ KitBuildRequest req;
+ KitBuildResult result;
+ KitStatus st;
+ char exports_path[KIT_BUILD_PATH_MAX], kit_dir[KIT_BUILD_PATH_MAX];
+ memset(&req, 0, sizeof req);
+ req.target = kit_slice_cstr(target);
+ st = kit_build_client_need(client, &req, &result);
+ if (st != KIT_OK) {
+ driver_errf(BUILD_TOOL, "export-get need failed: %s", build_status_name(st));
+ return 1;
+ }
+ if (build_join_path(kit_dir, sizeof kit_dir, result.path, ".kit") != 0 ||
+ build_join_path(exports_path, sizeof exports_path, kit_dir, "exports") !=
+ 0 ||
+ build_exports_print_var(env, exports_path, var) != 0) {
+ driver_errf(BUILD_TOOL, "export variable not found: %s", var);
+ return 1;
+ }
+ return 0;
+}
+
static int build_client_mode(int argc, char** argv, int verb_index) {
DriverEnv env;
DriverBuildHost bh;
@@ -317,6 +710,10 @@ static int build_client_mode(int argc, char** argv, int verb_index) {
verb = argv[verb_index];
driver_env_init(&env);
ctx = driver_env_to_context(&env);
+ if (driver_streq(verb, "export-set")) {
+ rc = build_export_set(&env, argc, argv, first_arg);
+ goto out_env;
+ }
if (driver_build_host_init(&bh, &env) != 0) {
driver_errf(BUILD_TOOL, "hosted build adapter is unavailable");
goto out_env;
@@ -587,6 +984,26 @@ static int build_client_mode(int argc, char** argv, int verb_index) {
build_client_print_value(cli.format, result.output_tree,
kit_slice_cstr(result.path));
rc = 0;
+ } else if (driver_streq(verb, "export-get")) {
+ if (argc != first_arg + 2) {
+ driver_errf(BUILD_TOOL, "usage: kit build export-get TARGET VAR");
+ rc = 2;
+ goto out_client;
+ }
+ rc = build_export_get_one(&env, client, argv[first_arg],
+ argv[first_arg + 1]);
+ } else if (driver_streq(verb, "export-collect")) {
+ int i;
+ if (argc < first_arg + 2) {
+ driver_errf(BUILD_TOOL, "usage: kit build export-collect VAR TARGET...");
+ rc = 2;
+ goto out_client;
+ }
+ rc = 0;
+ for (i = first_arg + 1; i < argc; ++i) {
+ if (build_export_get_one(&env, client, argv[i], argv[first_arg]) != 0)
+ rc = 1;
+ }
} else {
driver_errf(BUILD_TOOL, "unknown client command: %s", verb);
rc = 2;
@@ -613,18 +1030,23 @@ static int build_parse_args(BuildCli* cli, int argc, char** argv, int first_arg,
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) {
+ } else if ((driver_streq(a, "--def") || driver_streq(a, "--def-name")) &&
+ i + 1 < argc) {
cli->def = argv[++i];
} else if (driver_streq(a, "--profile") && i + 1 < argc) {
cli->profile = 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 or env.NAME");
+ const char* opt = argv[++i];
+ if (build_parse_config(cli, opt) != 0) {
+ driver_errf(BUILD_TOOL,
+ "bad --config '%s', expected K=V or env.NAME", opt);
return 2;
}
} else if (driver_streq(a, "--env") && i + 1 < argc) {
- if (build_parse_env(cli, argv[++i]) != 0) {
- driver_errf(BUILD_TOOL, "bad --env, expected NAME or NAME=VALUE");
+ const char* opt = argv[++i];
+ if (build_parse_env(cli, opt) != 0) {
+ driver_errf(BUILD_TOOL, "bad --env '%s', expected NAME or NAME=VALUE",
+ opt);
return 2;
}
} else if (driver_streq(a, "--")) {
@@ -666,6 +1088,10 @@ static int build_parse_args(BuildCli* cli, int argc, char** argv, int first_arg,
driver_errf(BUILD_TOOL, "missing target");
return 2;
}
+ if (cli->def && !build_def_name_valid(cli->def)) {
+ driver_errf(BUILD_TOOL, "bad def-name: %s", cli->def);
+ return 2;
+ }
return 0;
}
@@ -713,6 +1139,304 @@ static int build_verify_tree(DriverEnv* env, const char* store,
return 0;
}
+static int build_find_top_command(int argc, char** argv) {
+ int i;
+ for (i = 1; i < argc; ++i) {
+ if ((driver_streq(argv[i], "--store") || driver_streq(argv[i], "--root") ||
+ driver_streq(argv[i], "--def") || driver_streq(argv[i], "--def-name") ||
+ driver_streq(argv[i], "--profile") || driver_streq(argv[i], "--config") ||
+ driver_streq(argv[i], "--env")) &&
+ i + 1 < argc) {
+ ++i;
+ continue;
+ }
+ if (driver_streq(argv[i], "--stats") || driver_streq(argv[i], "--trace") ||
+ driver_streq(argv[i], "--verify"))
+ continue;
+ if (driver_streq(argv[i], "list") || driver_streq(argv[i], "repo") ||
+ driver_streq(argv[i], "workspace"))
+ return i;
+ return -1;
+ }
+ return -1;
+}
+
+static void build_global_option_defaults(int argc, char** argv, int cmd_index,
+ const char** root,
+ const char** def_name,
+ const char** store) {
+ int i;
+ (void)argc;
+ *root = ".";
+ *def_name = BUILD_DEFAULT_DEF;
+ *store = NULL;
+ for (i = 1; i < cmd_index; ++i) {
+ if (driver_streq(argv[i], "--root") && i + 1 < cmd_index) {
+ *root = argv[++i];
+ } else if ((driver_streq(argv[i], "--def") ||
+ driver_streq(argv[i], "--def-name")) &&
+ i + 1 < cmd_index) {
+ *def_name = argv[++i];
+ } else if (driver_streq(argv[i], "--store") && i + 1 < cmd_index) {
+ *store = argv[++i];
+ } else if ((driver_streq(argv[i], "--profile") ||
+ driver_streq(argv[i], "--config") ||
+ driver_streq(argv[i], "--env")) &&
+ i + 1 < cmd_index) {
+ ++i;
+ }
+ }
+}
+
+static int build_cmd_list(int argc, char** argv, int cmd_index) {
+ DriverEnv env;
+ const char *root, *def_name, *store_ignored;
+ const char* pattern = "//...";
+ int scan_do = 0;
+ int i, recursive = 0;
+ char pkg[BUILD_PATH_MAX];
+ BuildListTarget targets[512];
+ size_t ntargets = 0, j;
+ (void)store_ignored;
+ build_global_option_defaults(argc, argv, cmd_index, &root, &def_name,
+ &store_ignored);
+ if (!build_def_name_valid(def_name)) {
+ driver_errf(BUILD_TOOL, "bad def-name");
+ return 2;
+ }
+ for (i = cmd_index + 1; i < argc; ++i) {
+ if (driver_streq(argv[i], "--scan-do")) {
+ scan_do = 1;
+ } else if (argv[i][0] == '-') {
+ driver_errf(BUILD_TOOL, "unexpected list option: %s", argv[i]);
+ return 2;
+ } else {
+ pattern = argv[i];
+ }
+ }
+ memset(pkg, 0, sizeof pkg);
+ if (!driver_strneq(pattern, "//", 2u)) {
+ driver_errf(BUILD_TOOL, "list pattern must be //pkg or //pkg/...");
+ return 2;
+ }
+ {
+ const char* body = pattern + 2u;
+ size_t len = driver_strlen(body);
+ if (len >= 4u && driver_streq(body + len - 4u, "/...")) {
+ recursive = 1;
+ len -= 4u;
+ }
+ if (len >= sizeof pkg) return 2;
+ memcpy(pkg, body, len);
+ pkg[len] = '\0';
+ }
+ driver_env_init(&env);
+ if (scan_do) {
+ char pkg_dir[KIT_BUILD_PATH_MAX], do_dir[KIT_BUILD_PATH_MAX];
+ DriverDirHandle* dh;
+ if (build_join_path(pkg_dir, sizeof pkg_dir, root, pkg) != 0 ||
+ build_join_path(do_dir, sizeof do_dir, pkg_dir, "do") != 0) {
+ driver_env_fini(&env);
+ return 1;
+ }
+ dh = driver_open_dir(&env, do_dir);
+ if (dh) {
+ uint64_t idx;
+ for (idx = 0;; ++idx) {
+ const char* name;
+ uint32_t name_len;
+ uint64_t ino, size, mtime;
+ uint8_t ftype;
+ if (driver_read_dir_entry(dh, idx, &name, &name_len, &ino, &size,
+ &mtime, &ftype) != 0)
+ break;
+ (void)ino;
+ (void)size;
+ (void)mtime;
+ if (ftype == 4u && name_len > 3u &&
+ memcmp(name + name_len - 3u, ".do", 3u) == 0)
+ driver_printf("//%s:%.*s %.*s\n", pkg, (int)name_len, name,
+ (int)name_len, name);
+ }
+ driver_close_dir(&env, dh);
+ }
+ } else if (build_list_package(&env, root, def_name, pkg, recursive, targets,
+ &ntargets,
+ sizeof targets / sizeof targets[0]) != 0) {
+ driver_env_fini(&env);
+ return 1;
+ }
+ qsort(targets, ntargets, sizeof targets[0], build_list_cmp);
+ for (j = 0; j < ntargets; ++j) driver_printf("%s\n", targets[j].label);
+ driver_env_fini(&env);
+ return 0;
+}
+
+static int build_cmd_repo(int argc, char** argv, int cmd_index) {
+ DriverEnv env;
+ const char *root, *def_name_ignored, *store_ignored;
+ BuildWorkspace ws;
+ int present = 0;
+ size_t i;
+ (void)def_name_ignored;
+ (void)store_ignored;
+ if (argc <= cmd_index + 1) {
+ driver_errf(BUILD_TOOL, "usage: kit build repo {add|fetch|list}");
+ return 2;
+ }
+ build_global_option_defaults(argc, argv, cmd_index, &root, &def_name_ignored,
+ &store_ignored);
+ driver_env_init(&env);
+ if (build_load_workspace(&env, root, &ws, &present) != 0) {
+ driver_env_fini(&env);
+ return 1;
+ }
+ if (driver_streq(argv[cmd_index + 1], "list")) {
+ for (i = 0; i < ws.n_externals; ++i) {
+ char hex[BUILD_HEX_LEN];
+ const char* fmt = ws.externals[i].format == BUILD_WS_EXT_TREE
+ ? "tree"
+ : ws.externals[i].format == BUILD_WS_EXT_TARGZ
+ ? "tar.gz"
+ : "kpkg";
+ kit_hex_encode(hex, ws.externals[i].archive, BUILD_HASH_LEN);
+ driver_printf("%s %s %s\n", ws.externals[i].name, fmt, hex);
+ }
+ } else if (driver_streq(argv[cmd_index + 1], "fetch")) {
+ if (argc <= cmd_index + 2) {
+ driver_errf(BUILD_TOOL, "usage: kit build repo fetch NAME");
+ driver_env_fini(&env);
+ return 2;
+ }
+ if (!build_workspace_external_find(&ws, kit_slice_cstr(argv[cmd_index + 2]))) {
+ driver_errf(BUILD_TOOL, "unknown external repo: %s", argv[cmd_index + 2]);
+ driver_env_fini(&env);
+ return 1;
+ }
+ driver_printf("%s\n", argv[cmd_index + 2]);
+ } else if (driver_streq(argv[cmd_index + 1], "add")) {
+ const char* name;
+ const char* url;
+ const char* src;
+ const char* format = "tar.gz";
+ const char* strip = NULL;
+ char ws_path[KIT_BUILD_PATH_MAX], line[2048], hex[BUILD_HEX_LEN];
+ DriverLoad load;
+ KitSlice bytes;
+ KitBlobInfo bi;
+ int a;
+ if (argc <= cmd_index + 3) {
+ driver_errf(BUILD_TOOL, "usage: kit build repo add NAME URL");
+ driver_env_fini(&env);
+ return 2;
+ }
+ name = argv[cmd_index + 2];
+ url = argv[cmd_index + 3];
+ for (a = cmd_index + 4; a < argc; ++a) {
+ if (driver_streq(argv[a], "--format") && a + 1 < argc) {
+ format = argv[++a];
+ } else if (driver_streq(argv[a], "--strip-prefix") && a + 1 < argc) {
+ strip = argv[++a];
+ }
+ }
+ if (build_file_url_path(url, &src) != 0 ||
+ build_load_file(&env, src, &load, &bytes) != 0) {
+ driver_env_fini(&env);
+ return 1;
+ }
+ kit_blob_info(&bi, bytes.data, bytes.len);
+ driver_release_bytes(&env.file_io, &load);
+ kit_hex_encode(hex, bi.id, BUILD_HASH_LEN);
+ if (build_join_path(ws_path, sizeof ws_path, root, "WORKSPACE.kit") != 0) {
+ driver_env_fini(&env);
+ return 1;
+ }
+ snprintf(line, sizeof line,
+ "\n[external %s]\nformat %s\narchive %s\nurl %s\n", name, format,
+ hex, url);
+ if (build_file_append(&env, ws_path, line) != 0 ||
+ (strip && (snprintf(line, sizeof line, "strip-prefix %s\n", strip),
+ build_file_append(&env, ws_path, line) != 0))) {
+ driver_env_fini(&env);
+ return 1;
+ }
+ driver_printf("%s %s\n", name, hex);
+ } else {
+ driver_errf(BUILD_TOOL, "unknown repo command: %s", argv[cmd_index + 1]);
+ driver_env_fini(&env);
+ return 2;
+ }
+ driver_env_fini(&env);
+ return 0;
+}
+
+static int build_cmd_workspace(int argc, char** argv, int cmd_index) {
+ DriverEnv env;
+ DriverBuildHost bh;
+ const char *root, *def_name_ignored, *store_ignored;
+ const char* out = NULL;
+ const char* format = "tar.gz";
+ const char* tar_paths[] = {"/usr/bin/tar", "/bin/tar"};
+ KitBuildKV envv[1];
+ KitBuildProc* proc = NULL;
+ int exit_code = 127;
+ int ran = 0;
+ int i;
+ (void)def_name_ignored;
+ (void)store_ignored;
+ if (argc <= cmd_index + 1 || !driver_streq(argv[cmd_index + 1], "package")) {
+ driver_errf(BUILD_TOOL, "usage: kit build workspace package -o OUT");
+ return 2;
+ }
+ build_global_option_defaults(argc, argv, cmd_index, &root, &def_name_ignored,
+ &store_ignored);
+ for (i = cmd_index + 2; i < argc; ++i) {
+ if (driver_streq(argv[i], "--format") && i + 1 < argc)
+ format = argv[++i];
+ else if (driver_streq(argv[i], "-o") && i + 1 < argc)
+ out = argv[++i];
+ }
+ if (!out || !driver_streq(format, "tar.gz")) {
+ driver_errf(BUILD_TOOL, "workspace package supports --format tar.gz -o OUT");
+ return 2;
+ }
+ driver_env_init(&env);
+ if (driver_build_host_init(&bh, &env) != 0) {
+ driver_env_fini(&env);
+ return 1;
+ }
+ envv[0].key = kit_slice_cstr("COPYFILE_DISABLE");
+ envv[0].value = kit_slice_cstr("1");
+ for (i = 0; i < (int)(sizeof tar_paths / sizeof tar_paths[0]); ++i) {
+ KitSlice tar_argv[6];
+ tar_argv[0] = kit_slice_cstr(tar_paths[i]);
+ tar_argv[1] = kit_slice_cstr("--format");
+ tar_argv[2] = kit_slice_cstr("ustar");
+ tar_argv[3] = kit_slice_cstr("-czf");
+ tar_argv[4] = kit_slice_cstr(out);
+ tar_argv[5] = kit_slice_cstr(".");
+ proc = NULL;
+ if (bh.exec.spawn(bh.exec.user, tar_argv, 6u, envv, 1u,
+ kit_slice_cstr(root), &proc) != 0)
+ continue;
+ ran = 1;
+ if (bh.exec.wait(bh.exec.user, proc, &exit_code) != 0) {
+ driver_build_host_fini(&bh);
+ driver_env_fini(&env);
+ return 1;
+ }
+ if (exit_code != 127) break;
+ }
+ driver_build_host_fini(&bh);
+ driver_env_fini(&env);
+ if (!ran || exit_code != 0) {
+ driver_errf(BUILD_TOOL, "workspace package failed");
+ return 1;
+ }
+ driver_printf("%s\n", out);
+ return 0;
+}
+
int driver_build(int argc, char** argv) {
DriverEnv env;
DriverBuildHost bh;
@@ -742,6 +1466,17 @@ int driver_build(int argc, char** argv) {
if (argc >= 2 && driver_getenv(KIT_BUILD_ENV_SOCK) &&
build_client_verb(argv[1]))
return build_client_mode(argc, argv, 1);
+ {
+ int cmd_index = build_find_top_command(argc, argv);
+ if (cmd_index >= 0) {
+ if (driver_streq(argv[cmd_index], "list"))
+ return build_cmd_list(argc, argv, cmd_index);
+ if (driver_streq(argv[cmd_index], "repo"))
+ return build_cmd_repo(argc, argv, cmd_index);
+ if (driver_streq(argv[cmd_index], "workspace"))
+ return build_cmd_workspace(argc, argv, cmd_index);
+ }
+ }
rc = build_parse_args(&cli, argc, argv,
argc >= 2 && driver_streq(argv[1], "test") ? 2 : 1,
diff --git a/src/build/resolve.c b/src/build/resolve.c
@@ -1092,10 +1092,13 @@ out:
static int build_leafset_refresh_inner(KitBuildCoordinator* c,
const BuildLeafSet* leafset,
const BuildConfig* cfg,
- int check_config, int* all_match) {
+ int check_config,
+ const char* trace_root,
+ int* all_match) {
size_t i;
KitSlice target;
uint8_t recipe[BUILD_HASH_LEN];
+ int matched_all = 1;
if (!c || !leafset || !cfg || !all_match) return BUILD_ERR;
*all_match = 0;
target = kit_slice_cstr(leafset->target);
@@ -1104,8 +1107,20 @@ static int build_leafset_refresh_inner(KitBuildCoordinator* c,
goto done;
if (check_config) {
for (i = 0; i < leafset->n_configs; ++i) {
- if (!config_observation_match(cfg, &leafset->configs[i])) goto done;
+ int matched = config_observation_match(cfg, &leafset->configs[i]);
+ if (c->opts.trace)
+ build_coord_tracef(c,
+ "deep-config target=%s key=%s scope=root result=%s",
+ leafset->target, leafset->configs[i].key,
+ matched ? "match" : "mismatch");
+ if (!matched) matched_all = 0;
}
+ } else if (c->opts.trace) {
+ for (i = 0; i < leafset->n_configs; ++i)
+ build_coord_tracef(c,
+ "deep-config target=%s child=%s key=%s scope=overlay-shielded",
+ trace_root ? trace_root : leafset->target,
+ leafset->target, leafset->configs[i].key);
}
for (i = 0; i < leafset->n_sources; ++i) {
uint8_t blob[BUILD_HASH_LEN];
@@ -1115,9 +1130,9 @@ static int build_leafset_refresh_inner(KitBuildCoordinator* c,
blob, &present) != BUILD_OK)
return BUILD_ERR;
if (leafset->sources[i].absent) {
- if (present) goto done;
+ if (present) matched_all = 0;
} else if (!present || !build_id_eq(blob, leafset->sources[i].blob)) {
- goto done;
+ matched_all = 0;
}
}
for (i = 0; i < leafset->n_globs; ++i) {
@@ -1126,25 +1141,28 @@ static int build_leafset_refresh_inner(KitBuildCoordinator* 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;
+ if (!build_id_eq(hash, leafset->globs[i].result_hash)) matched_all = 0;
}
for (i = 0; i < leafset->n_blobs; ++i) {
if (kit_cas_has_blob(c->cas, leafset->blobs[i].blob) != KIT_OK)
- goto done;
+ matched_all = 0;
}
for (i = 0; i < leafset->n_children; ++i) {
int child_match = 0;
if (build_leafset_refresh_inner(c, leafset->children[i], cfg, 0,
+ trace_root ? trace_root : leafset->target,
&child_match) != BUILD_OK)
return BUILD_ERR;
- if (!child_match) goto done;
+ if (!child_match) matched_all = 0;
}
- *all_match = 1;
+ if (matched_all) *all_match = 1;
done:
return BUILD_OK;
}
int build_leafset_refresh(KitBuildCoordinator* c, const BuildLeafSet* leafset,
const BuildConfig* cfg, int* all_match) {
- return build_leafset_refresh_inner(c, leafset, cfg, 1, all_match);
+ return build_leafset_refresh_inner(c, leafset, cfg, 1,
+ leafset ? leafset->target : NULL,
+ all_match);
}
diff --git a/test/buildcoord/run.sh b/test/buildcoord/run.sh
@@ -23,7 +23,7 @@ kit_report_init
ws="$work/ws"
store="$work/store"
mkdir -p "$ws/recipes" "$ws/remote" "$ws/src/depfile" "$ws/src/globset" \
- "$ws/src/tree/nested/deeper" "$store"
+ "$ws/src/list/sub" "$ws/src/tree/nested/deeper" "$store"
make_pkg() {
pkg=$1
@@ -41,12 +41,15 @@ make_pkg() {
make_pkg absent probe absent.sh
make_pkg app bundle app.sh
make_pkg argv argv argv.sh echo argv.sh
+make_pkg async dep async_dep.sh unawaited async_unawaited.sh
make_pkg cfg default cfg_default.sh probe cfg.sh
make_pkg client formats client_formats.sh
make_pkg cycle a cycle_a.sh b cycle_b.sh self cycle_self.sh
make_pkg defn probe defn_v1.sh unused defn_unused_v1.sh
make_pkg depfile lines depfile_lines.sh probe depfile.sh
make_pkg env probe env_probe.sh
+make_pkg exports consumer exports_consumer.sh other exports_other.sh \
+ provider exports_provider.sh
make_pkg extuser probe extuser.sh
make_pkg fail probe fail.sh
make_pkg fetch blob fetch.sh format fetch_format.sh
@@ -62,6 +65,7 @@ make_pkg scope leaf scope_leaf.sh parent-inherit scope_parent_inherit.sh \
parent-override scope_parent_override.sh plain scope_plain.sh
make_pkg shadow leaf shadow_leaf.sh parent shadow_parent.sh
make_pkg stable dep stable_dep.sh parent stable_parent.sh
+make_pkg storage probe material.sh
make_pkg submit echo argv.sh probe submit.sh
make_pkg test fail test_fail.sh nondet test_nondet.sh parent test_parent.sh \
pass test_pass.sh separate test_pass.sh
@@ -77,6 +81,35 @@ search . do
walk-parents true
EOF
+mkdir -p "$ws/listpkg" "$ws/listpkg/subpkg"
+cat > "$ws/listpkg/BUILD.kit" <<'EOF'
+kit-build 2
+
+[target exact]
+recipe //recipes/argv.sh
+
+[targets c.object]
+list ../src/list/*.c -> {stem}.o
+
+[rule object]
+match *.o
+type c.object
+
+[default c.object]
+recipe //recipes/argv.sh
+
+[redo-defaults]
+enabled true
+search . do
+walk-parents true
+EOF
+
+cat > "$ws/listpkg/subpkg/BUILD.kit" <<'EOF'
+kit-build 1
+[target child]
+recipe //recipes/argv.sh
+EOF
+
cat > "$ws/BUILD.kit" <<'EOF'
kit-build 2
@@ -107,6 +140,12 @@ EOF
cat > "$ws/src/globset/one.txt" <<'EOF'
one
EOF
+cat > "$ws/src/list/foo.c" <<'EOF'
+int foo;
+EOF
+cat > "$ws/src/list/sub/bar.c" <<'EOF'
+int bar;
+EOF
cat > "$ws/src/tree/root.txt" <<'EOF'
root
EOF
@@ -176,6 +215,27 @@ mkdir -p "$KIT_BUILD_OUT"
printf '%s\n' "$@" > "$KIT_BUILD_OUT/args.txt"
EOF
+cat > "$ws/recipes/async_dep.sh" <<'EOF'
+#!/bin/sh
+set -eu
+count=0
+if [ -f async-dep.count ]; then
+ count=$(cat async-dep.count)
+fi
+count=$((count + 1))
+printf '%s\n' "$count" > async-dep.count
+mkdir -p "$KIT_BUILD_OUT"
+printf 'async-dep:%s\n' "$count" > "$KIT_BUILD_OUT/dep.txt"
+EOF
+
+cat > "$ws/recipes/async_unawaited.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+"$KIT" build need-submit //async:dep >/dev/null
+printf 'unawaited\n' > "$KIT_BUILD_OUT/unawaited.txt"
+EOF
+
cat > "$ws/recipes/cfg.sh" <<'EOF'
#!/bin/sh
set -eu
@@ -295,6 +355,33 @@ printf 'ambient:%s\n' "${AMBIENT-unset}" > "$KIT_BUILD_OUT/env.txt"
printf 'declared:%s\n' "${DECLARED-unset}" >> "$KIT_BUILD_OUT/env.txt"
EOF
+cat > "$ws/recipes/exports_provider.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+"$KIT" build export-set cflags -- -Iinclude -DPROVIDER=1
+"$KIT" build export-set libs -- -Llib -lprovider
+printf 'provider\n' > "$KIT_BUILD_OUT/provider.txt"
+EOF
+
+cat > "$ws/recipes/exports_other.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+"$KIT" build export-set cflags -- -Iother
+printf 'other\n' > "$KIT_BUILD_OUT/other.txt"
+EOF
+
+cat > "$ws/recipes/exports_consumer.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+"$KIT" build export-get //exports:provider cflags > "$KIT_BUILD_OUT/cflags.txt"
+"$KIT" build export-get //exports:provider libs > "$KIT_BUILD_OUT/libs.txt"
+"$KIT" build export-collect cflags //exports:provider //exports:other \
+ > "$KIT_BUILD_OUT/collect.txt"
+EOF
+
cat > "$ws/recipes/fail.sh" <<'EOF'
#!/bin/sh
set -eu
@@ -657,6 +744,13 @@ run_test() {
> "$work/$name.out" 2> "$work/$name.err"
}
+run_build_tool() {
+ name=$1
+ shift
+ "$KIT" build --store "$store" --root "$ws" --def-name BUILD.kit "$@" \
+ > "$work/$name.out" 2> "$work/$name.err"
+}
+
build_assert_ok() {
name=$1
shift
@@ -730,6 +824,12 @@ tree_id_from() {
awk 'NF >= 1 {print $1; exit}' "$1"
}
+target_record_for() {
+ target=$1
+ find "$store/build/target" -type f -exec grep -l "$target" {} + 2>/dev/null |
+ awk 'NR == 1 {print; exit}'
+}
+
cache_path_from() {
id=$1
pp=$(printf '%.2s' "$id")
@@ -812,6 +912,51 @@ run_fail "buildcoord-helper-outside-recipe-fails" \
contains "buildcoord-helper-outside-recipe-diag" \
"$work/buildcoord-helper-outside-recipe-fails.err" "unexpected argument"
+if run_build_tool buildcoord-list-package list //listpkg/...; then
+ ok "buildcoord-list-package"
+else
+ not_ok "buildcoord-list-package" "$work/buildcoord-list-package.err"
+fi
+contains "buildcoord-list-exact-target" \
+ "$work/buildcoord-list-package.out" "//listpkg:exact"
+contains "buildcoord-list-generated-target" \
+ "$work/buildcoord-list-package.out" "//listpkg:foo.o"
+contains "buildcoord-list-nested-package" \
+ "$work/buildcoord-list-package.out" "//listpkg/subpkg:child"
+not_contains "buildcoord-list-no-child-files-from-parent-rule" \
+ "$work/buildcoord-list-package.out" "//listpkg:sub/bar.o"
+
+if run_build_tool buildcoord-list-scan-do list --scan-do //redo/...; then
+ ok "buildcoord-list-scan-do"
+else
+ not_ok "buildcoord-list-scan-do" "$work/buildcoord-list-scan-do.err"
+fi
+contains "buildcoord-list-scan-do-default-provider" \
+ "$work/buildcoord-list-scan-do.out" "default.o.do"
+
+run_fail "buildcoord-invalid-def-name-rejected" \
+ "$KIT" build --store "$store" --root "$ws" --def-name ../BUILD.kit \
+ //app:bundle
+contains "buildcoord-invalid-def-name-diag" \
+ "$work/buildcoord-invalid-def-name-rejected.err" "def"
+
+run_fail "buildcoord-invalid-label-rejected" \
+ "$KIT" build --store "$store" --root "$ws" --def BUILD.kit //bad/../pkg:target
+contains "buildcoord-invalid-label-diag" \
+ "$work/buildcoord-invalid-label-rejected.err" "invalid"
+
+mkdir -p "$ws/badrecipe"
+cat > "$ws/badrecipe/BUILD.kit" <<'EOF'
+kit-build 1
+[target probe]
+recipe ../recipes/argv.sh
+EOF
+run_fail "buildcoord-invalid-recipe-path-rejected" \
+ "$KIT" build --store "$store" --root "$ws" --def BUILD.kit \
+ --config "env.KIT=$KIT" //badrecipe:probe
+contains "buildcoord-invalid-recipe-path-diag" \
+ "$work/buildcoord-invalid-recipe-path-rejected.err" "recipe"
+
build_assert_ok buildcoord-workspace-default //wscfg:probe
wscfg_default_path=$(tree_path_from "$work/buildcoord-workspace-default.out")
contains "buildcoord-workspace-default-output" \
@@ -849,6 +994,81 @@ else
kit_skip "buildcoord-external-targz" "host tar/gzip unavailable"
fi
+if run_build_tool buildcoord-repo-list repo list; then
+ ok "buildcoord-repo-list"
+else
+ not_ok "buildcoord-repo-list" "$work/buildcoord-repo-list.err"
+fi
+contains "buildcoord-repo-list-ext" "$work/buildcoord-repo-list.out" "ext"
+contains "buildcoord-repo-list-tree-format" \
+ "$work/buildcoord-repo-list.out" "tree"
+
+if run_build_tool buildcoord-repo-fetch repo fetch ext; then
+ ok "buildcoord-repo-fetch"
+else
+ not_ok "buildcoord-repo-fetch" "$work/buildcoord-repo-fetch.err"
+fi
+contains "buildcoord-repo-fetch-output" \
+ "$work/buildcoord-repo-fetch.out" "ext"
+
+mkdir -p "$work/repoadd-src"
+cat > "$work/repoadd-src/WORKSPACE.kit" <<'EOF'
+kit-workspace 1
+name repoadd
+version test
+EOF
+cat > "$work/repoadd-src/BUILD.kit" <<'EOF'
+kit-build 1
+[target data]
+recipe //build.sh
+EOF
+cat > "$work/repoadd-src/build.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+printf 'repoadd\n' > "$KIT_BUILD_OUT/repoadd.txt"
+EOF
+chmod +x "$work/repoadd-src/build.sh"
+if (cd "$work" && COPYFILE_DISABLE=1 tar --format ustar -czf \
+ "$work/repoadd.tar.gz" repoadd-src) \
+ > "$work/repoadd-pack.out" 2> "$work/repoadd-pack.err"; then
+ if run_build_tool buildcoord-repo-add repo add added \
+ "file://$work/repoadd.tar.gz" --format tar.gz \
+ --strip-prefix repoadd-src; then
+ ok "buildcoord-repo-add"
+ else
+ not_ok "buildcoord-repo-add" "$work/buildcoord-repo-add.err"
+ fi
+ contains "buildcoord-repo-add-workspace-entry" "$ws/WORKSPACE.kit" \
+ "[external added]"
+else
+ kit_skip "buildcoord-repo-add" "host tar unavailable"
+ kit_skip "buildcoord-repo-add-workspace-entry" "host tar unavailable"
+fi
+
+if run_build_tool buildcoord-workspace-package workspace package \
+ --format tar.gz -o "$work/workspace-package.tar.gz"; then
+ ok "buildcoord-workspace-package"
+else
+ not_ok "buildcoord-workspace-package" \
+ "$work/buildcoord-workspace-package.err"
+fi
+assert_file_exists "buildcoord-workspace-package-output" \
+ "$work/workspace-package.tar.gz"
+if tar -tzf "$work/workspace-package.tar.gz" \
+ > "$work/workspace-package.list" 2> "$work/workspace-package-list.err"; then
+ ok "buildcoord-workspace-package-list"
+else
+ not_ok "buildcoord-workspace-package-list" \
+ "$work/workspace-package-list.err"
+fi
+contains "buildcoord-workspace-package-includes-workspace" \
+ "$work/workspace-package.list" "WORKSPACE.kit"
+contains "buildcoord-workspace-package-includes-build-def" \
+ "$work/workspace-package.list" "BUILD.kit"
+not_contains "buildcoord-workspace-package-excludes-store" \
+ "$work/workspace-package.list" "/build/cache/"
+
build_assert_ok buildcoord-absent-cold //absent:probe
absent_path=$(tree_path_from "$work/buildcoord-absent-cold.out")
contains "buildcoord-absent-cold-output" "$absent_path/optional.txt" "absent"
@@ -1077,6 +1297,9 @@ contains "buildcoord-config-override-change-deep" \
contains "buildcoord-config-override-change-parent-deep" \
"$work/buildcoord-config-override-change.err" \
"trace deep-hit target=//scope:parent-override"
+contains "buildcoord-config-override-change-shielded-trace" \
+ "$work/buildcoord-config-override-change.err" \
+ "deep-config target=//scope:parent-override child=//scope:leaf key=mode scope=overlay-shielded"
not_contains "buildcoord-config-override-change-parent-not-shallow" \
"$work/buildcoord-config-override-change.err" \
"trace shallow-hit target=//scope:parent-override"
@@ -1133,6 +1356,15 @@ contains "buildcoord-config-shadow-parent-change-parent" \
contains "buildcoord-config-shadow-parent-change-leaf" \
"$shadow_parent2_path/leaf.txt" "leaf:child"
+build_assert_ok buildcoord-config-shadow-parent-change-trace --trace --stats \
+ --config mode=parent3 //shadow:parent
+contains "buildcoord-config-shadow-parent-root-mismatch-trace" \
+ "$work/buildcoord-config-shadow-parent-change-trace.err" \
+ "deep-config target=//shadow:parent key=mode scope=root result=mismatch"
+contains "buildcoord-config-shadow-parent-child-shielded-trace" \
+ "$work/buildcoord-config-shadow-parent-change-trace.err" \
+ "deep-config target=//shadow:parent child=//shadow:leaf key=mode scope=overlay-shielded"
+
build_assert_ok buildcoord-stats-argv-cold --stats //argv:echo -- stat-one
contains "buildcoord-stats-argv-cold-run" "$work/buildcoord-stats-argv-cold.err" \
"recipes_run=1"
@@ -1243,6 +1475,43 @@ fi
contains "buildcoord-need-await-format-output" "$await_fmt_path/args.txt" \
"format-arg"
+build_assert_ok buildcoord-exports-provider --stats //exports:provider
+exports_provider_path=$(tree_path_from "$work/buildcoord-exports-provider.out")
+contains "buildcoord-exports-file-created" \
+ "$exports_provider_path/.kit/exports" "kit-build-exports 1"
+contains "buildcoord-exports-cflags-stored" \
+ "$exports_provider_path/.kit/exports" "DPROVIDER=1"
+
+build_assert_ok buildcoord-exports-consumer --stats //exports:consumer
+exports_consumer_path=$(tree_path_from "$work/buildcoord-exports-consumer.out")
+contains "buildcoord-export-get-cflags" \
+ "$exports_consumer_path/cflags.txt" "Iinclude"
+contains "buildcoord-export-get-libs" \
+ "$exports_consumer_path/libs.txt" "lprovider"
+contains "buildcoord-export-collect-provider-first" \
+ "$exports_consumer_path/collect.txt" "Iinclude"
+contains "buildcoord-export-collect-other-second" \
+ "$exports_consumer_path/collect.txt" "Iother"
+
+cat > "$ws/recipes/exports_provider.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+"$KIT" build export-set cflags -- -Iinclude -DPROVIDER=2
+"$KIT" build export-set libs -- -Llib -lprovider
+printf 'provider-v2\n' > "$KIT_BUILD_OUT/provider.txt"
+EOF
+chmod +x "$ws/recipes/exports_provider.sh"
+build_assert_ok buildcoord-exports-provider-change-invalidates-consumer \
+ --stats //exports:consumer
+contains "buildcoord-exports-consumer-reruns-on-provider-tree-change" \
+ "$work/buildcoord-exports-provider-change-invalidates-consumer.err" \
+ "recipes_run="
+exports_consumer_v2_path=$(tree_path_from \
+ "$work/buildcoord-exports-provider-change-invalidates-consumer.out")
+contains "buildcoord-export-get-cflags-updated" \
+ "$exports_consumer_v2_path/cflags.txt" "DPROVIDER=2"
+
build_assert_ok buildcoord-defn-v1 --stats //defn:probe
contains "buildcoord-defn-v1-run" "$work/buildcoord-defn-v1.err" \
"recipes_run=1"
@@ -1297,6 +1566,35 @@ contains "buildcoord-material-missing-bytes-reruns-run" \
contains "buildcoord-material-missing-bytes-reruns-miss" \
"$work/buildcoord-material-missing-bytes-reruns.err" "materialize_misses="
+build_assert_ok buildcoord-storage-record-cold --stats //storage:probe
+contains "buildcoord-storage-record-cold-run" \
+ "$work/buildcoord-storage-record-cold.err" "recipes_run=1"
+storage_record=$(target_record_for "//storage:probe")
+if [ -n "$storage_record" ] && [ -f "$storage_record" ]; then
+ ok "buildcoord-storage-record-found"
+ printf 'not a kit-build-record\n' > "$storage_record"
+else
+ printf 'record=%s\n' "$storage_record" > "$work/storage-record.diag"
+ not_ok "buildcoord-storage-record-found" "$work/storage-record.diag"
+fi
+build_assert_ok buildcoord-storage-corrupt-record-reruns --stats //storage:probe
+contains "buildcoord-storage-corrupt-record-reruns-run" \
+ "$work/buildcoord-storage-corrupt-record-reruns.err" "recipes_run=1"
+
+find "$store/build/trace" -type f -exec grep -l "target //storage:probe" {} + \
+ > "$work/storage-traces.txt" 2> "$work/storage-traces.err"
+if [ -s "$work/storage-traces.txt" ]; then
+ ok "buildcoord-storage-traces-found"
+ while IFS= read -r trace_file; do
+ printf 'not a kit-build-trace\n' > "$trace_file"
+ done < "$work/storage-traces.txt"
+else
+ not_ok "buildcoord-storage-traces-found" "$work/storage-traces.err"
+fi
+build_assert_ok buildcoord-storage-malformed-trace-reruns --stats //storage:probe
+contains "buildcoord-storage-malformed-trace-reruns-run" \
+ "$work/buildcoord-storage-malformed-trace-reruns.err" "recipes_run=1"
+
build_assert_ok buildcoord-globset-cold --stats //globset:probe
contains "buildcoord-globset-cold-run" "$work/buildcoord-globset-cold.err" \
"recipes_run=1"
@@ -1480,6 +1778,40 @@ run_fail "buildcoord-cycle-indirect-fails" \
contains "buildcoord-cycle-indirect-diag" \
"$work/buildcoord-cycle-indirect-fails.err" "cycle"
+build_assert_ok buildcoord-async-unawaited-cold --stats //async:unawaited
+contains "buildcoord-async-unawaited-cold-stats" \
+ "$work/buildcoord-async-unawaited-cold.err" "recipes_run="
+async_unawaited_path=$(tree_path_from "$work/buildcoord-async-unawaited-cold.out")
+contains "buildcoord-async-unawaited-output" \
+ "$async_unawaited_path/unawaited.txt" "unawaited"
+find "$store/build/trace" -type f -exec grep -l "target //async:unawaited" {} + \
+ > "$work/async-unawaited-traces.txt" 2> "$work/async-unawaited-traces.err"
+if [ -s "$work/async-unawaited-traces.txt" ]; then
+ xargs grep -h "//async:dep" < "$work/async-unawaited-traces.txt" \
+ > "$work/async-unawaited-deps.txt" 2> "$work/async-unawaited-deps.err" || true
+ if [ ! -s "$work/async-unawaited-deps.txt" ]; then
+ ok "buildcoord-async-unawaited-not-recorded"
+ else
+ not_ok "buildcoord-async-unawaited-not-recorded" \
+ "$work/async-unawaited-deps.txt"
+ fi
+else
+ not_ok "buildcoord-async-unawaited-traces-found" \
+ "$work/async-unawaited-traces.err"
+fi
+cat > "$ws/recipes/async_dep.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+printf 'async-dep:edited\n' > "$KIT_BUILD_OUT/dep.txt"
+EOF
+chmod +x "$ws/recipes/async_dep.sh"
+build_assert_ok buildcoord-async-unawaited-dep-edit --stats //async:unawaited
+contains "buildcoord-async-unawaited-dep-edit-hit" \
+ "$work/buildcoord-async-unawaited-dep-edit.err" "deep_hits=1"
+contains "buildcoord-async-unawaited-dep-edit-no-run" \
+ "$work/buildcoord-async-unawaited-dep-edit.err" "recipes_run=0"
+
export AMBIENT=leaked
build_assert_ok buildcoord-env-cold --stats --config env.DECLARED=one \
//env:probe
@@ -1490,8 +1822,21 @@ contains "buildcoord-env-clean-ambient" "$env_one_path/env.txt" \
contains "buildcoord-env-declared-visible" "$env_one_path/env.txt" \
"declared:one"
-# Spec checks below are intentionally red until these BUILD.md promises are
-# implemented.
+unset MISSING_BUILDCOORD_ENV
+run_fail "buildcoord-env-shorthand-unset-rejected" \
+ "$KIT" build --store "$store" --root "$ws" --def BUILD.kit \
+ --env MISSING_BUILDCOORD_ENV --config "env.KIT=$KIT" //env:probe
+contains "buildcoord-env-shorthand-unset-rejected-diag" \
+ "$work/buildcoord-env-shorthand-unset-rejected.err" "MISSING_BUILDCOORD_ENV"
+
+run_fail "buildcoord-env-config-unset-rejected" \
+ "$KIT" build --store "$store" --root "$ws" --def BUILD.kit \
+ --config env.MISSING_BUILDCOORD_ENV --config "env.KIT=$KIT" //env:probe
+contains "buildcoord-env-config-unset-rejected-diag" \
+ "$work/buildcoord-env-config-unset-rejected.err" "MISSING_BUILDCOORD_ENV"
+
+# Spec checks below are intentionally red until these BUILD_COORDINATOR.md
+# promises are implemented.
build_assert_ok buildcoord-spec-env-change-invalidates --stats \
--config env.DECLARED=two //env:probe
contains "buildcoord-spec-env-change-reruns" \