kit

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

runner.c (41742B)


      1 #include "runner.h"
      2 
      3 #include "protocol.h"
      4 
      5 #include <stdio.h>
      6 #include <string.h>
      7 
      8 static int path_join2(char* out, size_t cap, const char* a, const char* b) {
      9   size_t na, nb;
     10   int need_sep;
     11   if (!out || cap == 0u || !a || !b) return BUILD_ERR;
     12   na = strlen(a);
     13   nb = strlen(b);
     14   need_sep = na > 0u && a[na - 1u] != '/';
     15   if (na + (need_sep ? 1u : 0u) + nb + 1u > cap) return BUILD_ERR;
     16   memcpy(out, a, na);
     17   if (need_sep) out[na++] = '/';
     18   memcpy(out + na, b, nb);
     19   out[na + nb] = '\0';
     20   return BUILD_OK;
     21 }
     22 
     23 static int target_copy(KitSlice target, char out[BUILD_TARGET_MAX]) {
     24   if (!target.s || target.len == 0u || target.len >= BUILD_TARGET_MAX)
     25     return BUILD_ERR;
     26   memcpy(out, target.s, target.len);
     27   out[target.len] = '\0';
     28   return BUILD_OK;
     29 }
     30 
     31 typedef struct BuildRunScratch {
     32   BuildConfigLeaf configs[BUILD_RECIPE_CONFIG_CAP];
     33   BuildSourceLeaf sources[BUILD_RECIPE_SOURCE_CAP];
     34   BuildGlobLeaf globs[BUILD_RECIPE_GLOB_CAP];
     35   BuildBlobLeaf blobs[BUILD_RECIPE_BLOB_CAP];
     36   BuildDepEdge deps[BUILD_RECIPE_DEP_CAP];
     37   const BuildLeafSet* child_leafsets[BUILD_RECIPE_DEP_CAP];
     38   BuildPendingNeed pending[BUILD_RECIPE_DEP_CAP];
     39 } BuildRunScratch;
     40 
     41 static BuildRunScratch* build_run_scratch_new(KitBuildCoordinator* c) {
     42   BuildRunScratch* s;
     43   if (!c || !c->ctx || !c->ctx->heap) return NULL;
     44   s = (BuildRunScratch*)c->ctx->heap->alloc(c->ctx->heap, sizeof *s,
     45                                             _Alignof(BuildRunScratch));
     46   if (s) memset(s, 0, sizeof *s);
     47   return s;
     48 }
     49 
     50 static void build_run_scratch_free(KitBuildCoordinator* c,
     51                                    BuildRunScratch* s) {
     52   if (c && c->ctx && c->ctx->heap && s)
     53     c->ctx->heap->free(c->ctx->heap, s, sizeof *s);
     54 }
     55 
     56 static void build_dep_log_init(BuildDepLog* log, BuildRunScratch* scratch) {
     57   memset(log, 0, sizeof *log);
     58   log->configs = scratch->configs;
     59   log->cap_configs = sizeof scratch->configs / sizeof scratch->configs[0];
     60   log->sources = scratch->sources;
     61   log->cap_sources = sizeof scratch->sources / sizeof scratch->sources[0];
     62   log->globs = scratch->globs;
     63   log->cap_globs = sizeof scratch->globs / sizeof scratch->globs[0];
     64   log->blobs = scratch->blobs;
     65   log->cap_blobs = sizeof scratch->blobs / sizeof scratch->blobs[0];
     66   log->deps = scratch->deps;
     67   log->cap_deps = sizeof scratch->deps / sizeof scratch->deps[0];
     68   log->child_leafsets = scratch->child_leafsets;
     69   log->cap_children =
     70       sizeof scratch->child_leafsets / sizeof scratch->child_leafsets[0];
     71   log->pending = scratch->pending;
     72   log->cap_pending = sizeof scratch->pending / sizeof scratch->pending[0];
     73   log->next_token = 1;
     74 }
     75 
     76 static void hash_slice(KitSlice s, uint8_t out[BUILD_HASH_LEN]) {
     77   KitBlobInfo bi;
     78   static const uint8_t empty = 0;
     79   kit_blob_info(&bi, s.len ? s.data : &empty, s.len);
     80   memcpy(out, bi.id, BUILD_HASH_LEN);
     81 }
     82 
     83 static int config_leaf_eq(const BuildConfigLeaf* a, const BuildConfigLeaf* b) {
     84   return strcmp(a->key, b->key) == 0 && a->present == b->present &&
     85          a->has_default == b->has_default &&
     86          build_id_eq(a->value_hash, b->value_hash) &&
     87          build_id_eq(a->default_hash, b->default_hash);
     88 }
     89 
     90 static int append_config_observation(BuildDepLog* log, const BuildConfig* cfg,
     91                                      KitSlice key, KitSlice default_value,
     92                                      int has_default) {
     93   BuildConfigLeaf leaf;
     94   BuildConfigLeaf* row;
     95   KitSlice actual;
     96   size_t i;
     97   if (!log || !key.s || key.len == 0u || key.len >= BUILD_KEY_MAX)
     98     return BUILD_ERR;
     99   if (has_default &&
    100       (default_value.len >= BUILD_VAL_MAX ||
    101        (default_value.len && !default_value.s)))
    102     return BUILD_ERR;
    103   memset(&leaf, 0, sizeof leaf);
    104   memcpy(leaf.key, key.s, key.len);
    105   leaf.key[key.len] = '\0';
    106   actual = KIT_SLICE_NULL;
    107   if (build_config_get(cfg, key, &actual, &leaf.present) != BUILD_OK)
    108     return BUILD_ERR;
    109   leaf.has_default = has_default ? 1 : 0;
    110   if (leaf.present) {
    111     hash_slice(actual, leaf.value_hash);
    112   } else if (leaf.has_default) {
    113     hash_slice(default_value, leaf.value_hash);
    114   }
    115   if (leaf.has_default) hash_slice(default_value, leaf.default_hash);
    116   for (i = 0; i < log->n_configs; ++i) {
    117     if (config_leaf_eq(&log->configs[i], &leaf))
    118       return BUILD_OK;
    119   }
    120   if (log->n_configs >= log->cap_configs) return BUILD_ERR;
    121   row = &log->configs[log->n_configs++];
    122   *row = leaf;
    123   return BUILD_OK;
    124 }
    125 
    126 static int append_source(BuildDepLog* log, KitSlice path,
    127                          const uint8_t blob[BUILD_HASH_LEN], int present) {
    128   BuildSourceLeaf* row;
    129   size_t i;
    130   if (!log || !path.s || path.len == 0u || path.len >= BUILD_PATH_MAX)
    131     return BUILD_ERR;
    132   for (i = 0; i < log->n_sources; ++i) {
    133     row = &log->sources[i];
    134     if (strlen(row->path) == path.len &&
    135         memcmp(row->path, path.s, path.len) == 0) {
    136       int absent = present ? 0 : 1;
    137       if (row->absent != absent) return BUILD_ERR;
    138       if (!row->absent && !build_id_eq(row->blob, blob)) return BUILD_ERR;
    139       return BUILD_OK;
    140     }
    141   }
    142   if (log->n_sources >= log->cap_sources) return BUILD_ERR;
    143   row = &log->sources[log->n_sources++];
    144   memcpy(row->path, path.s, path.len);
    145   row->path[path.len] = '\0';
    146   row->absent = present ? 0 : 1;
    147   if (present)
    148     memcpy(row->blob, blob, BUILD_HASH_LEN);
    149   else
    150     memset(row->blob, 0, BUILD_HASH_LEN);
    151   return BUILD_OK;
    152 }
    153 
    154 static int append_glob(BuildDepLog* log, KitSlice pattern,
    155                        const uint8_t result_hash[BUILD_HASH_LEN]) {
    156   BuildGlobLeaf* row;
    157   size_t i;
    158   if (!log || !pattern.s || pattern.len == 0u ||
    159       pattern.len >= BUILD_PATTERN_MAX || !result_hash)
    160     return BUILD_ERR;
    161   for (i = 0; i < log->n_globs; ++i) {
    162     row = &log->globs[i];
    163     if (strlen(row->pattern) == pattern.len &&
    164         memcmp(row->pattern, pattern.s, pattern.len) == 0) {
    165       if (!build_id_eq(row->result_hash, result_hash)) return BUILD_ERR;
    166       return BUILD_OK;
    167     }
    168   }
    169   if (log->n_globs >= log->cap_globs) return BUILD_ERR;
    170   row = &log->globs[log->n_globs++];
    171   memcpy(row->pattern, pattern.s, pattern.len);
    172   row->pattern[pattern.len] = '\0';
    173   memcpy(row->result_hash, result_hash, BUILD_HASH_LEN);
    174   return BUILD_OK;
    175 }
    176 
    177 static int append_blob(BuildDepLog* log,
    178                         const uint8_t blob[BUILD_HASH_LEN]) {
    179   BuildBlobLeaf* row;
    180   size_t i;
    181   if (!log || !blob) return BUILD_ERR;
    182   for (i = 0; i < log->n_blobs; ++i) {
    183     row = &log->blobs[i];
    184     if (build_id_eq(row->blob, blob)) return BUILD_OK;
    185   }
    186   if (log->n_blobs >= log->cap_blobs) return BUILD_ERR;
    187   row = &log->blobs[log->n_blobs++];
    188   memcpy(row->blob, blob, BUILD_HASH_LEN);
    189   return BUILD_OK;
    190 }
    191 
    192 static int append_dep(BuildDepLog* log, KitSlice target,
    193                       const uint8_t overlay_id[BUILD_HASH_LEN],
    194                       const uint8_t argv_id[BUILD_HASH_LEN],
    195                       const BuildResolved* r) {
    196   BuildDepEdge* row;
    197   size_t i;
    198   if (!log || !target.s || target.len == 0u || target.len >= BUILD_TARGET_MAX ||
    199       !overlay_id || !argv_id || !r || !r->leafset)
    200     return BUILD_ERR;
    201   for (i = 0; i < log->n_deps; ++i) {
    202     row = &log->deps[i];
    203     if (strlen(row->name) == target.len &&
    204         memcmp(row->name, target.s, target.len) == 0 &&
    205         build_id_eq(row->overlay_id, overlay_id) &&
    206         build_id_eq(row->argv_id, argv_id) &&
    207         build_id_eq(row->output_tree, r->output_tree))
    208       return BUILD_OK;
    209   }
    210   if (log->n_deps >= log->cap_deps ||
    211       log->n_children >= log->cap_children)
    212     return BUILD_ERR;
    213   row = &log->deps[log->n_deps++];
    214   memcpy(row->name, target.s, target.len);
    215   row->name[target.len] = '\0';
    216   memcpy(row->overlay_id, overlay_id, BUILD_HASH_LEN);
    217   memcpy(row->argv_id, argv_id, BUILD_HASH_LEN);
    218   memcpy(row->output_tree, r->output_tree, BUILD_HASH_LEN);
    219   log->child_leafsets[log->n_children++] = r->leafset;
    220   return BUILD_OK;
    221 }
    222 
    223 static int write_resp(KitBuildCoordinator* c, KitBuildConn* conn,
    224                       const BuildReq* req, const BuildResp* resp);
    225 
    226 typedef struct BuildGlobStream {
    227   KitBuildCoordinator* c;
    228   KitBuildConn* conn;
    229   BuildReq req;
    230   int failed;
    231 } BuildGlobStream;
    232 
    233 static int glob_stream_cb(void* user, const char* path,
    234                           const uint8_t blob[BUILD_HASH_LEN]) {
    235   BuildGlobStream* s = (BuildGlobStream*)user;
    236   BuildResp resp;
    237   (void)blob;
    238   if (!s || !path) return 1;
    239   memset(&resp, 0, sizeof resp);
    240   resp.status = BUILD_RESP_OK;
    241   resp.text = kit_slice_cstr(path);
    242   if (write_resp(s->c, s->conn, &s->req, &resp) != BUILD_OK) {
    243     s->failed = 1;
    244     return 1;
    245   }
    246   return 0;
    247 }
    248 
    249 static BuildPendingNeed* find_pending(BuildDepLog* log, uint64_t token) {
    250   size_t i;
    251   if (!log) return NULL;
    252   for (i = 0; i < log->n_pending; ++i)
    253     if (log->pending[i].token == token) return &log->pending[i];
    254   return NULL;
    255 }
    256 
    257 static int write_resp(KitBuildCoordinator* c, KitBuildConn* conn,
    258                       const BuildReq* req, const BuildResp* resp) {
    259   uint8_t frame[BUILD_FRAME_MAX];
    260   size_t n = 0;
    261   if (!c || !conn || !req || !resp) return BUILD_ERR;
    262   if (build_proto_encode_resp(req, resp, frame, sizeof frame, &n) != BUILD_OK)
    263     return BUILD_ERR;
    264   return c->host.transport->write_frame(c->host.transport->user, conn, frame,
    265                                         n) == 0
    266              ? BUILD_OK
    267              : BUILD_ERR;
    268 }
    269 
    270 static void resp_error(BuildResp* resp, KitStatus st, const char* msg) {
    271   memset(resp, 0, sizeof *resp);
    272   resp->status = BUILD_RESP_ERROR;
    273   resp->error_status = (uint16_t)st;
    274   resp->text = kit_slice_cstr(msg ? msg : "build request failed");
    275 }
    276 
    277 static int store_config_blob(KitBuildCoordinator* c, const BuildConfig* cfg,
    278                              uint8_t out[BUILD_HASH_LEN]) {
    279   KitWriter* w = NULL;
    280   const uint8_t* bytes;
    281   size_t len;
    282   KitBlobInfo bi;
    283   int ok = BUILD_ERR;
    284   if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR;
    285   if (build_config_emit(cfg, w) != BUILD_OK || kit_writer_status(w) != KIT_OK)
    286     goto out_close;
    287   bytes = kit_writer_mem_bytes(w, &len);
    288   if (kit_cas_add_blob(c->cas, bytes, len, &bi) != KIT_OK) goto out_close;
    289   memcpy(out, bi.id, BUILD_HASH_LEN);
    290   ok = BUILD_OK;
    291 out_close:
    292   kit_writer_close(w);
    293   return ok;
    294 }
    295 
    296 static int store_argv_blob(KitBuildCoordinator* c, const BuildArgv* argv,
    297                            uint8_t out[BUILD_HASH_LEN]) {
    298   KitWriter* w = NULL;
    299   const uint8_t* bytes;
    300   size_t len;
    301   KitBlobInfo bi;
    302   int ok = BUILD_ERR;
    303   if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR;
    304   if (build_argv_emit(argv, w) != BUILD_OK || kit_writer_status(w) != KIT_OK)
    305     goto out_close;
    306   bytes = kit_writer_mem_bytes(w, &len);
    307   if (kit_cas_add_blob(c->cas, bytes, len, &bi) != KIT_OK) goto out_close;
    308   memcpy(out, bi.id, BUILD_HASH_LEN);
    309   ok = BUILD_OK;
    310 out_close:
    311   kit_writer_close(w);
    312   return ok;
    313 }
    314 
    315 static int service_recipe_connections(KitBuildCoordinator* c,
    316                                       KitBuildListener* listener,
    317                                       KitBuildProc** proc, KitSlice target,
    318                                       const BuildConfig* cfg,
    319                                       const BuildChainFrame* chain,
    320                                       BuildDepLog* log, int* exit_code) {
    321   int done = 0;
    322   KitBuildConn* conn = NULL;
    323   if (!c || !listener || !proc || !*proc || !cfg || !log || !exit_code)
    324     return BUILD_ERR;
    325   if (!c->host.transport->try_accept || !c->host.exec->poll) {
    326     if (c->host.transport->accept(c->host.transport->user, listener, &conn) !=
    327             0 ||
    328         !conn)
    329       return BUILD_ERR;
    330     if (build_runner_service(c, conn, target, cfg, chain, log) != BUILD_OK) {
    331       c->host.transport->close(c->host.transport->user, conn);
    332       return BUILD_ERR;
    333     }
    334     c->host.transport->close(c->host.transport->user, conn);
    335     if (c->host.exec->wait(c->host.exec->user, *proc, exit_code, NULL, NULL) !=
    336         0)
    337       return BUILD_ERR;
    338     *proc = NULL;
    339     return BUILD_OK;
    340   }
    341   while (!done) {
    342     int ready = 0;
    343     conn = NULL;
    344     if (c->host.exec->poll(c->host.exec->user, *proc, &done, exit_code) != 0)
    345       return BUILD_ERR;
    346     if (done) break;
    347     if (c->host.transport->try_accept(c->host.transport->user, listener, &conn,
    348                                       &ready) != 0)
    349       return BUILD_ERR;
    350     if (!ready) continue;
    351     if (!conn) return BUILD_ERR;
    352     if (build_runner_service(c, conn, target, cfg, chain, log) != BUILD_OK) {
    353       c->host.transport->close(c->host.transport->user, conn);
    354       return BUILD_ERR;
    355     }
    356     c->host.transport->close(c->host.transport->user, conn);
    357   }
    358   *proc = NULL;
    359   return BUILD_OK;
    360 }
    361 
    362 static int write_file_bytes(KitBuildCoordinator* c, const char* path,
    363                             const uint8_t* data, size_t len) {
    364   KitWriter* w = NULL;
    365   KitStatus st;
    366   if (!c || !path || (!data && len) || !c->host.cas_host ||
    367       !c->host.cas_host->file_io || !c->host.cas_host->file_io->open_writer)
    368     return BUILD_ERR;
    369   if (c->host.cas_host->file_io->open_writer(c->host.cas_host->file_io->user,
    370                                              path, &w) != KIT_OK ||
    371       !w)
    372     return BUILD_ERR;
    373   st = len ? kit_writer_write(w, data, len) : KIT_OK;
    374   if (st == KIT_OK) st = kit_writer_status(w);
    375   kit_writer_close(w);
    376   return st == KIT_OK ? BUILD_OK : BUILD_ERR;
    377 }
    378 
    379 static int copy_capture_into_output(KitBuildCoordinator* c,
    380                                     const char* capture_path,
    381                                     const char* out_dir, const char* name,
    382                                     uint8_t blob[BUILD_HASH_LEN]) {
    383   KitFileData fd;
    384   KitBlobInfo bi;
    385   char dest[BUILD_PATH_MAX];
    386   int ok = BUILD_ERR;
    387   if (!c || !capture_path || !out_dir || !name || !blob) return BUILD_ERR;
    388   if (path_join2(dest, sizeof dest, out_dir, name) != BUILD_OK) return BUILD_ERR;
    389   fd.data = NULL;
    390   fd.size = 0;
    391   fd.token = NULL;
    392   if (!c->host.cas_host || !c->host.cas_host->file_io ||
    393       !c->host.cas_host->file_io->read_all ||
    394       c->host.cas_host->file_io->read_all(c->host.cas_host->file_io->user,
    395                                           capture_path, &fd) != KIT_OK)
    396     return BUILD_ERR;
    397   if (write_file_bytes(c, dest, fd.data, fd.size) != BUILD_OK ||
    398       kit_cas_add_blob(c->cas, fd.data, fd.size, &bi) != KIT_OK)
    399     goto out;
    400   memcpy(blob, bi.id, BUILD_HASH_LEN);
    401   ok = BUILD_OK;
    402 out:
    403   if (c->host.cas_host->file_io->release)
    404     c->host.cas_host->file_io->release(c->host.cas_host->file_io->user, &fd);
    405   return ok;
    406 }
    407 
    408 static int build_run_recipe_impl(KitBuildCoordinator* c, KitSlice target,
    409                                  const BuildConfig* cfg, const BuildArgv* argv,
    410                                  const BuildChainFrame* chain,
    411                                  BuildResolved* out, int record_traces) {
    412   BuildRecipeResolution recipe;
    413   KitBuildListener* listener = NULL;
    414   KitBuildConn* conn = NULL;
    415   KitBuildProc* proc = NULL;
    416   char endpoint[BUILD_PATH_MAX];
    417   char sandbox[BUILD_PATH_MAX];
    418   char out_dir[BUILD_PATH_MAX];
    419   KitSlice proc_argv[129];
    420   KitBuildKV env[140];
    421   char env_keys[128][BUILD_KEY_MAX];
    422   size_t argc = 0, nenv = 0, i, base_nenv = 0;
    423   int exit_code = 1;
    424   int ok = BUILD_ERR;
    425   BuildRunScratch* scratch = NULL;
    426   BuildDepLog log;
    427 
    428   if (!c || !cfg || !argv || !out || !c->host.exec || !c->host.transport)
    429     return BUILD_ERR;
    430   endpoint[0] = '\0';
    431   sandbox[0] = '\0';
    432   out_dir[0] = '\0';
    433   memset(&recipe, 0, sizeof recipe);
    434   if (build_coord_resolve_recipe(c, target, &recipe) != BUILD_OK)
    435     return BUILD_ERR;
    436   if (c->host.transport->listen(c->host.transport->user, endpoint,
    437                                 sizeof endpoint, &listener) != 0 ||
    438       !listener)
    439     return BUILD_ERR;
    440   if (build_store_sandbox_new(&c->store, sandbox, sizeof sandbox, out_dir,
    441                               sizeof out_dir) != BUILD_OK)
    442     goto out_cleanup;
    443 
    444   scratch = build_run_scratch_new(c);
    445   if (!scratch) goto out_cleanup;
    446   build_dep_log_init(&log, scratch);
    447 
    448   proc_argv[argc++] = kit_slice_cstr(recipe.recipe_abspath);
    449   for (i = 0; i < argv->n && argc < sizeof proc_argv / sizeof proc_argv[0];
    450        ++i)
    451     proc_argv[argc++] = kit_slice_cstr(argv->args[i]);
    452   if (i != argv->n) goto out_cleanup;
    453 
    454   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_SOCK);
    455   env[nenv++].value = kit_slice_cstr(endpoint);
    456   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_OUT);
    457   env[nenv++].value = kit_slice_cstr(out_dir);
    458   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_TARGET);
    459   env[nenv++].value = target;
    460   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_REPO);
    461   env[nenv++].value = kit_slice_cstr(recipe.repo);
    462   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_PACKAGE);
    463   env[nenv++].value = kit_slice_cstr(recipe.package);
    464   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_LOCAL);
    465   env[nenv++].value = kit_slice_cstr(recipe.local_name);
    466   base_nenv = nenv;
    467   for (i = 0; i < cfg->n && nenv < sizeof env / sizeof env[0] &&
    468               nenv - base_nenv < sizeof env_keys / sizeof env_keys[0];
    469        ++i) {
    470     const char* key = cfg->entries[i].key;
    471     size_t prefix_len = sizeof(KIT_BUILD_ENV_PREFIX) - 1u;
    472     if (strncmp(key, KIT_BUILD_ENV_PREFIX, prefix_len) != 0) continue;
    473     if (append_config_observation(&log, cfg, kit_slice_cstr(key),
    474                                   KIT_SLICE_NULL, 0) != BUILD_OK)
    475       goto out_cleanup;
    476     snprintf(env_keys[nenv - base_nenv],
    477              sizeof env_keys[nenv - base_nenv], "%s",
    478              key + prefix_len);
    479     env[nenv].key = kit_slice_cstr(env_keys[nenv - base_nenv]);
    480     env[nenv].value = kit_slice_cstr(cfg->entries[i].value);
    481     ++nenv;
    482   }
    483 
    484   {
    485     KitExecOpts eo;
    486     memset(&eo, 0, sizeof eo);
    487     eo.argv = proc_argv;
    488     eo.argc = argc;
    489     eo.env = env;
    490     eo.nenv = nenv;
    491     eo.cwd = kit_slice_cstr(recipe.workspace_root);
    492     if (c->host.exec->spawn(c->host.exec->user, &eo, &proc) != 0 || !proc)
    493       goto out_cleanup;
    494   }
    495   build_coord_stat_bump(c, BUILD_STAT_RECIPE_RUN);
    496   if (service_recipe_connections(c, listener, &proc, target, cfg, chain, &log,
    497                                  &exit_code) != BUILD_OK)
    498     goto out_kill;
    499   if (exit_code != 0) goto out_cleanup;
    500   if (build_store_ingest_output(&c->store, out_dir, out->output_tree, out->path,
    501                                 sizeof out->path) != BUILD_OK)
    502     goto out_cleanup;
    503   if (record_traces) {
    504     if (build_runner_record_traces(c, target, argv, &log,
    505                                    out->output_tree, &out->leafset) != BUILD_OK)
    506       goto out_cleanup;
    507   } else {
    508     out->leafset = NULL;
    509   }
    510   ok = BUILD_OK;
    511   goto out_cleanup;
    512 
    513 out_kill:
    514   if (proc && c->host.exec->kill) c->host.exec->kill(c->host.exec->user, proc);
    515   if (proc && c->host.exec->wait)
    516     (void)c->host.exec->wait(c->host.exec->user, proc, &exit_code, NULL, NULL);
    517   proc = NULL;
    518 out_cleanup:
    519   if (conn) c->host.transport->close(c->host.transport->user, conn);
    520   if (listener)
    521     c->host.transport->close_listener(c->host.transport->user, listener);
    522   if (sandbox[0]) build_store_sandbox_done(&c->store, sandbox);
    523   build_run_scratch_free(c, scratch);
    524   return ok;
    525 }
    526 
    527 static int build_run_test_recipe_impl(KitBuildCoordinator* c, KitSlice target,
    528                                       const BuildConfig* cfg,
    529                                       const BuildArgv* argv,
    530                                       const BuildChainFrame* chain,
    531                                       BuildTestResolved* out,
    532                                       int record_traces) {
    533   BuildRecipeResolution recipe;
    534   KitBuildListener* listener = NULL;
    535   KitBuildConn* conn = NULL;
    536   KitBuildProc* proc = NULL;
    537   char endpoint[BUILD_PATH_MAX];
    538   char sandbox[BUILD_PATH_MAX];
    539   char out_dir[BUILD_PATH_MAX];
    540   char stdout_path[BUILD_PATH_MAX];
    541   char stderr_path[BUILD_PATH_MAX];
    542   KitSlice proc_argv[129];
    543   KitBuildKV env[140];
    544   char env_keys[128][BUILD_KEY_MAX];
    545   size_t argc = 0, nenv = 0, i, base_nenv = 0;
    546   int exit_code = 1;
    547   int ok = BUILD_ERR;
    548   BuildRunScratch* scratch = NULL;
    549   BuildDepLog log;
    550 
    551   if (!c || !cfg || !argv || !out || !c->host.exec || !c->host.transport ||
    552       !c->host.exec->spawn)
    553     return BUILD_ERR;
    554   endpoint[0] = '\0';
    555   sandbox[0] = '\0';
    556   out_dir[0] = '\0';
    557   stdout_path[0] = '\0';
    558   stderr_path[0] = '\0';
    559   memset(&recipe, 0, sizeof recipe);
    560   if (build_coord_resolve_recipe(c, target, &recipe) != BUILD_OK)
    561     return BUILD_ERR;
    562   if (c->host.transport->listen(c->host.transport->user, endpoint,
    563                                 sizeof endpoint, &listener) != 0 ||
    564       !listener)
    565     return BUILD_ERR;
    566   if (build_store_sandbox_new(&c->store, sandbox, sizeof sandbox, out_dir,
    567                               sizeof out_dir) != BUILD_OK)
    568     goto out_cleanup;
    569   if (path_join2(stdout_path, sizeof stdout_path, sandbox, "stdout") !=
    570           BUILD_OK ||
    571       path_join2(stderr_path, sizeof stderr_path, sandbox, "stderr") !=
    572           BUILD_OK)
    573     goto out_cleanup;
    574 
    575   scratch = build_run_scratch_new(c);
    576   if (!scratch) goto out_cleanup;
    577   build_dep_log_init(&log, scratch);
    578 
    579   proc_argv[argc++] = kit_slice_cstr(recipe.recipe_abspath);
    580   for (i = 0; i < argv->n && argc < sizeof proc_argv / sizeof proc_argv[0];
    581        ++i)
    582     proc_argv[argc++] = kit_slice_cstr(argv->args[i]);
    583   if (i != argv->n) goto out_cleanup;
    584 
    585   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_SOCK);
    586   env[nenv++].value = kit_slice_cstr(endpoint);
    587   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_OUT);
    588   env[nenv++].value = kit_slice_cstr(out_dir);
    589   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_TARGET);
    590   env[nenv++].value = target;
    591   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_REPO);
    592   env[nenv++].value = kit_slice_cstr(recipe.repo);
    593   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_PACKAGE);
    594   env[nenv++].value = kit_slice_cstr(recipe.package);
    595   env[nenv].key = KIT_SLICE_LIT(KIT_BUILD_ENV_LOCAL);
    596   env[nenv++].value = kit_slice_cstr(recipe.local_name);
    597   base_nenv = nenv;
    598   for (i = 0; i < cfg->n && nenv < sizeof env / sizeof env[0] &&
    599               nenv - base_nenv < sizeof env_keys / sizeof env_keys[0];
    600        ++i) {
    601     const char* key = cfg->entries[i].key;
    602     size_t prefix_len = sizeof(KIT_BUILD_ENV_PREFIX) - 1u;
    603     if (strncmp(key, KIT_BUILD_ENV_PREFIX, prefix_len) != 0) continue;
    604     if (append_config_observation(&log, cfg, kit_slice_cstr(key),
    605                                   KIT_SLICE_NULL, 0) != BUILD_OK)
    606       goto out_cleanup;
    607     snprintf(env_keys[nenv - base_nenv],
    608              sizeof env_keys[nenv - base_nenv], "%s",
    609              key + prefix_len);
    610     env[nenv].key = kit_slice_cstr(env_keys[nenv - base_nenv]);
    611     env[nenv].value = kit_slice_cstr(cfg->entries[i].value);
    612     ++nenv;
    613   }
    614 
    615   {
    616     KitExecOpts eo;
    617     memset(&eo, 0, sizeof eo);
    618     eo.argv = proc_argv;
    619     eo.argc = argc;
    620     eo.env = env;
    621     eo.nenv = nenv;
    622     eo.cwd = kit_slice_cstr(recipe.workspace_root);
    623     eo.stdout_path = kit_slice_cstr(stdout_path);
    624     eo.stderr_path = kit_slice_cstr(stderr_path);
    625     if (c->host.exec->spawn(c->host.exec->user, &eo, &proc) != 0 || !proc)
    626       goto out_cleanup;
    627   }
    628   build_coord_stat_bump(c, BUILD_STAT_TEST_RUN);
    629   if (service_recipe_connections(c, listener, &proc, target, cfg, chain, &log,
    630                                  &exit_code) != BUILD_OK)
    631     goto out_kill;
    632   if (copy_capture_into_output(c, stdout_path, out_dir, "stdout",
    633                                out->stdout_blob) != BUILD_OK ||
    634       copy_capture_into_output(c, stderr_path, out_dir, "stderr",
    635                                out->stderr_blob) != BUILD_OK)
    636     goto out_cleanup;
    637   if (build_store_ingest_output(&c->store, out_dir, out->result_tree, out->path,
    638                                 sizeof out->path) != BUILD_OK)
    639     goto out_cleanup;
    640   out->exit_code = exit_code;
    641   out->status = exit_code == 0 ? KIT_TEST_PASS : KIT_TEST_FAIL;
    642   if (out->status == KIT_TEST_PASS && record_traces) {
    643     if (build_runner_record_test_traces(c, target, argv, &log,
    644                                         out->result_tree, &out->leafset) !=
    645         BUILD_OK)
    646       goto out_cleanup;
    647   } else {
    648     out->leafset = NULL;
    649     if (out->status == KIT_TEST_FAIL)
    650       build_coord_stat_bump(c, BUILD_STAT_TEST_FAILURE);
    651   }
    652   ok = BUILD_OK;
    653   goto out_cleanup;
    654 
    655 out_kill:
    656   if (proc && c->host.exec->kill) c->host.exec->kill(c->host.exec->user, proc);
    657   if (proc && c->host.exec->wait)
    658     (void)c->host.exec->wait(c->host.exec->user, proc, &exit_code, NULL, NULL);
    659   proc = NULL;
    660 out_cleanup:
    661   if (conn) c->host.transport->close(c->host.transport->user, conn);
    662   if (listener)
    663     c->host.transport->close_listener(c->host.transport->user, listener);
    664   if (sandbox[0]) build_store_sandbox_done(&c->store, sandbox);
    665   build_run_scratch_free(c, scratch);
    666   return ok;
    667 }
    668 
    669 int build_run_recipe(KitBuildCoordinator* c, KitSlice target,
    670                      const BuildConfig* cfg, const BuildArgv* argv,
    671                      const BuildChainFrame* chain, BuildResolved* out) {
    672   return build_run_recipe_impl(c, target, cfg, argv, chain, out, 1);
    673 }
    674 
    675 int build_run_recipe_probe(KitBuildCoordinator* c, KitSlice target,
    676                            const BuildConfig* cfg, const BuildArgv* argv,
    677                            const BuildChainFrame* chain, BuildResolved* out) {
    678   return build_run_recipe_impl(c, target, cfg, argv, chain, out, 0);
    679 }
    680 
    681 int build_run_test_recipe(KitBuildCoordinator* c, KitSlice target,
    682                           const BuildConfig* cfg, const BuildArgv* argv,
    683                           const BuildChainFrame* chain, BuildTestResolved* out,
    684                           int record_traces) {
    685   return build_run_test_recipe_impl(c, target, cfg, argv, chain, out,
    686                                     record_traces);
    687 }
    688 
    689 int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
    690                          KitSlice target, const BuildConfig* cfg,
    691                          const BuildChainFrame* chain, BuildDepLog* log) {
    692   uint8_t frame[BUILD_FRAME_MAX];
    693   size_t n = 0;
    694   char current_repo[BUILD_KEY_MAX];
    695   char current_package[BUILD_PATH_MAX];
    696   char current_local[BUILD_TARGET_MAX];
    697   if (!c || !conn || !cfg || !log) return BUILD_ERR;
    698   if (build_target_split_repo(target, current_repo, current_package,
    699                               current_local) != BUILD_OK)
    700     return BUILD_ERR;
    701   for (;;) {
    702     BuildReq req;
    703     KitBuildKV overrides[64];
    704     KitSlice argv_slices[64];
    705     BuildResp resp;
    706     memset(&req, 0, sizeof req);
    707     if (c->host.transport->read_frame(c->host.transport->user, conn, frame,
    708                                       sizeof frame, &n) != 0)
    709       return BUILD_OK;
    710     if (build_proto_decode_req(frame, n, &req, overrides,
    711                                sizeof overrides / sizeof overrides[0],
    712                                argv_slices,
    713                                sizeof argv_slices / sizeof argv_slices[0]) !=
    714         BUILD_OK)
    715       return BUILD_ERR;
    716     memset(&resp, 0, sizeof resp);
    717     resp.status = BUILD_RESP_OK;
    718     if (req.cmd == BUILD_CMD_CONFIG_GET) {
    719       KitSlice value;
    720       int present = 0;
    721       if (build_config_get(cfg, req.arg, &value, &present) != BUILD_OK ||
    722           append_config_observation(log, cfg, req.arg,
    723                                     req.argc ? req.argv[0] : KIT_SLICE_NULL,
    724                                     req.argc ? 1 : 0) != BUILD_OK) {
    725         resp_error(&resp, KIT_ERR, "config-get failed");
    726       } else if (!present && req.argc) {
    727         resp.status = BUILD_RESP_DEFAULT;
    728         resp.text = req.argv[0];
    729       } else if (!present) {
    730         resp.status = BUILD_RESP_UNSET;
    731       } else {
    732         resp.text = value;
    733       }
    734       if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
    735     } else if (req.cmd == BUILD_CMD_SOURCE) {
    736       uint8_t blob[BUILD_HASH_LEN];
    737       int present = 0;
    738       char full[BUILD_PATH_MAX];
    739       BuildRecipeResolution recipe;
    740       memset(&recipe, 0, sizeof recipe);
    741       if (build_coord_source_hash_target(c, target, req.arg, blob, &present) !=
    742               BUILD_OK ||
    743           append_source(log, req.arg, blob, present) != BUILD_OK) {
    744         resp_error(&resp, KIT_ERR, "source failed");
    745       } else if (!present) {
    746         resp.status = BUILD_RESP_ABSENT;
    747       } else if (build_coord_resolve_recipe(c, target, &recipe) != BUILD_OK ||
    748                  path_join2(full, sizeof full, recipe.workspace_root,
    749                             req.arg.s) != BUILD_OK) {
    750         resp_error(&resp, KIT_ERR, "source path failed");
    751       } else {
    752         memcpy(resp.id, blob, BUILD_HASH_LEN);
    753         resp.text = kit_slice_cstr(full);
    754       }
    755       if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
    756     } else if (req.cmd == BUILD_CMD_FETCH) {
    757       uint8_t blob[BUILD_HASH_LEN];
    758       char path[BUILD_PATH_MAX];
    759       if (kit_hex_decode(blob, req.arg.s, BUILD_HASH_LEN) != KIT_OK ||
    760           build_coord_fetch_blob(c, blob, req.argv, req.argc, path,
    761                                  sizeof path) != BUILD_OK ||
    762           append_blob(log, blob) != BUILD_OK) {
    763         resp_error(&resp, KIT_ERR, "fetch failed");
    764       } else {
    765         memcpy(resp.id, blob, BUILD_HASH_LEN);
    766         resp.text = kit_slice_cstr(path);
    767       }
    768       if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
    769     } else if (req.cmd == BUILD_CMD_GLOB) {
    770       BuildGlobStream stream;
    771       uint8_t result_hash[BUILD_HASH_LEN];
    772       memset(&stream, 0, sizeof stream);
    773       stream.c = c;
    774       stream.conn = conn;
    775       stream.req = req;
    776       if (build_coord_glob_target(c, target, req.arg, result_hash,
    777                                   glob_stream_cb, &stream) != BUILD_OK ||
    778           stream.failed ||
    779           append_glob(log, req.arg, result_hash) != BUILD_OK) {
    780         resp_error(&resp, KIT_ERR, "glob failed");
    781         if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
    782       } else {
    783         resp.status = BUILD_RESP_GLOB_END;
    784         if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
    785       }
    786     } else if (req.cmd == BUILD_CMD_NEED) {
    787       BuildConfigEntry cfg_entries[128], overlay_entries[64];
    788       char argv_entries[64][BUILD_VAL_MAX];
    789       BuildConfig dep_cfg, overlay_cfg, empty_cfg;
    790       BuildArgv dep_argv;
    791       BuildResolved r;
    792       uint8_t overlay_id[BUILD_HASH_LEN], argv_id[BUILD_HASH_LEN];
    793       char dep_target[BUILD_TARGET_MAX];
    794       build_config_init(&dep_cfg, cfg_entries,
    795                         sizeof cfg_entries / sizeof cfg_entries[0]);
    796       build_config_init(&overlay_cfg, overlay_entries,
    797                         sizeof overlay_entries / sizeof overlay_entries[0]);
    798       build_config_init(&empty_cfg, NULL, 0);
    799       build_argv_init(&dep_argv, argv_entries,
    800                       sizeof argv_entries / sizeof argv_entries[0]);
    801       if (build_coord_canonical_target(c, req.arg, kit_slice_cstr(current_repo),
    802                                        kit_slice_cstr(current_package),
    803                                        dep_target) != BUILD_OK ||
    804           build_config_overlay(cfg, req.overrides, req.noverrides, &dep_cfg) !=
    805               BUILD_OK ||
    806           build_config_overlay(&empty_cfg, req.overrides, req.noverrides,
    807                                &overlay_cfg) != BUILD_OK ||
    808           store_config_blob(c, &overlay_cfg, overlay_id) != BUILD_OK ||
    809           build_argv_set(&dep_argv, req.argv, req.argc) != BUILD_OK ||
    810           build_argv_id(c->ctx->heap, &dep_argv, argv_id) != BUILD_OK ||
    811           build_resolve(c, kit_slice_cstr(dep_target), &dep_cfg, &dep_argv,
    812                         chain, &r) !=
    813               BUILD_OK ||
    814           append_dep(log, kit_slice_cstr(dep_target), overlay_id, argv_id,
    815                      &r) !=
    816               BUILD_OK) {
    817         resp_error(&resp, KIT_ERR, "need failed");
    818       } else {
    819         memcpy(resp.id, r.output_tree, BUILD_HASH_LEN);
    820         resp.text = kit_slice_cstr(r.path);
    821       }
    822       if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
    823     } else if (req.cmd == BUILD_CMD_NEED_SUBMIT) {
    824       BuildConfigEntry cfg_entries[128], overlay_entries[64];
    825       char argv_entries[64][BUILD_VAL_MAX];
    826       BuildConfig dep_cfg, overlay_cfg, empty_cfg;
    827       BuildArgv dep_argv;
    828       BuildTargetFuture* f = NULL;
    829       BuildPendingNeed* p;
    830       uint8_t overlay_id[BUILD_HASH_LEN], argv_id[BUILD_HASH_LEN];
    831       char dep_target[BUILD_TARGET_MAX];
    832       char err[128];
    833       build_config_init(&dep_cfg, cfg_entries,
    834                         sizeof cfg_entries / sizeof cfg_entries[0]);
    835       build_config_init(&overlay_cfg, overlay_entries,
    836                         sizeof overlay_entries / sizeof overlay_entries[0]);
    837       build_config_init(&empty_cfg, NULL, 0);
    838       build_argv_init(&dep_argv, argv_entries,
    839                       sizeof argv_entries / sizeof argv_entries[0]);
    840       if (log->n_pending >= log->cap_pending ||
    841           build_coord_canonical_target(c, req.arg, kit_slice_cstr(current_repo),
    842                                        kit_slice_cstr(current_package),
    843                                        dep_target) != BUILD_OK ||
    844           build_config_overlay(cfg, req.overrides, req.noverrides, &dep_cfg) !=
    845               BUILD_OK ||
    846           build_config_overlay(&empty_cfg, req.overrides, req.noverrides,
    847                                &overlay_cfg) != BUILD_OK ||
    848           store_config_blob(c, &overlay_cfg, overlay_id) != BUILD_OK ||
    849           build_argv_set(&dep_argv, req.argv, req.argc) != BUILD_OK ||
    850           build_argv_id(c->ctx->heap, &dep_argv, argv_id) != BUILD_OK ||
    851           build_dispatch(c, kit_slice_cstr(dep_target), &dep_cfg, &dep_argv,
    852                          chain, &f, err, sizeof err) != BUILD_OK) {
    853         resp_error(&resp, KIT_ERR, "need-submit failed");
    854       } else {
    855         p = &log->pending[log->n_pending++];
    856         memset(p, 0, sizeof *p);
    857         p->token = log->next_token++;
    858         target_copy(kit_slice_cstr(dep_target), p->dep);
    859         memcpy(p->overlay_id, overlay_id, BUILD_HASH_LEN);
    860         memcpy(p->argv_id, argv_id, BUILD_HASH_LEN);
    861         p->future = f;
    862         resp.token = p->token;
    863       }
    864       if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
    865     } else if (req.cmd == BUILD_CMD_NEED_AWAIT) {
    866       BuildPendingNeed* p = find_pending(log, req.token);
    867       BuildResolved r;
    868       if (!p) {
    869         resp_error(&resp, KIT_ERR, "need-await failed");
    870       } else if (p->awaited) {
    871         memcpy(resp.id, p->result.output_tree, BUILD_HASH_LEN);
    872         resp.text = kit_slice_cstr(p->result.path);
    873       } else if (build_coord_target_await(c, p->future, &r) != BUILD_OK ||
    874                  append_dep(log, kit_slice_cstr(p->dep), p->overlay_id,
    875                             p->argv_id, &r) != BUILD_OK) {
    876         resp_error(&resp, KIT_ERR, "need-await failed");
    877       } else {
    878         p->awaited = 1;
    879         p->result = r;
    880         memcpy(resp.id, r.output_tree, BUILD_HASH_LEN);
    881         resp.text = kit_slice_cstr(r.path);
    882       }
    883       if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
    884     } else {
    885       return BUILD_ERR;
    886     }
    887   }
    888 }
    889 
    890 int build_runner_record_traces(KitBuildCoordinator* c, KitSlice target,
    891                                const BuildArgv* argv, const BuildDepLog* log,
    892                                const uint8_t output[BUILD_HASH_LEN],
    893                                const BuildLeafSet** out_leafset) {
    894   BuildLeafSet direct;
    895   BuildShallowTrace shallow;
    896   BuildDeepTrace deep;
    897   KitWriter* w = NULL;
    898   const uint8_t* bytes;
    899   size_t len;
    900   uint8_t trace_id[BUILD_HASH_LEN];
    901   uint8_t target_key[BUILD_HASH_LEN];
    902   char err[128];
    903   int ok = BUILD_ERR;
    904 
    905   if (!c || !argv || !log || !output || !out_leafset)
    906     return BUILD_ERR;
    907   memset(&direct, 0, sizeof direct);
    908   if (target_copy(target, direct.target) != BUILD_OK) return BUILD_ERR;
    909   if (build_coord_recipe_id(c, target, direct.recipe) != BUILD_OK)
    910     return BUILD_ERR;
    911   direct.configs = log->configs;
    912   direct.n_configs = log->n_configs;
    913   direct.sources = log->sources;
    914   direct.n_sources = log->n_sources;
    915   direct.globs = log->globs;
    916   direct.n_globs = log->n_globs;
    917   direct.blobs = log->blobs;
    918   direct.n_blobs = log->n_blobs;
    919   direct.children = log->child_leafsets;
    920   direct.n_children = log->n_children;
    921   if (build_leafset_union(c, &direct, log->deps, log->child_leafsets,
    922                           log->n_children, out_leafset) != BUILD_OK)
    923     return BUILD_ERR;
    924 
    925   memset(&shallow, 0, sizeof shallow);
    926   if (target_copy(target, shallow.target) != BUILD_OK) return BUILD_ERR;
    927   memcpy(shallow.recipe, direct.recipe, BUILD_HASH_LEN);
    928   memcpy(shallow.output, output, BUILD_HASH_LEN);
    929   if (store_argv_blob(c, argv, shallow.argv) != BUILD_OK)
    930     return BUILD_ERR;
    931   shallow.configs = log->configs;
    932   shallow.n_configs = log->n_configs;
    933   shallow.sources = log->sources;
    934   shallow.n_sources = log->n_sources;
    935   shallow.globs = log->globs;
    936   shallow.n_globs = log->n_globs;
    937   shallow.blobs = log->blobs;
    938   shallow.n_blobs = log->n_blobs;
    939   shallow.deps = log->deps;
    940   shallow.n_deps = log->n_deps;
    941 
    942   if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR;
    943   if (build_shallow_emit(&shallow, w, err, sizeof err) != BUILD_OK ||
    944       kit_writer_status(w) != KIT_OK)
    945     goto out;
    946   bytes = kit_writer_mem_bytes(w, &len);
    947   if (build_store_put_trace(&c->store, bytes, len, trace_id) != BUILD_OK)
    948     goto out;
    949   if (build_target_key(target, target_key) != BUILD_OK ||
    950       build_store_record_update(&c->store, target_key, target,
    951                                 BUILD_TRACE_SHALLOW, trace_id) != BUILD_OK)
    952     goto out;
    953   kit_writer_close(w);
    954   w = NULL;
    955 
    956   memset(&deep, 0, sizeof deep);
    957   if (target_copy(target, deep.target) != BUILD_OK) return BUILD_ERR;
    958   memcpy(deep.recipe, direct.recipe, BUILD_HASH_LEN);
    959   memcpy(deep.output, output, BUILD_HASH_LEN);
    960   memcpy(deep.argv, shallow.argv, BUILD_HASH_LEN);
    961   memcpy(deep.deepset, (*out_leafset)->id, BUILD_HASH_LEN);
    962   if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR;
    963   if (build_deep_emit(&deep, w, err, sizeof err) != BUILD_OK ||
    964       kit_writer_status(w) != KIT_OK)
    965     goto out;
    966   bytes = kit_writer_mem_bytes(w, &len);
    967   if (build_store_put_trace(&c->store, bytes, len, trace_id) != BUILD_OK)
    968     goto out;
    969   if (build_store_record_update(&c->store, target_key, target, BUILD_TRACE_DEEP,
    970                                 trace_id) != BUILD_OK)
    971     goto out;
    972   ok = BUILD_OK;
    973 out:
    974   if (w) kit_writer_close(w);
    975   return ok;
    976 }
    977 
    978 int build_runner_record_test_traces(KitBuildCoordinator* c, KitSlice target,
    979                                     const BuildArgv* argv,
    980                                     const BuildDepLog* log,
    981                                     const uint8_t result[BUILD_HASH_LEN],
    982                                     const BuildLeafSet** out_leafset) {
    983   BuildLeafSet direct;
    984   BuildShallowTrace shallow;
    985   BuildDeepTrace deep;
    986   KitWriter* w = NULL;
    987   const uint8_t* bytes;
    988   size_t len;
    989   uint8_t trace_id[BUILD_HASH_LEN];
    990   uint8_t target_key[BUILD_HASH_LEN];
    991   char err[128];
    992   int ok = BUILD_ERR;
    993 
    994   if (!c || !argv || !log || !result || !out_leafset)
    995     return BUILD_ERR;
    996   memset(&direct, 0, sizeof direct);
    997   if (target_copy(target, direct.target) != BUILD_OK) return BUILD_ERR;
    998   if (build_coord_recipe_id(c, target, direct.recipe) != BUILD_OK)
    999     return BUILD_ERR;
   1000   direct.configs = log->configs;
   1001   direct.n_configs = log->n_configs;
   1002   direct.sources = log->sources;
   1003   direct.n_sources = log->n_sources;
   1004   direct.globs = log->globs;
   1005   direct.n_globs = log->n_globs;
   1006   direct.blobs = log->blobs;
   1007   direct.n_blobs = log->n_blobs;
   1008   direct.children = log->child_leafsets;
   1009   direct.n_children = log->n_children;
   1010   if (build_leafset_union(c, &direct, log->deps, log->child_leafsets,
   1011                           log->n_children, out_leafset) != BUILD_OK)
   1012     return BUILD_ERR;
   1013 
   1014   memset(&shallow, 0, sizeof shallow);
   1015   if (target_copy(target, shallow.target) != BUILD_OK) return BUILD_ERR;
   1016   memcpy(shallow.recipe, direct.recipe, BUILD_HASH_LEN);
   1017   memcpy(shallow.output, result, BUILD_HASH_LEN);
   1018   if (store_argv_blob(c, argv, shallow.argv) != BUILD_OK)
   1019     return BUILD_ERR;
   1020   shallow.configs = log->configs;
   1021   shallow.n_configs = log->n_configs;
   1022   shallow.sources = log->sources;
   1023   shallow.n_sources = log->n_sources;
   1024   shallow.globs = log->globs;
   1025   shallow.n_globs = log->n_globs;
   1026   shallow.blobs = log->blobs;
   1027   shallow.n_blobs = log->n_blobs;
   1028   shallow.deps = log->deps;
   1029   shallow.n_deps = log->n_deps;
   1030 
   1031   if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR;
   1032   if (build_test_shallow_emit(&shallow, w, err, sizeof err) != BUILD_OK ||
   1033       kit_writer_status(w) != KIT_OK)
   1034     goto out;
   1035   bytes = kit_writer_mem_bytes(w, &len);
   1036   if (build_store_put_trace(&c->store, bytes, len, trace_id) != BUILD_OK)
   1037     goto out;
   1038   if (build_test_target_key(target, target_key) != BUILD_OK ||
   1039       build_store_record_update(&c->store, target_key, target,
   1040                                 BUILD_TRACE_SHALLOW, trace_id) != BUILD_OK)
   1041     goto out;
   1042   kit_writer_close(w);
   1043   w = NULL;
   1044 
   1045   memset(&deep, 0, sizeof deep);
   1046   if (target_copy(target, deep.target) != BUILD_OK) return BUILD_ERR;
   1047   memcpy(deep.recipe, direct.recipe, BUILD_HASH_LEN);
   1048   memcpy(deep.output, result, BUILD_HASH_LEN);
   1049   memcpy(deep.argv, shallow.argv, BUILD_HASH_LEN);
   1050   memcpy(deep.deepset, (*out_leafset)->id, BUILD_HASH_LEN);
   1051   if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR;
   1052   if (build_test_deep_emit(&deep, w, err, sizeof err) != BUILD_OK ||
   1053       kit_writer_status(w) != KIT_OK)
   1054     goto out;
   1055   bytes = kit_writer_mem_bytes(w, &len);
   1056   if (build_store_put_trace(&c->store, bytes, len, trace_id) != BUILD_OK)
   1057     goto out;
   1058   if (build_store_record_update(&c->store, target_key, target, BUILD_TRACE_DEEP,
   1059                                 trace_id) != BUILD_OK)
   1060     goto out;
   1061   ok = BUILD_OK;
   1062 out:
   1063   if (w) kit_writer_close(w);
   1064   return ok;
   1065 }