kit

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

build_coord.c (58191B)


      1 #include <kit/build_coord.h>
      2 #include <kit/cas.h>
      3 #include <kit/core.h>
      4 
      5 #include <stddef.h>
      6 #include <stdint.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 
     11 #include "dist_host.h"
     12 #include "driver.h"
     13 #include "env.h"
     14 #include "env_build.h"
     15 
     16 #define BUILD_TOOL "build"
     17 #define BUILD_DEFAULT_DEF "BUILD.kit"
     18 #define BUILD_MAX_CONFIG 128u
     19 #define BUILD_MAX_CONFIG_KEY 64u
     20 #define BUILD_MAX_ARGS 128u
     21 
     22 typedef enum BuildClientFormat {
     23   BUILD_CLIENT_FORMAT_PATH = 0,
     24   BUILD_CLIENT_FORMAT_ID = 1,
     25   BUILD_CLIENT_FORMAT_ID_PATH = 2,
     26 } BuildClientFormat;
     27 
     28 typedef struct BuildCli {
     29   const char* store;
     30   const char* root;
     31   const char* def;
     32   const char* profile;
     33   const char* target;
     34   const char* config_default;
     35   KitBuildKV config[BUILD_MAX_CONFIG];
     36   char config_keys[BUILD_MAX_CONFIG][BUILD_MAX_CONFIG_KEY];
     37   size_t nconfig;
     38   KitSlice args[BUILD_MAX_ARGS];
     39   size_t nargs;
     40   int verify;
     41   int stats;
     42   int trace;
     43   int test_mode;
     44   int has_config_default;
     45   int source_optional;
     46   int has_format;
     47   BuildClientFormat format;
     48 } BuildCli;
     49 
     50 typedef struct BuildListTarget {
     51   char label[KIT_BUILD_TARGET_MAX];
     52 } BuildListTarget;
     53 
     54 void driver_help_build(void) {
     55   driver_printf(
     56       "kit build - content-addressed build coordinator\n"
     57       "\n"
     58       "USAGE\n"
     59       "  kit build [--store DIR] [--root DIR] [--def FILE] [--profile NAME]\n"
     60       "            [--config K=V|env.NAME]... [--env NAME[=V]]...\n"
     61       "            [--verify] [--stats] [--trace] TARGET [-- ARG...]\n"
     62       "  kit build test [--store DIR] [--root DIR] [--def FILE] [--profile NAME]\n"
     63       "            [--config K=V|env.NAME]... [--env NAME[=V]]...\n"
     64       "            [--verify] [--stats] [--trace] TARGET [-- ARG...]\n"
     65       "  kit build [--root DIR] [--def FILE] list [--scan-do] [//PACKAGE[/...]]\n"
     66       "  kit build [--root DIR] repo add NAME URL\n"
     67       "            [--format tar.gz|kpkg] [--strip-prefix DIR]\n"
     68       "  kit build [--root DIR] repo fetch NAME\n"
     69       "  kit build [--root DIR] repo list\n"
     70       "  kit build [--root DIR] workspace package [--format tar.gz] -o OUT\n"
     71       "  kit build config-get [--default VALUE] KEY # inside a build recipe\n"
     72       "  kit build source [--optional] [--format path|id|id-path] PATH\n"
     73       "                                            # inside a build recipe\n"
     74       "  kit build fetch [--format path|id|id-path] BLOB URL...\n"
     75       "                                            # inside a build recipe\n"
     76       "  kit build depfile [--lines] FILE          # inside a build recipe\n"
     77       "  kit build glob PATTERN                    # inside a build recipe\n"
     78       "  kit build export-set VAR -- VALUE...      # inside a build recipe\n"
     79       "  kit build export-get TARGET VAR           # inside a build recipe\n"
     80       "  kit build export-collect VAR TARGET...    # inside a build recipe\n"
     81       "  kit build need [--format path|id|id-path]\n"
     82       "                 [--config K=V|env.NAME]... [--env NAME[=V]]...\n"
     83       "                 TARGET [-- ARG...]\n"
     84       "                                            # inside a build recipe\n"
     85       "  kit build need-submit [--config K=V|env.NAME]... [--env NAME[=V]]...\n"
     86       "                        TARGET [-- ARG...]\n"
     87       "                                            # inside a build recipe\n"
     88       "  kit build need-await [--format path|id|id-path] TOKEN\n"
     89       "                                            # inside a build recipe\n"
     90       "\n"
     91       "BUILD DEFINITIONS AND LABELS\n"
     92       "  The default package definition is BUILD.kit under --root (or the\n"
     93       "  basename selected by WORKSPACE.kit). A minimal definition is:\n"
     94       "\n"
     95       "    kit-build 1\n"
     96       "    [target copy]\n"
     97       "    recipe recipes/copy.sh\n"
     98       "\n"
     99       "  The command-line TARGET names a [target NAME] in the current package.\n"
    100       "  `copy` at the workspace root is rendered canonically as `//:copy` in\n"
    101       "  traces. Package paths are resolved relative to --root; recipes and\n"
    102       "  source paths are package-relative. Arguments after -- participate in\n"
    103       "  the target key and are available to the recipe protocol.\n"
    104       "\n");
    105   driver_printf(
    106       "RECIPE PROTOCOL\n"
    107       "  A recipe is an executable that writes its output tree below\n"
    108       "  $KIT_BUILD_OUT. It receives a clean environment containing\n"
    109       "  KIT_BUILD_SOCK, KIT_BUILD_OUT, KIT_BUILD_TARGET, KIT_BUILD_REPO,\n"
    110       "  KIT_BUILD_PACKAGE, and KIT_BUILD_LOCAL, plus values explicitly\n"
    111       "  propagated with --env/--config env.NAME.\n"
    112       "\n"
    113       "  Recipe-only `source` records a source dependency and prints its\n"
    114       "  workspace path or requested id form. `need` records another\n"
    115       "  target dependency; need-submit/need-await permit parallel requests.\n"
    116       "  config-get, fetch, depfile, and glob also require KIT_BUILD_SOCK and\n"
    117       "  are rejected outside a running recipe. export-set publishes ordered\n"
    118       "  metadata in the output tree; export-get/collect read it through need\n"
    119       "  dependencies. Use `kit build --client VERB ...` to select recipe\n"
    120       "  client mode explicitly.\n"
    121       "\n"
    122       "WORKSPACE COMMANDS\n"
    123       "  list prints targets in one package; a trailing /... scans packages\n"
    124       "  recursively. --scan-do lists Redo-style do/*.do rules instead. repo\n"
    125       "  add hashes a local path or file:// archive and records the pinned\n"
    126       "  entry in WORKSPACE.kit. repo fetch validates that a configured name\n"
    127       "  exists; external content is fetched and materialized when its label is\n"
    128       "  resolved. repo list prints the configured names, formats, and ids.\n"
    129       "  workspace package writes the workspace as a tar.gz using the hosted\n"
    130       "  system tar. Signed kpkg creation is provided by `kit pkg create`.\n"
    131       "\n"
    132       "OPTIONS\n"
    133       "  --store DIR     Build store root (default: Kit platform cache/build)\n"
    134       "  --root DIR      Workspace root (default: .)\n"
    135       "  --def FILE      Package build-file basename (alias: --def-name;\n"
    136       "                  default: WORKSPACE def-name or BUILD.kit)\n"
    137       "  --profile NAME  Apply WORKSPACE.kit [config NAME] after [config default]\n"
    138       "  --config K=V    Seed propagated configuration\n"
    139       "  --config env.N  Seed env.N from current $N\n"
    140       "  --env N[=V]     Shorthand for --config env.N[=V]\n"
    141       "  --verify        Verify the returned output tree in the CAS\n"
    142       "  --trace         Print target-level resolution decisions to stderr\n"
    143       "  --stats         Print build-resolution counters to stderr\n"
    144       "  -h, --help      Show this help and exit\n"
    145       "  --version       Show Kit version and exit\n"
    146       "\n"
    147       "OUTPUT AND CACHING\n"
    148       "  A successful top-level build prints `TREE_ID MATERIALIZED_PATH` to\n"
    149       "  stdout. --trace/--stats write diagnostics and counters to stderr. The\n"
    150       "  recipe output tree is content-addressed; an unchanged definition,\n"
    151       "  configuration, arguments, and recorded dependencies may produce a\n"
    152       "  deep cache hit with recipes_run=0. --verify checks the returned tree.\n");
    153   driver_printf(
    154       "\n"
    155       "EXAMPLES\n"
    156       "  mkdir -p demo/recipes\n"
    157       "  printf '%%s\\n' 'kit-build 1' '[target copy]' \\\n"
    158       "    'recipe recipes/copy.sh' > demo/BUILD.kit\n"
    159       "  printf '%%s\\n' '#!/bin/sh' 'set -eu' \\\n"
    160       "    'printf \"%%s\\n\" built > \"$KIT_BUILD_OUT/result.txt\"' \\\n"
    161       "    > demo/recipes/copy.sh\n"
    162       "  chmod +x demo/recipes/copy.sh\n"
    163       "  kit build --root \"$PWD/demo\" \\\n"
    164       "    --verify --stats copy\n"
    165       "  # Repeat the same command to observe a cache hit.\n"
    166       "\n"
    167       "EXIT CODES\n"
    168       "  0   success    1   resolution/recipe/CAS/I/O error    2   bad usage\n");
    169 }
    170 
    171 static const char* build_status_name(KitStatus st) {
    172   switch (st) {
    173     case KIT_OK: return "ok";
    174     case KIT_ERR: return "error";
    175     case KIT_NOMEM: return "out of memory";
    176     case KIT_INVALID: return "invalid input";
    177     case KIT_UNSUPPORTED: return "unsupported";
    178     case KIT_MALFORMED: return "malformed input";
    179     case KIT_IO: return "I/O error";
    180     case KIT_NOT_FOUND: return "not found";
    181     case KIT_AMBIGUOUS: return "ambiguous input";
    182   }
    183   return "error";
    184 }
    185 
    186 static int build_join_path(char* out, size_t cap, const char* a,
    187                            const char* b) {
    188   size_t na, nb;
    189   int sep;
    190   if (!out || cap == 0u || !a || !b) return 1;
    191   na = driver_strlen(a);
    192   nb = driver_strlen(b);
    193   sep = na > 0u && a[na - 1u] != '/' && a[na - 1u] != '\\';
    194   if (na + (sep ? 1u : 0u) + nb + 1u > cap) return 1;
    195   memcpy(out, a, na);
    196   if (sep) out[na++] = '/';
    197   memcpy(out + na, b, nb);
    198   out[na + nb] = '\0';
    199   return 0;
    200 }
    201 
    202 static int build_def_name_valid(const char* s) {
    203   size_t i;
    204   if (!s || !s[0]) return 0;
    205   for (i = 0; s[i]; ++i) {
    206     unsigned char c = (unsigned char)s[i];
    207     if (c == '/' || c == '\\' || c == ':' || c == '\n' || c == '\r')
    208       return 0;
    209   }
    210   return 1;
    211 }
    212 
    213 static int build_load_file(DriverEnv* env, const char* path, DriverLoad* load,
    214                            KitSlice* bytes) {
    215   return driver_load_bytes(&env->file_io, BUILD_TOOL, path, load, bytes);
    216 }
    217 
    218 static int build_load_workspace(DriverEnv* env, const char* root,
    219                                 KitBuildWorkspace* ws, int* present) {
    220   char path[KIT_BUILD_PATH_MAX];
    221   DriverLoad load;
    222   KitSlice bytes;
    223   char err[256];
    224   if (present) *present = 0;
    225   if (build_join_path(path, sizeof path, root, "WORKSPACE.kit") != 0) return 1;
    226   if (!driver_path_exists(path)) {
    227     kit_build_workspace_init(ws);
    228     return 0;
    229   }
    230   if (present) *present = 1;
    231   if (build_load_file(env, path, &load, &bytes) != 0) return 1;
    232   if (kit_build_workspace_parse(bytes.data, bytes.len, ws, err, sizeof err) !=
    233       KIT_OK) {
    234     driver_errf(BUILD_TOOL, "bad WORKSPACE.kit: %s", err);
    235     driver_release_bytes(&env->file_io, &load);
    236     return 1;
    237   }
    238   driver_release_bytes(&env->file_io, &load);
    239   return 0;
    240 }
    241 
    242 static int build_file_write(DriverEnv* env, const char* path, const char* text) {
    243   KitWriter* w = NULL;
    244   size_t n;
    245   if (!env || !path || !text) return 1;
    246   if (env->file_io.open_writer(env->file_io.user, path, &w) != KIT_OK || !w)
    247     return 1;
    248   n = driver_strlen(text);
    249   if ((n && kit_writer_write(w, text, n) != KIT_OK) ||
    250       kit_writer_status(w) != KIT_OK) {
    251     kit_writer_close(w);
    252     return 1;
    253   }
    254   kit_writer_close(w);
    255   return 0;
    256 }
    257 
    258 static int build_file_append(DriverEnv* env, const char* path,
    259                              const char* text) {
    260   DriverLoad load;
    261   KitSlice old = KIT_SLICE_NULL;
    262   KitWriter* w = NULL;
    263   size_t n;
    264   int had_old = 0;
    265   if (!env || !path || !text) return 1;
    266   if (driver_path_exists(path)) {
    267     if (build_load_file(env, path, &load, &old) != 0) return 1;
    268     had_old = 1;
    269   }
    270   if (env->file_io.open_writer(env->file_io.user, path, &w) != KIT_OK || !w) {
    271     if (had_old) driver_release_bytes(&env->file_io, &load);
    272     return 1;
    273   }
    274   n = driver_strlen(text);
    275   if ((old.len && kit_writer_write(w, old.data, old.len) != KIT_OK) ||
    276       (n && kit_writer_write(w, text, n) != KIT_OK) ||
    277       kit_writer_status(w) != KIT_OK) {
    278     kit_writer_close(w);
    279     if (had_old) driver_release_bytes(&env->file_io, &load);
    280     return 1;
    281   }
    282   kit_writer_close(w);
    283   if (had_old) driver_release_bytes(&env->file_io, &load);
    284   return 0;
    285 }
    286 
    287 static int build_file_url_path(const char* url, const char** out) {
    288   if (!url || !out || !driver_strneq(url, "file://", 7u)) return 1;
    289   *out = url + 7u;
    290   return 0;
    291 }
    292 
    293 static int build_list_cmp(const void* a, const void* b) {
    294   const BuildListTarget* la = (const BuildListTarget*)a;
    295   const BuildListTarget* lb = (const BuildListTarget*)b;
    296   return strcmp(la->label, lb->label);
    297 }
    298 
    299 static int build_list_add(BuildListTarget* out, size_t* n, size_t cap,
    300                           const char* label) {
    301   size_t i;
    302   if (!out || !n || !label || !label[0]) return 1;
    303   for (i = 0; i < *n; ++i)
    304     if (driver_streq(out[i].label, label)) return 0;
    305   if (*n >= cap || driver_strlen(label) >= sizeof out[0].label) return 1;
    306   snprintf(out[*n].label, sizeof out[*n].label, "%s", label);
    307   ++*n;
    308   return 0;
    309 }
    310 
    311 static void build_pkg_label(char* out, size_t cap, const char* pkg,
    312                             const char* local) {
    313   if (pkg && pkg[0])
    314     snprintf(out, cap, "//%s:%s", pkg, local);
    315   else
    316     snprintf(out, cap, "//:%s", local);
    317 }
    318 
    319 static int build_line_next(KitSlice bytes, size_t* pos, KitSlice* line) {
    320   size_t start = *pos;
    321   size_t end = start;
    322   while (end < bytes.len && bytes.s[end] != '\n') ++end;
    323   if (start >= bytes.len) return 1;
    324   line->s = bytes.s + start;
    325   line->len = end - start;
    326   *pos = end < bytes.len ? end + 1u : end;
    327   return 0;
    328 }
    329 
    330 static int build_slice_starts(KitSlice s, const char* prefix) {
    331   size_t n = driver_strlen(prefix);
    332   return s.len >= n && memcmp(s.s, prefix, n) == 0;
    333 }
    334 
    335 static void build_copy_slice0(char* out, size_t cap, KitSlice s) {
    336   size_t n = s.len < cap - 1u ? s.len : cap - 1u;
    337   if (n) memcpy(out, s.s, n);
    338   out[n] = '\0';
    339 }
    340 
    341 static int build_list_generated_from_projection(DriverEnv* env,
    342                                                 const char* root,
    343                                                 const char* pkg,
    344                                                 KitSlice line,
    345                                                 BuildListTarget* out,
    346                                                 size_t* n, size_t cap) {
    347   char pat[KIT_BUILD_PATH_MAX], tmpl[KIT_BUILD_TARGET_MAX];
    348   char dir_rel[KIT_BUILD_PATH_MAX], dir_abs[KIT_BUILD_PATH_MAX];
    349   char prefix[128], suffix[128], label[KIT_BUILD_TARGET_MAX], local[KIT_BUILD_TARGET_MAX];
    350   DriverDirHandle* dh;
    351   size_t i, arrow = (size_t)-1, star = (size_t)-1, slash = (size_t)-1;
    352   if (!build_slice_starts(line, "list ")) return 0;
    353   line.s += 5u;
    354   line.len -= 5u;
    355   for (i = 0; i + 3u <= line.len; ++i) {
    356     if (line.s[i] == ' ' && line.s[i + 1u] == '-' && line.s[i + 2u] == '>' &&
    357         line.s[i + 3u] == ' ') {
    358       arrow = i;
    359       break;
    360     }
    361   }
    362   if (arrow == (size_t)-1) return 0;
    363   build_copy_slice0(pat, sizeof pat, (KitSlice){.s = line.s, .len = arrow});
    364   build_copy_slice0(tmpl, sizeof tmpl,
    365                     (KitSlice){.s = line.s + arrow + 4u,
    366                                .len = line.len - arrow - 4u});
    367   for (i = 0; pat[i]; ++i) {
    368     if (pat[i] == '*') star = i;
    369     if (pat[i] == '/') slash = i;
    370   }
    371   if (star == (size_t)-1) return 0;
    372   if (slash != (size_t)-1) {
    373     snprintf(dir_rel, sizeof dir_rel, "%s/%.*s", pkg,
    374              (int)slash, pat);
    375     snprintf(prefix, sizeof prefix, "%.*s", (int)(star - slash - 1u),
    376              pat + slash + 1u);
    377   } else {
    378     snprintf(dir_rel, sizeof dir_rel, "%s", pkg);
    379     snprintf(prefix, sizeof prefix, "%.*s", (int)star, pat);
    380   }
    381   snprintf(suffix, sizeof suffix, "%s", pat + star + 1u);
    382   if (build_join_path(dir_abs, sizeof dir_abs, root, dir_rel) != 0) return 1;
    383   dh = driver_open_dir(env, dir_abs);
    384   if (!dh) return 0;
    385   for (i = 0;; ++i) {
    386     const char* name;
    387     uint32_t name_len;
    388     uint64_t ino, size, mtime;
    389     uint8_t ftype;
    390     size_t pn = driver_strlen(prefix), sn = driver_strlen(suffix);
    391     char stem[KIT_BUILD_TARGET_MAX];
    392     if (driver_read_dir_entry(dh, i, &name, &name_len, &ino, &size, &mtime,
    393                               &ftype) != 0)
    394       break;
    395     (void)ino;
    396     (void)size;
    397     (void)mtime;
    398     if (ftype != 4u || name_len < pn + sn ||
    399         memcmp(name, prefix, pn) != 0 ||
    400         (sn && memcmp(name + name_len - sn, suffix, sn) != 0))
    401       continue;
    402     snprintf(stem, sizeof stem, "%.*s", (int)(name_len - pn - sn),
    403              name + pn);
    404     if (driver_streq(tmpl, "{stem}.o"))
    405       snprintf(local, sizeof local, "%s.o", stem);
    406     else
    407       snprintf(local, sizeof local, "%s", stem);
    408     build_pkg_label(label, sizeof label, pkg, local);
    409     if (build_list_add(out, n, cap, label) != 0) {
    410       driver_close_dir(env, dh);
    411       return 1;
    412     }
    413   }
    414   driver_close_dir(env, dh);
    415   return 0;
    416 }
    417 
    418 static int build_list_package(DriverEnv* env, const char* root,
    419                               const char* def_name, const char* pkg,
    420                               int recursive, BuildListTarget* out, size_t* n,
    421                               size_t cap) {
    422   char pkg_dir[KIT_BUILD_PATH_MAX], def_path[KIT_BUILD_PATH_MAX];
    423   DriverLoad load;
    424   KitSlice bytes, line;
    425   size_t pos = 0;
    426   int in_targets = 0;
    427   if (build_join_path(pkg_dir, sizeof pkg_dir, root, pkg) != 0 ||
    428       build_join_path(def_path, sizeof def_path, pkg_dir, def_name) != 0)
    429     return 1;
    430   if (driver_path_exists(def_path) &&
    431       build_load_file(env, def_path, &load, &bytes) == 0) {
    432     while (build_line_next(bytes, &pos, &line) == 0) {
    433       if (build_slice_starts(line, "[target ") && line.len > 9u &&
    434           line.s[line.len - 1u] == ']') {
    435         char local[KIT_BUILD_TARGET_MAX], label[KIT_BUILD_TARGET_MAX];
    436         build_copy_slice0(local, sizeof local,
    437                           (KitSlice){.s = line.s + 8u,
    438                                      .len = line.len - 9u});
    439         build_pkg_label(label, sizeof label, pkg, local);
    440         if (build_list_add(out, n, cap, label) != 0) {
    441           driver_release_bytes(&env->file_io, &load);
    442           return 1;
    443         }
    444         in_targets = 0;
    445       } else if (build_slice_starts(line, "[targets ")) {
    446         in_targets = 1;
    447       } else if (line.len && line.s[0] == '[') {
    448         in_targets = 0;
    449       } else if (in_targets) {
    450         if (build_list_generated_from_projection(env, root, pkg, line, out, n,
    451                                                  cap) != 0) {
    452           driver_release_bytes(&env->file_io, &load);
    453           return 1;
    454         }
    455       }
    456     }
    457     driver_release_bytes(&env->file_io, &load);
    458   }
    459   if (recursive) {
    460     DriverDirHandle* dh = driver_open_dir(env, pkg_dir);
    461     uint64_t idx;
    462     if (!dh) return 0;
    463     for (idx = 0;; ++idx) {
    464       const char* name;
    465       uint32_t name_len;
    466       uint64_t ino, size, mtime;
    467       uint8_t ftype;
    468       char child_pkg[KIT_BUILD_PATH_MAX], child_dir[KIT_BUILD_PATH_MAX],
    469           child_def[KIT_BUILD_PATH_MAX];
    470       if (driver_read_dir_entry(dh, idx, &name, &name_len, &ino, &size, &mtime,
    471                                 &ftype) != 0)
    472         break;
    473       (void)ino;
    474       (void)size;
    475       (void)mtime;
    476       if (ftype != 3u) continue;
    477       if (pkg && pkg[0])
    478         snprintf(child_pkg, sizeof child_pkg, "%s/%.*s", pkg, (int)name_len,
    479                  name);
    480       else
    481         snprintf(child_pkg, sizeof child_pkg, "%.*s", (int)name_len, name);
    482       if (build_join_path(child_dir, sizeof child_dir, root, child_pkg) != 0 ||
    483           build_join_path(child_def, sizeof child_def, child_dir, def_name) != 0)
    484         continue;
    485       if (driver_path_exists(child_def)) {
    486         if (build_list_package(env, root, def_name, child_pkg, 1, out, n,
    487                                cap) != 0) {
    488           driver_close_dir(env, dh);
    489           return 1;
    490         }
    491       }
    492     }
    493     driver_close_dir(env, dh);
    494   }
    495   return 0;
    496 }
    497 
    498 static int build_add_config(BuildCli* cli, KitSlice key, KitSlice value) {
    499   if (!cli || !key.s || key.len == 0u || cli->nconfig >= BUILD_MAX_CONFIG)
    500     return 1;
    501   cli->config[cli->nconfig].key = key;
    502   cli->config[cli->nconfig].value = value;
    503   ++cli->nconfig;
    504   return 0;
    505 }
    506 
    507 static int build_env_name_valid(const char* name, size_t len) {
    508   size_t i;
    509   if (!name || len == 0u) return 0;
    510   for (i = 0; i < len; ++i) {
    511     unsigned char c = (unsigned char)name[i];
    512     if (c == '\0' || c == '=' || c == '\n' || c == '\r') return 0;
    513   }
    514   return 1;
    515 }
    516 
    517 static int build_add_env_config(BuildCli* cli, const char* name, size_t name_len,
    518                                 const char* value) {
    519   char* key;
    520   if (!cli || !build_env_name_valid(name, name_len) || !value ||
    521       cli->nconfig >= BUILD_MAX_CONFIG ||
    522       sizeof(KIT_BUILD_ENV_PREFIX) - 1u + name_len >= BUILD_MAX_CONFIG_KEY)
    523     return 1;
    524   key = cli->config_keys[cli->nconfig];
    525   memcpy(key, KIT_BUILD_ENV_PREFIX, sizeof(KIT_BUILD_ENV_PREFIX) - 1u);
    526   memcpy(key + sizeof(KIT_BUILD_ENV_PREFIX) - 1u, name, name_len);
    527   key[sizeof(KIT_BUILD_ENV_PREFIX) - 1u + name_len] = '\0';
    528   return build_add_config(cli, kit_slice_cstr(key), kit_slice_cstr(value));
    529 }
    530 
    531 static int build_parse_config(BuildCli* cli, const char* text) {
    532   const char* eq;
    533   size_t env_prefix_len = sizeof(KIT_BUILD_ENV_PREFIX) - 1u;
    534   if (!cli || !text) return 1;
    535   eq = driver_strchr(text, '=');
    536   if (!eq) {
    537     const char* value;
    538     if (!driver_strneq(text, KIT_BUILD_ENV_PREFIX, env_prefix_len) ||
    539         text[env_prefix_len] == '\0')
    540       return 1;
    541     value = driver_getenv(text + env_prefix_len);
    542     if (!value) return 1;
    543     return build_add_config(cli, kit_slice_cstr(text), kit_slice_cstr(value));
    544   }
    545   if (eq == text) return 1;
    546   return build_add_config(cli, (KitSlice){.s = text, .len = (size_t)(eq - text)},
    547                           kit_slice_cstr(eq + 1));
    548 }
    549 
    550 static int build_parse_env(BuildCli* cli, const char* text) {
    551   const char* eq;
    552   if (!cli || !text) return 1;
    553   eq = driver_strchr(text, '=');
    554   if (!eq) {
    555     const char* value = driver_getenv(text);
    556     if (!value) return 1;
    557     return build_add_env_config(cli, text, driver_strlen(text), value);
    558   }
    559   return build_add_env_config(cli, text, (size_t)(eq - text), eq + 1);
    560 }
    561 
    562 static int build_client_glob_print(void* user, KitSlice path) {
    563   (void)user;
    564   driver_printf("%.*s\n", KIT_SLICE_ARG(path));
    565   return 0;
    566 }
    567 
    568 static int build_client_parse_format(const char* s, BuildClientFormat* out) {
    569   if (!s || !out) return 1;
    570   if (driver_streq(s, "path")) {
    571     *out = BUILD_CLIENT_FORMAT_PATH;
    572     return 0;
    573   }
    574   if (driver_streq(s, "id")) {
    575     *out = BUILD_CLIENT_FORMAT_ID;
    576     return 0;
    577   }
    578   if (driver_streq(s, "id-path")) {
    579     *out = BUILD_CLIENT_FORMAT_ID_PATH;
    580     return 0;
    581   }
    582   return 1;
    583 }
    584 
    585 static void build_client_print_value(BuildClientFormat fmt,
    586                                      const uint8_t id[KIT_BUILD_HASH_LEN],
    587                                      KitSlice path) {
    588   char hex[2u * KIT_BUILD_HASH_LEN + 1u];
    589   if (fmt == BUILD_CLIENT_FORMAT_PATH) {
    590     driver_printf("%.*s\n", KIT_SLICE_ARG(path));
    591     return;
    592   }
    593   kit_hex_encode(hex, id, KIT_BUILD_HASH_LEN);
    594   if (fmt == BUILD_CLIENT_FORMAT_ID) {
    595     driver_printf("%s\n", hex);
    596     return;
    597   }
    598   driver_printf("%s %.*s\n", hex, KIT_SLICE_ARG(path));
    599 }
    600 
    601 static int build_client_verb(const char* s) {
    602   return driver_streq(s, "config-get") || driver_streq(s, "source") ||
    603          driver_streq(s, "fetch") || driver_streq(s, "depfile") ||
    604          driver_streq(s, "glob") || driver_streq(s, "export-set") ||
    605          driver_streq(s, "export-get") || driver_streq(s, "export-collect") ||
    606          driver_streq(s, "need") ||
    607          driver_streq(s, "need-submit") || driver_streq(s, "need-await");
    608 }
    609 
    610 static int build_parse_u64(const char* s, uint64_t* out) {
    611   uint64_t v = 0;
    612   size_t i;
    613   if (!s || !s[0] || !out) return 1;
    614   for (i = 0; s[i]; ++i) {
    615     unsigned c = (unsigned char)s[i];
    616     if (c < '0' || c > '9') return 1;
    617     if (v > (UINT64_MAX - (uint64_t)(c - '0')) / 10u) return 1;
    618     v = v * 10u + (uint64_t)(c - '0');
    619   }
    620   *out = v;
    621   return 0;
    622 }
    623 
    624 static int build_client_need_parse(BuildCli* cli, int argc, char** argv,
    625                                    int first_arg) {
    626   int i;
    627   memset(cli, 0, sizeof *cli);
    628   for (i = first_arg; i < argc; ++i) {
    629     const char* a = argv[i];
    630     if (driver_streq(a, "--config") && i + 1 < argc) {
    631       if (build_parse_config(cli, argv[++i]) != 0) {
    632         driver_errf(BUILD_TOOL, "bad --config, expected K=V or env.NAME");
    633         return 2;
    634       }
    635     } else if (driver_streq(a, "--env") && i + 1 < argc) {
    636       if (build_parse_env(cli, argv[++i]) != 0) {
    637         driver_errf(BUILD_TOOL, "bad --env, expected NAME or NAME=VALUE");
    638         return 2;
    639       }
    640     } else if (driver_streq(a, "--format") && i + 1 < argc) {
    641       if (build_client_parse_format(argv[++i], &cli->format) != 0) {
    642         driver_errf(BUILD_TOOL, "bad --format, expected path, id, or id-path");
    643         return 2;
    644       }
    645       cli->has_format = 1;
    646     } else if (driver_streq(a, "--")) {
    647       if (!cli->target) {
    648         driver_errf(BUILD_TOOL, "missing client target before --");
    649         return 2;
    650       }
    651       while (++i < argc) {
    652         if (cli->nargs >= BUILD_MAX_ARGS) {
    653           driver_errf(BUILD_TOOL, "too many recipe arguments");
    654           return 2;
    655         }
    656         cli->args[cli->nargs++] = kit_slice_cstr(argv[i]);
    657       }
    658       break;
    659     } else if (driver_streq(a, "--arg") && i + 1 < argc) {
    660       if (cli->nargs >= BUILD_MAX_ARGS) {
    661         driver_errf(BUILD_TOOL, "too many --arg values");
    662         return 2;
    663       }
    664       cli->args[cli->nargs++] = kit_slice_cstr(argv[++i]);
    665     } else if (a[0] == '-') {
    666       driver_errf(BUILD_TOOL, "unexpected client option: %s", a);
    667       return 2;
    668     } else if (!cli->target) {
    669       cli->target = a;
    670     } else {
    671       driver_errf(BUILD_TOOL, "unexpected client argument: %s", a);
    672       return 2;
    673     }
    674   }
    675   if (!cli->target) {
    676     driver_errf(BUILD_TOOL, "missing client target");
    677     return 2;
    678   }
    679   return 0;
    680 }
    681 
    682 static int build_export_set(DriverEnv* env, int argc, char** argv,
    683                             int first_arg) {
    684   const char* out_dir = driver_getenv(KIT_BUILD_ENV_OUT);
    685   char kit_dir[KIT_BUILD_PATH_MAX], exports_path[KIT_BUILD_PATH_MAX];
    686   size_t i;
    687   if (!out_dir || argc < first_arg + 3 ||
    688       !driver_streq(argv[first_arg + 1], "--")) {
    689     driver_errf(BUILD_TOOL, "usage: kit build export-set VAR -- VALUE...");
    690     return 2;
    691   }
    692   if (build_join_path(kit_dir, sizeof kit_dir, out_dir, ".kit") != 0 ||
    693       build_join_path(exports_path, sizeof exports_path, kit_dir, "exports") !=
    694           0) {
    695     driver_errf(BUILD_TOOL, "export path too long");
    696     return 1;
    697   }
    698   if (driver_mkdir_p(env, kit_dir) != 0) {
    699     driver_errf(BUILD_TOOL, "failed to create exports directory");
    700     return 1;
    701   }
    702   if (!driver_path_exists(exports_path) &&
    703       build_file_write(env, exports_path, "kit-build-exports 1\n") != 0) {
    704     driver_errf(BUILD_TOOL, "failed to write exports file");
    705     return 1;
    706   }
    707   {
    708     char line[KIT_BUILD_VAL_MAX + 32u];
    709     snprintf(line, sizeof line, "[var %s]\n", argv[first_arg]);
    710     if (build_file_append(env, exports_path, line) != 0) {
    711       driver_errf(BUILD_TOOL, "failed to append exports variable");
    712       return 1;
    713     }
    714   }
    715   for (i = (size_t)first_arg + 2u; i < (size_t)argc; ++i) {
    716     char line[KIT_BUILD_VAL_MAX + 2u];
    717     snprintf(line, sizeof line, "%s\n", argv[i]);
    718     if (build_file_append(env, exports_path, line) != 0) {
    719       driver_errf(BUILD_TOOL, "failed to append exports value");
    720       return 1;
    721     }
    722   }
    723   return 0;
    724 }
    725 
    726 static int build_exports_print_var(DriverEnv* env, const char* path,
    727                                    const char* var) {
    728   DriverLoad load;
    729   KitSlice bytes, line;
    730   size_t pos = 0;
    731   int active = 0, found = 0;
    732   if (build_load_file(env, path, &load, &bytes) != 0) return 1;
    733   while (build_line_next(bytes, &pos, &line) == 0) {
    734     if (line.len >= 6u && build_slice_starts(line, "[var ") &&
    735         line.s[line.len - 1u] == ']') {
    736       KitSlice name = {.s = line.s + 5u, .len = line.len - 6u};
    737       active = name.len == driver_strlen(var) &&
    738                memcmp(name.s, var, name.len) == 0;
    739       if (active) found = 1;
    740     } else if (active && line.len && line.s[0] != '[') {
    741       driver_printf("%.*s\n", KIT_SLICE_ARG(line));
    742     }
    743   }
    744   driver_release_bytes(&env->file_io, &load);
    745   return found ? 0 : 1;
    746 }
    747 
    748 static int build_export_get_one(DriverEnv* env, KitBuildClient* client,
    749                                 const char* target, const char* var) {
    750   KitBuildRequest req;
    751   KitBuildResult result;
    752   KitStatus st;
    753   char exports_path[KIT_BUILD_PATH_MAX], kit_dir[KIT_BUILD_PATH_MAX];
    754   memset(&req, 0, sizeof req);
    755   req.target = kit_slice_cstr(target);
    756   st = kit_build_client_need(client, &req, &result);
    757   if (st != KIT_OK) {
    758     driver_errf(BUILD_TOOL, "export-get need failed: %s", build_status_name(st));
    759     return 1;
    760   }
    761   if (build_join_path(kit_dir, sizeof kit_dir, result.path, ".kit") != 0 ||
    762       build_join_path(exports_path, sizeof exports_path, kit_dir, "exports") !=
    763           0 ||
    764       build_exports_print_var(env, exports_path, var) != 0) {
    765     driver_errf(BUILD_TOOL, "export variable not found: %s", var);
    766     return 1;
    767   }
    768   return 0;
    769 }
    770 
    771 static int build_client_mode(int argc, char** argv, int verb_index) {
    772   DriverEnv env;
    773   DriverBuildHost bh;
    774   KitContext ctx;
    775   KitBuildClient* client = NULL;
    776   KitStatus st;
    777   int rc = 1;
    778   const char* verb;
    779   int first_arg = verb_index + 1;
    780   if (argc <= verb_index) {
    781     driver_errf(BUILD_TOOL, "missing client command");
    782     return 2;
    783   }
    784   verb = argv[verb_index];
    785   driver_env_init(&env);
    786   ctx = driver_env_to_context(&env);
    787   if (driver_streq(verb, "export-set")) {
    788     rc = build_export_set(&env, argc, argv, first_arg);
    789     goto out_env;
    790   }
    791   if (driver_build_host_init(&bh, &env) != 0) {
    792     driver_errf(BUILD_TOOL, "hosted build adapter is unavailable");
    793     goto out_env;
    794   }
    795   st = kit_build_client_open(&ctx, &bh.transport, &client);
    796   if (st != KIT_OK) {
    797     driver_errf(BUILD_TOOL, "failed to connect to build coordinator: %s",
    798                 build_status_name(st));
    799     goto out_host;
    800   }
    801   if (driver_streq(verb, "config-get")) {
    802     KitSlice value;
    803     int present = 0;
    804     BuildCli cli;
    805     int i;
    806     memset(&cli, 0, sizeof cli);
    807     for (i = first_arg; i < argc; ++i) {
    808       if (driver_streq(argv[i], "--default") && i + 1 < argc) {
    809         cli.has_config_default = 1;
    810         cli.config_default = argv[++i];
    811       } else if (argv[i][0] == '-') {
    812         driver_errf(BUILD_TOOL, "unexpected config-get option: %s", argv[i]);
    813         rc = 2;
    814         goto out_client;
    815       } else if (!cli.target) {
    816         cli.target = argv[i];
    817       } else {
    818         driver_errf(BUILD_TOOL, "unexpected config-get argument: %s", argv[i]);
    819         rc = 2;
    820         goto out_client;
    821       }
    822     }
    823     if (!cli.target) {
    824       driver_errf(BUILD_TOOL, "usage: kit build config-get [--default VALUE] KEY");
    825       rc = 2;
    826       goto out_client;
    827     }
    828     st = kit_build_client_config_get(
    829         client, kit_slice_cstr(cli.target),
    830         cli.has_config_default ? kit_slice_cstr(cli.config_default)
    831                                : KIT_SLICE_NULL,
    832         cli.has_config_default, &value, &present);
    833     if (st != KIT_OK) {
    834       driver_errf(BUILD_TOOL, "config-get failed: %s", build_status_name(st));
    835       goto out_client;
    836     }
    837     if (!present) {
    838       if (value.s) {
    839         driver_printf("%.*s\n", KIT_SLICE_ARG(value));
    840         rc = 0;
    841         goto out_client;
    842       }
    843       rc = 1;
    844       goto out_client;
    845     }
    846     driver_printf("%.*s\n", KIT_SLICE_ARG(value));
    847     rc = 0;
    848   } else if (driver_streq(verb, "source")) {
    849     uint8_t blob[KIT_BUILD_HASH_LEN];
    850     KitSlice path;
    851     BuildCli cli;
    852     int i;
    853     memset(&cli, 0, sizeof cli);
    854     for (i = first_arg; i < argc; ++i) {
    855       if (driver_streq(argv[i], "--optional")) {
    856         cli.source_optional = 1;
    857       } else if (driver_streq(argv[i], "--format") && i + 1 < argc) {
    858         if (build_client_parse_format(argv[++i], &cli.format) != 0) {
    859           driver_errf(BUILD_TOOL, "bad --format, expected path, id, or id-path");
    860           rc = 2;
    861           goto out_client;
    862         }
    863       } else if (argv[i][0] == '-') {
    864         driver_errf(BUILD_TOOL, "unexpected source option: %s", argv[i]);
    865         rc = 2;
    866         goto out_client;
    867       } else if (!cli.target) {
    868         cli.target = argv[i];
    869       } else {
    870         driver_errf(BUILD_TOOL, "unexpected source argument: %s", argv[i]);
    871         rc = 2;
    872         goto out_client;
    873       }
    874     }
    875     if (!cli.target) {
    876       driver_errf(BUILD_TOOL,
    877                   "usage: kit build source [--optional] "
    878                   "[--format path|id|id-path] PATH");
    879       rc = 2;
    880       goto out_client;
    881     }
    882     st = kit_build_client_source(client, kit_slice_cstr(cli.target), blob, &path);
    883     if (st == KIT_NOT_FOUND && cli.source_optional) {
    884       rc = 0;
    885       goto out_client;
    886     }
    887     if (st != KIT_OK) {
    888       driver_errf(BUILD_TOOL, "source failed: %s", build_status_name(st));
    889       goto out_client;
    890     }
    891     build_client_print_value(cli.format, blob, path);
    892     rc = 0;
    893   } else if (driver_streq(verb, "fetch")) {
    894     uint8_t blob[KIT_BUILD_HASH_LEN];
    895     KitSlice urls[BUILD_MAX_ARGS];
    896     KitSlice path;
    897     size_t nurls = 0;
    898     int i;
    899     BuildCli cli;
    900     int blob_arg = 0;
    901     memset(&cli, 0, sizeof cli);
    902     for (i = first_arg; i < argc; ++i) {
    903       if (driver_streq(argv[i], "--format") && i + 1 < argc) {
    904         if (build_client_parse_format(argv[++i], &cli.format) != 0) {
    905           driver_errf(BUILD_TOOL, "bad --format, expected path, id, or id-path");
    906           rc = 2;
    907           goto out_client;
    908         }
    909       } else if (!blob_arg) {
    910         blob_arg = i;
    911       } else {
    912         if (nurls >= BUILD_MAX_ARGS) {
    913           driver_errf(BUILD_TOOL, "too many fetch URLs");
    914           rc = 2;
    915           goto out_client;
    916         }
    917         urls[nurls++] = kit_slice_cstr(argv[i]);
    918       }
    919     }
    920     if (!blob_arg || nurls == 0u ||
    921         driver_strlen(argv[blob_arg]) != 2u * KIT_BUILD_HASH_LEN ||
    922         kit_hex_decode(blob, argv[blob_arg], KIT_BUILD_HASH_LEN) != KIT_OK) {
    923       driver_errf(BUILD_TOOL,
    924                   "usage: kit build fetch [--format path|id|id-path] BLOB URL...");
    925       rc = 2;
    926       goto out_client;
    927     }
    928     st = kit_build_client_fetch(client, blob, urls, nurls, &path);
    929     if (st != KIT_OK) {
    930       driver_errf(BUILD_TOOL, "fetch failed: %s", build_status_name(st));
    931       goto out_client;
    932     }
    933     build_client_print_value(cli.format, blob, path);
    934     rc = 0;
    935   } else if (driver_streq(verb, "depfile")) {
    936     DriverLoad load;
    937     KitSlice depfile;
    938     uint32_t depfile_flags = KIT_BUILD_DEPFILE_DEFAULT;
    939     int file_arg = first_arg;
    940     if (argc == first_arg + 2 && driver_streq(argv[first_arg], "--lines")) {
    941       depfile_flags = KIT_BUILD_DEPFILE_LINES;
    942       file_arg = first_arg + 1;
    943     } else if (argc != first_arg + 1) {
    944       driver_errf(BUILD_TOOL, "usage: kit build depfile [--lines] FILE");
    945       rc = 2;
    946       goto out_client;
    947     }
    948     if (driver_load_bytes(&env.file_io, BUILD_TOOL, argv[file_arg], &load,
    949                           &depfile) != 0) {
    950       rc = 1;
    951       goto out_client;
    952     }
    953     st = kit_build_client_depfile_ex(client, depfile, depfile_flags);
    954     driver_release_bytes(&env.file_io, &load);
    955     if (st != KIT_OK) {
    956       driver_errf(BUILD_TOOL, "depfile failed: %s", build_status_name(st));
    957       goto out_client;
    958     }
    959     rc = 0;
    960   } else if (driver_streq(verb, "glob")) {
    961     if (argc != first_arg + 1) {
    962       driver_errf(BUILD_TOOL, "usage: kit build glob PATTERN");
    963       rc = 2;
    964       goto out_client;
    965     }
    966     st = kit_build_client_glob(client, kit_slice_cstr(argv[first_arg]),
    967                                build_client_glob_print, NULL);
    968     if (st != KIT_OK) {
    969       driver_errf(BUILD_TOOL, "glob failed: %s", build_status_name(st));
    970       goto out_client;
    971     }
    972     rc = 0;
    973   } else if (driver_streq(verb, "need")) {
    974     BuildCli cli;
    975     KitBuildRequest req;
    976     KitBuildResult result;
    977     rc = build_client_need_parse(&cli, argc, argv, first_arg);
    978     if (rc != 0) goto out_client;
    979     memset(&req, 0, sizeof req);
    980     req.target = kit_slice_cstr(cli.target);
    981     req.config = cli.config;
    982     req.nconfig = cli.nconfig;
    983     req.argv = cli.args;
    984     req.argc = cli.nargs;
    985     st = kit_build_client_need(client, &req, &result);
    986     if (st != KIT_OK) {
    987       driver_errf(BUILD_TOOL, "need failed: %s", build_status_name(st));
    988       rc = 1;
    989       goto out_client;
    990     }
    991     build_client_print_value(cli.format, result.output_tree,
    992                              kit_slice_cstr(result.path));
    993     rc = 0;
    994   } else if (driver_streq(verb, "need-submit")) {
    995     BuildCli cli;
    996     KitBuildRequest req;
    997     KitBuildNeedToken token;
    998     rc = build_client_need_parse(&cli, argc, argv, first_arg);
    999     if (rc != 0) goto out_client;
   1000     if (cli.has_format) {
   1001       driver_errf(BUILD_TOOL, "need-submit does not support --format");
   1002       rc = 2;
   1003       goto out_client;
   1004     }
   1005     memset(&req, 0, sizeof req);
   1006     req.target = kit_slice_cstr(cli.target);
   1007     req.config = cli.config;
   1008     req.nconfig = cli.nconfig;
   1009     req.argv = cli.args;
   1010     req.argc = cli.nargs;
   1011     st = kit_build_client_need_submit(client, &req, &token);
   1012     if (st != KIT_OK) {
   1013       driver_errf(BUILD_TOOL, "need-submit failed: %s",
   1014                   build_status_name(st));
   1015       rc = 1;
   1016       goto out_client;
   1017     }
   1018     driver_printf("%llu\n", (unsigned long long)token.id);
   1019     rc = 0;
   1020   } else if (driver_streq(verb, "need-await")) {
   1021     KitBuildNeedToken token;
   1022     KitBuildResult result;
   1023     BuildCli cli;
   1024     const char* token_arg = NULL;
   1025     int i;
   1026     memset(&cli, 0, sizeof cli);
   1027     for (i = first_arg; i < argc; ++i) {
   1028       if (driver_streq(argv[i], "--format") && i + 1 < argc) {
   1029         if (build_client_parse_format(argv[++i], &cli.format) != 0) {
   1030           driver_errf(BUILD_TOOL, "bad --format, expected path, id, or id-path");
   1031           rc = 2;
   1032           goto out_client;
   1033         }
   1034       } else if (argv[i][0] == '-') {
   1035         driver_errf(BUILD_TOOL, "unexpected need-await option: %s", argv[i]);
   1036         rc = 2;
   1037         goto out_client;
   1038       } else if (!token_arg) {
   1039         token_arg = argv[i];
   1040       } else {
   1041         driver_errf(BUILD_TOOL, "unexpected need-await argument: %s", argv[i]);
   1042         rc = 2;
   1043         goto out_client;
   1044       }
   1045     }
   1046     if (!token_arg || build_parse_u64(token_arg, &token.id) != 0) {
   1047       driver_errf(BUILD_TOOL,
   1048                   "usage: kit build need-await [--format path|id|id-path] TOKEN");
   1049       rc = 2;
   1050       goto out_client;
   1051     }
   1052     st = kit_build_client_need_await(client, token, &result);
   1053     if (st != KIT_OK) {
   1054       driver_errf(BUILD_TOOL, "need-await failed: %s", build_status_name(st));
   1055       rc = 1;
   1056       goto out_client;
   1057     }
   1058     build_client_print_value(cli.format, result.output_tree,
   1059                              kit_slice_cstr(result.path));
   1060     rc = 0;
   1061   } else if (driver_streq(verb, "export-get")) {
   1062     if (argc != first_arg + 2) {
   1063       driver_errf(BUILD_TOOL, "usage: kit build export-get TARGET VAR");
   1064       rc = 2;
   1065       goto out_client;
   1066     }
   1067     rc = build_export_get_one(&env, client, argv[first_arg],
   1068                               argv[first_arg + 1]);
   1069   } else if (driver_streq(verb, "export-collect")) {
   1070     int i;
   1071     if (argc < first_arg + 2) {
   1072       driver_errf(BUILD_TOOL, "usage: kit build export-collect VAR TARGET...");
   1073       rc = 2;
   1074       goto out_client;
   1075     }
   1076     rc = 0;
   1077     for (i = first_arg + 1; i < argc; ++i) {
   1078       if (build_export_get_one(&env, client, argv[i], argv[first_arg]) != 0)
   1079         rc = 1;
   1080     }
   1081   } else {
   1082     driver_errf(BUILD_TOOL, "unknown client command: %s", verb);
   1083     rc = 2;
   1084   }
   1085 
   1086 out_client:
   1087   kit_build_client_close(client);
   1088 out_host:
   1089   driver_build_host_fini(&bh);
   1090 out_env:
   1091   driver_env_fini(&env);
   1092   return rc;
   1093 }
   1094 
   1095 static int build_parse_args(BuildCli* cli, int argc, char** argv, int first_arg,
   1096                             int test_mode) {
   1097   int i;
   1098   memset(cli, 0, sizeof *cli);
   1099   cli->root = ".";
   1100   cli->test_mode = test_mode;
   1101   for (i = first_arg; i < argc; ++i) {
   1102     const char* a = argv[i];
   1103     if (driver_streq(a, "--store") && i + 1 < argc) {
   1104       cli->store = argv[++i];
   1105     } else if (driver_streq(a, "--root") && i + 1 < argc) {
   1106       cli->root = argv[++i];
   1107     } else if ((driver_streq(a, "--def") || driver_streq(a, "--def-name")) &&
   1108                i + 1 < argc) {
   1109       cli->def = argv[++i];
   1110     } else if (driver_streq(a, "--profile") && i + 1 < argc) {
   1111       cli->profile = argv[++i];
   1112     } else if (driver_streq(a, "--config") && i + 1 < argc) {
   1113       const char* opt = argv[++i];
   1114       if (build_parse_config(cli, opt) != 0) {
   1115         driver_errf(BUILD_TOOL,
   1116                     "bad --config '%s', expected K=V or env.NAME", opt);
   1117         return 2;
   1118       }
   1119     } else if (driver_streq(a, "--env") && i + 1 < argc) {
   1120       const char* opt = argv[++i];
   1121       if (build_parse_env(cli, opt) != 0) {
   1122         driver_errf(BUILD_TOOL, "bad --env '%s', expected NAME or NAME=VALUE",
   1123                     opt);
   1124         return 2;
   1125       }
   1126     } else if (driver_streq(a, "--")) {
   1127       if (!cli->target) {
   1128         driver_errf(BUILD_TOOL, "missing target before --");
   1129         return 2;
   1130       }
   1131       while (++i < argc) {
   1132         if (cli->nargs >= BUILD_MAX_ARGS) {
   1133           driver_errf(BUILD_TOOL, "too many recipe arguments");
   1134           return 2;
   1135         }
   1136         cli->args[cli->nargs++] = kit_slice_cstr(argv[i]);
   1137       }
   1138       break;
   1139     } else if (driver_streq(a, "--arg") && i + 1 < argc) {
   1140       if (cli->nargs >= BUILD_MAX_ARGS) {
   1141         driver_errf(BUILD_TOOL, "too many --arg values");
   1142         return 2;
   1143       }
   1144       cli->args[cli->nargs++] = kit_slice_cstr(argv[++i]);
   1145     } else if (driver_streq(a, "--stats")) {
   1146       cli->stats = 1;
   1147     } else if (driver_streq(a, "--trace")) {
   1148       cli->trace = 1;
   1149     } else if (driver_streq(a, "--verify")) {
   1150       cli->verify = 1;
   1151     } else if (a[0] == '-') {
   1152       driver_errf(BUILD_TOOL, "unexpected option: %s", a);
   1153       return 2;
   1154     } else if (!cli->target) {
   1155       cli->target = a;
   1156     } else {
   1157       driver_errf(BUILD_TOOL, "unexpected argument: %s", a);
   1158       return 2;
   1159     }
   1160   }
   1161   if (!cli->target) {
   1162     driver_errf(BUILD_TOOL, "missing target");
   1163     return 2;
   1164   }
   1165   if (cli->def && !build_def_name_valid(cli->def)) {
   1166     driver_errf(BUILD_TOOL, "bad def-name: %s", cli->def);
   1167     return 2;
   1168   }
   1169   return 0;
   1170 }
   1171 
   1172 static void build_trace_stderr(void* user, KitSlice line) {
   1173   (void)user;
   1174   driver_errf(BUILD_TOOL, "trace %.*s", KIT_SLICE_ARG(line));
   1175 }
   1176 
   1177 static void build_print_stats(const KitBuildStats* s) {
   1178   if (!s) return;
   1179   driver_errf(BUILD_TOOL,
   1180               "stats deep_hits=%llu shallow_hits=%llu recipes_run=%llu "
   1181               "materialize_misses=%llu object_fetches=%llu trace_pulls=%llu "
   1182               "test_runs=%llu test_cache_hits=%llu test_failures=%llu",
   1183               (unsigned long long)s->deep_hits,
   1184               (unsigned long long)s->shallow_hits,
   1185               (unsigned long long)s->recipes_run,
   1186               (unsigned long long)s->materialize_misses,
   1187               (unsigned long long)s->object_fetches,
   1188               (unsigned long long)s->trace_pulls,
   1189               (unsigned long long)s->test_runs,
   1190               (unsigned long long)s->test_cache_hits,
   1191               (unsigned long long)s->test_failures);
   1192 }
   1193 
   1194 static int build_verify_tree(DriverEnv* env, const char* store,
   1195                              const uint8_t tree[KIT_BUILD_HASH_LEN]) {
   1196   KitContext ctx = driver_env_to_context(env);
   1197   KitCasHost cas_host = driver_cas_host(env);
   1198   KitCas* cas = NULL;
   1199   char cas_root[KIT_BUILD_PATH_MAX];
   1200   KitStatus st;
   1201   if (build_join_path(cas_root, sizeof cas_root, store, "cas") != 0) {
   1202     driver_errf(BUILD_TOOL, "store path is too long");
   1203     return 1;
   1204   }
   1205   st = kit_cas_open(&ctx, &cas_host, cas_root, &cas);
   1206   if (st == KIT_OK) st = kit_cas_verify_tree(cas, tree);
   1207   if (cas) kit_cas_close(cas);
   1208   if (st != KIT_OK) {
   1209     driver_errf(BUILD_TOOL, "output tree verification failed: %s",
   1210                 build_status_name(st));
   1211     return 1;
   1212   }
   1213   return 0;
   1214 }
   1215 
   1216 static int build_find_top_command(int argc, char** argv) {
   1217   int i;
   1218   for (i = 1; i < argc; ++i) {
   1219     if ((driver_streq(argv[i], "--store") || driver_streq(argv[i], "--root") ||
   1220          driver_streq(argv[i], "--def") || driver_streq(argv[i], "--def-name") ||
   1221          driver_streq(argv[i], "--profile") || driver_streq(argv[i], "--config") ||
   1222          driver_streq(argv[i], "--env")) &&
   1223         i + 1 < argc) {
   1224       ++i;
   1225       continue;
   1226     }
   1227     if (driver_streq(argv[i], "--stats") || driver_streq(argv[i], "--trace") ||
   1228         driver_streq(argv[i], "--verify"))
   1229       continue;
   1230     if (driver_streq(argv[i], "list") || driver_streq(argv[i], "repo") ||
   1231         driver_streq(argv[i], "workspace"))
   1232       return i;
   1233     return -1;
   1234   }
   1235   return -1;
   1236 }
   1237 
   1238 static void build_global_option_defaults(int argc, char** argv, int cmd_index,
   1239                                          const char** root,
   1240                                          const char** def_name,
   1241                                          const char** store) {
   1242   int i;
   1243   (void)argc;
   1244   *root = ".";
   1245   *def_name = BUILD_DEFAULT_DEF;
   1246   *store = NULL;
   1247   for (i = 1; i < cmd_index; ++i) {
   1248     if (driver_streq(argv[i], "--root") && i + 1 < cmd_index) {
   1249       *root = argv[++i];
   1250     } else if ((driver_streq(argv[i], "--def") ||
   1251                 driver_streq(argv[i], "--def-name")) &&
   1252                i + 1 < cmd_index) {
   1253       *def_name = argv[++i];
   1254     } else if (driver_streq(argv[i], "--store") && i + 1 < cmd_index) {
   1255       *store = argv[++i];
   1256     } else if ((driver_streq(argv[i], "--profile") ||
   1257                 driver_streq(argv[i], "--config") ||
   1258                 driver_streq(argv[i], "--env")) &&
   1259                i + 1 < cmd_index) {
   1260       ++i;
   1261     }
   1262   }
   1263 }
   1264 
   1265 static int build_cmd_list(int argc, char** argv, int cmd_index) {
   1266   DriverEnv env;
   1267   const char *root, *def_name, *store_ignored;
   1268   const char* pattern = "//...";
   1269   int scan_do = 0;
   1270   int i, recursive = 0;
   1271   char pkg[KIT_BUILD_PATH_MAX];
   1272   BuildListTarget targets[512];
   1273   size_t ntargets = 0, j;
   1274   (void)store_ignored;
   1275   build_global_option_defaults(argc, argv, cmd_index, &root, &def_name,
   1276                                &store_ignored);
   1277   if (!build_def_name_valid(def_name)) {
   1278     driver_errf(BUILD_TOOL, "bad def-name");
   1279     return 2;
   1280   }
   1281   for (i = cmd_index + 1; i < argc; ++i) {
   1282     if (driver_streq(argv[i], "--scan-do")) {
   1283       scan_do = 1;
   1284     } else if (argv[i][0] == '-') {
   1285       driver_errf(BUILD_TOOL, "unexpected list option: %s", argv[i]);
   1286       return 2;
   1287     } else {
   1288       pattern = argv[i];
   1289     }
   1290   }
   1291   memset(pkg, 0, sizeof pkg);
   1292   if (!driver_strneq(pattern, "//", 2u)) {
   1293     driver_errf(BUILD_TOOL, "list pattern must be //pkg or //pkg/...");
   1294     return 2;
   1295   }
   1296   {
   1297     const char* body = pattern + 2u;
   1298     size_t len = driver_strlen(body);
   1299     if (len >= 4u && driver_streq(body + len - 4u, "/...")) {
   1300       recursive = 1;
   1301       len -= 4u;
   1302     }
   1303     if (len >= sizeof pkg) return 2;
   1304     memcpy(pkg, body, len);
   1305     pkg[len] = '\0';
   1306   }
   1307   driver_env_init(&env);
   1308   if (scan_do) {
   1309     char pkg_dir[KIT_BUILD_PATH_MAX], do_dir[KIT_BUILD_PATH_MAX];
   1310     DriverDirHandle* dh;
   1311     if (build_join_path(pkg_dir, sizeof pkg_dir, root, pkg) != 0 ||
   1312         build_join_path(do_dir, sizeof do_dir, pkg_dir, "do") != 0) {
   1313       driver_env_fini(&env);
   1314       return 1;
   1315     }
   1316     dh = driver_open_dir(&env, do_dir);
   1317     if (dh) {
   1318       uint64_t idx;
   1319       for (idx = 0;; ++idx) {
   1320         const char* name;
   1321         uint32_t name_len;
   1322         uint64_t ino, size, mtime;
   1323         uint8_t ftype;
   1324         if (driver_read_dir_entry(dh, idx, &name, &name_len, &ino, &size,
   1325                                   &mtime, &ftype) != 0)
   1326           break;
   1327         (void)ino;
   1328         (void)size;
   1329         (void)mtime;
   1330         if (ftype == 4u && name_len > 3u &&
   1331             memcmp(name + name_len - 3u, ".do", 3u) == 0)
   1332           driver_printf("//%s:%.*s %.*s\n", pkg, (int)name_len, name,
   1333                         (int)name_len, name);
   1334       }
   1335       driver_close_dir(&env, dh);
   1336     }
   1337   } else if (build_list_package(&env, root, def_name, pkg, recursive, targets,
   1338                                 &ntargets,
   1339                                 sizeof targets / sizeof targets[0]) != 0) {
   1340     driver_env_fini(&env);
   1341     return 1;
   1342   }
   1343   qsort(targets, ntargets, sizeof targets[0], build_list_cmp);
   1344   for (j = 0; j < ntargets; ++j) driver_printf("%s\n", targets[j].label);
   1345   driver_env_fini(&env);
   1346   return 0;
   1347 }
   1348 
   1349 static int build_cmd_repo(int argc, char** argv, int cmd_index) {
   1350   DriverEnv env;
   1351   const char *root, *def_name_ignored, *store_ignored;
   1352   KitBuildWorkspace ws;
   1353   int present = 0;
   1354   size_t i;
   1355   (void)def_name_ignored;
   1356   (void)store_ignored;
   1357   if (argc <= cmd_index + 1) {
   1358     driver_errf(BUILD_TOOL, "usage: kit build repo {add|fetch|list}");
   1359     return 2;
   1360   }
   1361   build_global_option_defaults(argc, argv, cmd_index, &root, &def_name_ignored,
   1362                                &store_ignored);
   1363   driver_env_init(&env);
   1364   if (build_load_workspace(&env, root, &ws, &present) != 0) {
   1365     driver_env_fini(&env);
   1366     return 1;
   1367   }
   1368   if (driver_streq(argv[cmd_index + 1], "list")) {
   1369     for (i = 0; i < ws.n_externals; ++i) {
   1370       char hex[KIT_BUILD_HEX_LEN];
   1371       const char* fmt = ws.externals[i].format == KIT_BUILD_WS_EXT_TREE
   1372                             ? "tree"
   1373                             : ws.externals[i].format == KIT_BUILD_WS_EXT_TARGZ
   1374                                   ? "tar.gz"
   1375                                   : "kpkg";
   1376       kit_hex_encode(hex, ws.externals[i].archive, KIT_BUILD_HASH_LEN);
   1377       driver_printf("%s %s %s\n", ws.externals[i].name, fmt, hex);
   1378     }
   1379   } else if (driver_streq(argv[cmd_index + 1], "fetch")) {
   1380     if (argc <= cmd_index + 2) {
   1381       driver_errf(BUILD_TOOL, "usage: kit build repo fetch NAME");
   1382       driver_env_fini(&env);
   1383       return 2;
   1384     }
   1385     if (!kit_build_workspace_external_find(&ws, kit_slice_cstr(argv[cmd_index + 2]))) {
   1386       driver_errf(BUILD_TOOL, "unknown external repo: %s", argv[cmd_index + 2]);
   1387       driver_env_fini(&env);
   1388       return 1;
   1389     }
   1390     driver_printf("%s\n", argv[cmd_index + 2]);
   1391   } else if (driver_streq(argv[cmd_index + 1], "add")) {
   1392     const char* name;
   1393     const char* url;
   1394     const char* src;
   1395     const char* format = "tar.gz";
   1396     const char* strip = NULL;
   1397     char ws_path[KIT_BUILD_PATH_MAX], line[2048], hex[KIT_BUILD_HEX_LEN];
   1398     DriverLoad load;
   1399     KitSlice bytes;
   1400     KitBlobInfo bi;
   1401     int a;
   1402     if (argc <= cmd_index + 3) {
   1403       driver_errf(BUILD_TOOL, "usage: kit build repo add NAME URL");
   1404       driver_env_fini(&env);
   1405       return 2;
   1406     }
   1407     name = argv[cmd_index + 2];
   1408     url = argv[cmd_index + 3];
   1409     for (a = cmd_index + 4; a < argc; ++a) {
   1410       if (driver_streq(argv[a], "--format") && a + 1 < argc) {
   1411         format = argv[++a];
   1412       } else if (driver_streq(argv[a], "--strip-prefix") && a + 1 < argc) {
   1413         strip = argv[++a];
   1414       }
   1415     }
   1416     if (build_file_url_path(url, &src) != 0 ||
   1417         build_load_file(&env, src, &load, &bytes) != 0) {
   1418       driver_env_fini(&env);
   1419       return 1;
   1420     }
   1421     kit_blob_info(&bi, bytes.data, bytes.len);
   1422     driver_release_bytes(&env.file_io, &load);
   1423     kit_hex_encode(hex, bi.id, KIT_BUILD_HASH_LEN);
   1424     if (build_join_path(ws_path, sizeof ws_path, root, "WORKSPACE.kit") != 0) {
   1425       driver_env_fini(&env);
   1426       return 1;
   1427     }
   1428     snprintf(line, sizeof line,
   1429              "\n[external %s]\nformat %s\narchive %s\nurl %s\n", name, format,
   1430              hex, url);
   1431     if (build_file_append(&env, ws_path, line) != 0 ||
   1432         (strip && (snprintf(line, sizeof line, "strip-prefix %s\n", strip),
   1433                    build_file_append(&env, ws_path, line) != 0))) {
   1434       driver_env_fini(&env);
   1435       return 1;
   1436     }
   1437     driver_printf("%s %s\n", name, hex);
   1438   } else {
   1439     driver_errf(BUILD_TOOL, "unknown repo command: %s", argv[cmd_index + 1]);
   1440     driver_env_fini(&env);
   1441     return 2;
   1442   }
   1443   driver_env_fini(&env);
   1444   return 0;
   1445 }
   1446 
   1447 static int build_cmd_workspace(int argc, char** argv, int cmd_index) {
   1448   DriverEnv env;
   1449   DriverBuildHost bh;
   1450   const char *root, *def_name_ignored, *store_ignored;
   1451   const char* out = NULL;
   1452   const char* format = "tar.gz";
   1453   const char* tar_paths[] = {"/usr/bin/tar", "/bin/tar"};
   1454   KitBuildKV envv[1];
   1455   KitBuildProc* proc = NULL;
   1456   int exit_code = 127;
   1457   int ran = 0;
   1458   int i;
   1459   (void)def_name_ignored;
   1460   (void)store_ignored;
   1461   if (argc <= cmd_index + 1 || !driver_streq(argv[cmd_index + 1], "package")) {
   1462     driver_errf(BUILD_TOOL, "usage: kit build workspace package -o OUT");
   1463     return 2;
   1464   }
   1465   build_global_option_defaults(argc, argv, cmd_index, &root, &def_name_ignored,
   1466                                &store_ignored);
   1467   for (i = cmd_index + 2; i < argc; ++i) {
   1468     if (driver_streq(argv[i], "--format") && i + 1 < argc)
   1469       format = argv[++i];
   1470     else if (driver_streq(argv[i], "-o") && i + 1 < argc)
   1471       out = argv[++i];
   1472   }
   1473   if (!out || !driver_streq(format, "tar.gz")) {
   1474     driver_errf(BUILD_TOOL, "workspace package supports --format tar.gz -o OUT");
   1475     return 2;
   1476   }
   1477   driver_env_init(&env);
   1478   if (driver_build_host_init(&bh, &env) != 0) {
   1479     driver_env_fini(&env);
   1480     return 1;
   1481   }
   1482   envv[0].key = kit_slice_cstr("COPYFILE_DISABLE");
   1483   envv[0].value = kit_slice_cstr("1");
   1484   for (i = 0; i < (int)(sizeof tar_paths / sizeof tar_paths[0]); ++i) {
   1485     KitSlice tar_argv[6];
   1486     tar_argv[0] = kit_slice_cstr(tar_paths[i]);
   1487     tar_argv[1] = kit_slice_cstr("--format");
   1488     tar_argv[2] = kit_slice_cstr("ustar");
   1489     tar_argv[3] = kit_slice_cstr("-czf");
   1490     tar_argv[4] = kit_slice_cstr(out);
   1491     tar_argv[5] = kit_slice_cstr(".");
   1492     proc = NULL;
   1493     {
   1494       KitExecOpts eo;
   1495       memset(&eo, 0, sizeof eo);
   1496       eo.argv = tar_argv;
   1497       eo.argc = 6u;
   1498       eo.env = envv;
   1499       eo.nenv = 1u;
   1500       eo.cwd = kit_slice_cstr(root);
   1501       if (bh.exec.spawn(bh.exec.user, &eo, &proc) != 0) continue;
   1502     }
   1503     ran = 1;
   1504     if (bh.exec.wait(bh.exec.user, proc, &exit_code, NULL, NULL) != 0) {
   1505       driver_build_host_fini(&bh);
   1506       driver_env_fini(&env);
   1507       return 1;
   1508     }
   1509     if (exit_code != 127) break;
   1510   }
   1511   driver_build_host_fini(&bh);
   1512   driver_env_fini(&env);
   1513   if (!ran || exit_code != 0) {
   1514     driver_errf(BUILD_TOOL, "workspace package failed");
   1515     return 1;
   1516   }
   1517   driver_printf("%s\n", out);
   1518   return 0;
   1519 }
   1520 
   1521 int driver_build(int argc, char** argv) {
   1522   DriverEnv env;
   1523   DriverBuildHost bh;
   1524   BuildCli cli;
   1525   KitContext ctx;
   1526   KitBuildOptions opts;
   1527   KitBuildRequest req;
   1528   KitBuildResult result;
   1529   KitBuildTestResult test_result;
   1530   KitBuildCoordinator* coord = NULL;
   1531   KitStatus st;
   1532   char* default_store = NULL;
   1533   size_t default_store_size = 0;
   1534   char* abs_store = NULL;
   1535   char* abs_root = NULL;
   1536   size_t abs_store_size = 0;
   1537   size_t abs_root_size = 0;
   1538   char hex[2u * KIT_BUILD_HASH_LEN + 1u];
   1539   int rc;
   1540 
   1541   if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
   1542     driver_help_build();
   1543     return 0;
   1544   }
   1545   if (argc >= 2 && driver_streq(argv[1], "--client"))
   1546     return build_client_mode(argc, argv, 2);
   1547   if (argc >= 2 && driver_getenv(KIT_BUILD_ENV_SOCK) &&
   1548       build_client_verb(argv[1]))
   1549     return build_client_mode(argc, argv, 1);
   1550   {
   1551     int cmd_index = build_find_top_command(argc, argv);
   1552     if (cmd_index >= 0) {
   1553       if (driver_streq(argv[cmd_index], "list"))
   1554         return build_cmd_list(argc, argv, cmd_index);
   1555       if (driver_streq(argv[cmd_index], "repo"))
   1556         return build_cmd_repo(argc, argv, cmd_index);
   1557       if (driver_streq(argv[cmd_index], "workspace"))
   1558         return build_cmd_workspace(argc, argv, cmd_index);
   1559     }
   1560   }
   1561 
   1562   rc = build_parse_args(&cli, argc, argv,
   1563                         argc >= 2 && driver_streq(argv[1], "test") ? 2 : 1,
   1564                         argc >= 2 && driver_streq(argv[1], "test"));
   1565   if (rc != 0) return rc;
   1566 
   1567   driver_env_init(&env);
   1568   if (!cli.store) {
   1569     default_store = driver_path_join(&env, env.cache_dir, "build",
   1570                                      &default_store_size);
   1571     if (!default_store) {
   1572       driver_errf(BUILD_TOOL, "out of memory");
   1573       driver_env_fini(&env);
   1574       return 1;
   1575     }
   1576     cli.store = default_store;
   1577   }
   1578 
   1579   ctx = driver_env_to_context(&env);
   1580   if (driver_build_host_init(&bh, &env) != 0) {
   1581     driver_errf(BUILD_TOOL, "hosted build adapter is unavailable");
   1582     rc = 1;
   1583     goto out_env;
   1584   }
   1585   abs_store = driver_build_host_abs_path(&env, cli.store, &abs_store_size);
   1586   abs_root = driver_build_host_abs_path(&env, cli.root, &abs_root_size);
   1587   if (!abs_store || !abs_root) {
   1588     driver_errf(BUILD_TOOL, "failed to resolve build paths");
   1589     rc = 1;
   1590     goto out_host_only;
   1591   }
   1592 
   1593   memset(&opts, 0, sizeof opts);
   1594   opts.workspace_root = kit_slice_cstr(abs_root);
   1595   opts.build_def_path = cli.def ? kit_slice_cstr(cli.def) : KIT_SLICE_NULL;
   1596   opts.profile = cli.profile ? kit_slice_cstr(cli.profile) : KIT_SLICE_NULL;
   1597   opts.jobs = 1;
   1598   opts.verify = cli.verify;
   1599   if (cli.trace) opts.trace = build_trace_stderr;
   1600 
   1601   st = kit_build_coordinator_open(&ctx, &bh.host, kit_slice_cstr(abs_store),
   1602                                   &opts, &coord);
   1603   if (st != KIT_OK) {
   1604     driver_errf(BUILD_TOOL, "failed to open coordinator: %s",
   1605                 build_status_name(st));
   1606     rc = st == KIT_INVALID || st == KIT_MALFORMED ? 2 : 1;
   1607     goto out;
   1608   }
   1609 
   1610   memset(&req, 0, sizeof req);
   1611   req.target = kit_slice_cstr(cli.target);
   1612   req.config = cli.config;
   1613   req.nconfig = cli.nconfig;
   1614   req.argv = cli.args;
   1615   req.argc = cli.nargs;
   1616   memset(&result, 0, sizeof result);
   1617   memset(&test_result, 0, sizeof test_result);
   1618   st = cli.test_mode ? kit_build_test(coord, &req, &test_result)
   1619                      : kit_build(coord, &req, &result);
   1620   if (st != KIT_OK) {
   1621     driver_errf(BUILD_TOOL, "%s failed: %s",
   1622                 cli.test_mode ? "build test" : "build",
   1623                 build_status_name(st));
   1624     rc = 1;
   1625     goto out;
   1626   }
   1627   if (cli.stats) {
   1628     KitBuildStats stats;
   1629     kit_build_stats(coord, &stats);
   1630     build_print_stats(&stats);
   1631   }
   1632 
   1633   if (cli.verify &&
   1634       build_verify_tree(&env, abs_store,
   1635                         cli.test_mode ? test_result.result_tree
   1636                                       : result.output_tree) != 0) {
   1637     rc = 1;
   1638     goto out;
   1639   }
   1640 
   1641   if (cli.test_mode) {
   1642     kit_hex_encode(hex, test_result.result_tree, KIT_BUILD_HASH_LEN);
   1643     driver_printf("%s %s %s\n",
   1644                   test_result.status == KIT_TEST_PASS ? "PASS" : "FAIL", hex,
   1645                   test_result.path);
   1646     rc = test_result.status == KIT_TEST_PASS ? 0 : 1;
   1647   } else {
   1648     kit_hex_encode(hex, result.output_tree, KIT_BUILD_HASH_LEN);
   1649     driver_printf("%s %s\n", hex, result.path);
   1650     rc = 0;
   1651   }
   1652 
   1653 out:
   1654   if (coord) kit_build_coordinator_close(coord);
   1655 out_host_only:
   1656   if (abs_store) driver_free(&env, abs_store, abs_store_size);
   1657   if (abs_root) driver_free(&env, abs_root, abs_root_size);
   1658   driver_build_host_fini(&bh);
   1659 out_env:
   1660   if (default_store) driver_free(&env, default_store, default_store_size);
   1661   driver_env_fini(&env);
   1662   return rc;
   1663 }