kit

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

commit 26f466c16a079ded8a59a73730b84f915cd90790
parent aa5af123555320e089cbcd32d8678a81f20e2e43
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 19 Jun 2026 08:32:08 -0700

Make build traces config-read aware

Diffstat:
Mdriver/cmd/build_coord.c | 11+++++++----
Minclude/kit/build_coord.h | 10+++++++---
Msrc/api/build_coord.c | 11+++++++++++
Msrc/api/config_stubs.c | 3+++
Msrc/build/bundle.c | 98+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/build/coord.c | 59++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/build/coord.h | 10++++++----
Msrc/build/protocol.c | 36+++++++++++++++++++++++++++++-------
Msrc/build/protocol.h | 7++++---
Msrc/build/resolve.c | 92+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/build/resolve.h | 2+-
Msrc/build/runner.c | 90++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/build/runner.h | 4++--
Msrc/build/trace.c | 167++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/build/trace.h | 44++++++++++++++++++++++++++------------------
Mtest/build/build_public_link_test.c | 9+++++----
Mtest/build/build_pure_test.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mtest/buildcoord/run.sh | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
18 files changed, 628 insertions(+), 232 deletions(-)

diff --git a/driver/cmd/build_coord.c b/driver/cmd/build_coord.c @@ -350,15 +350,18 @@ static int build_client_mode(int argc, char** argv, int verb_index) { rc = 2; goto out_client; } - st = kit_build_client_config_get(client, kit_slice_cstr(cli.target), &value, - &present); + st = kit_build_client_config_get( + client, kit_slice_cstr(cli.target), + cli.has_config_default ? kit_slice_cstr(cli.config_default) + : KIT_SLICE_NULL, + cli.has_config_default, &value, &present); if (st != KIT_OK) { driver_errf(BUILD_TOOL, "config-get failed: %s", build_status_name(st)); goto out_client; } if (!present) { - if (cli.has_config_default) { - driver_printf("%s\n", cli.config_default); + if (value.s) { + driver_printf("%.*s\n", KIT_SLICE_ARG(value)); rc = 0; goto out_client; } diff --git a/include/kit/build_coord.h b/include/kit/build_coord.h @@ -414,10 +414,14 @@ KIT_API KitStatus kit_build_client_open(const KitContext*, KitBuildClient** out); KIT_API void kit_build_client_close(KitBuildClient*); -/* Read a propagated config value (logs a config dep on `key`). On return - * *present is 0 when the key is unset; *value is borrowed until the next - * client call. */ +/* Read a propagated config value (logs a config observation on `key`). When + * has_default is nonzero and the key is unset, the coordinator returns + * default_value and records both the unset observation and default hash. On + * return *present is 0 when the underlying key is unset; *value is borrowed + * until the next client call. */ KIT_API KitStatus kit_build_client_config_get(KitBuildClient*, KitSlice key, + KitSlice default_value, + int has_default, KitSlice* value, int* present); /* Resolve a source path to its pinned blob id and a readable path (logs a diff --git a/src/api/build_coord.c b/src/api/build_coord.c @@ -209,19 +209,30 @@ void kit_build_client_close(KitBuildClient* c) { } KitStatus kit_build_client_config_get(KitBuildClient* c, KitSlice key, + KitSlice default_value, int has_default, KitSlice* value, int* present) { BuildReq req; BuildResp resp; KitStatus st; + KitSlice argv[1]; if (value) *value = KIT_SLICE_NULL; if (present) *present = 0; if (!c || !value || !present) return KIT_INVALID; memset(&req, 0, sizeof req); req.cmd = BUILD_CMD_CONFIG_GET; req.arg = key; + if (has_default) { + argv[0] = default_value; + req.argv = argv; + req.argc = 1; + } st = build_client_rpc(c, &req, &resp, NULL, NULL); if (st != KIT_OK) return st; if (resp.status == BUILD_RESP_UNSET) return KIT_OK; + if (resp.status == BUILD_RESP_DEFAULT) { + *value = resp.text; + return KIT_OK; + } if (resp.status != BUILD_RESP_OK) return KIT_MALFORMED; *value = resp.text; *present = 1; diff --git a/src/api/config_stubs.c b/src/api/config_stubs.c @@ -90,9 +90,12 @@ KitStatus kit_build_client_open(const KitContext* ctx, void kit_build_client_close(KitBuildClient* c) { (void)c; } KitStatus kit_build_client_config_get(KitBuildClient* c, KitSlice key, + KitSlice default_value, int has_default, KitSlice* value, int* present) { (void)c; (void)key; + (void)default_value; + (void)has_default; if (value) *value = KIT_SLICE_NULL; if (present) *present = 0; return KIT_UNSUPPORTED; diff --git a/src/build/bundle.c b/src/build/bundle.c @@ -348,8 +348,8 @@ static int BUILD_MAYBE_UNUSED parse_trace_header(const KitContext* ctx, BuildShallowTrace t; size_t rows = count_lines(data, len); memset(&t, 0, sizeof t); - t.config_keys = (BuildConfigKey*)heap_array( - ctx, rows, sizeof *t.config_keys, _Alignof(BuildConfigKey)); + t.configs = (BuildConfigLeaf*)heap_array( + ctx, rows, sizeof *t.configs, _Alignof(BuildConfigLeaf)); t.sources = (BuildSourceLeaf*)heap_array(ctx, rows, sizeof *t.sources, _Alignof(BuildSourceLeaf)); t.globs = (BuildGlobLeaf*)heap_array(ctx, rows, sizeof *t.globs, @@ -358,21 +358,21 @@ static int BUILD_MAYBE_UNUSED parse_trace_header(const KitContext* ctx, _Alignof(BuildFetchLeaf)); t.deps = (BuildDepEdge*)heap_array(ctx, rows, sizeof *t.deps, _Alignof(BuildDepEdge)); - if (!t.config_keys || !t.sources || !t.globs || !t.fetches || !t.deps) { - heap_free_array(ctx, t.config_keys, rows, sizeof *t.config_keys); + if (!t.configs || !t.sources || !t.globs || !t.fetches || !t.deps) { + heap_free_array(ctx, t.configs, rows, sizeof *t.configs); heap_free_array(ctx, t.sources, rows, sizeof *t.sources); heap_free_array(ctx, t.globs, rows, sizeof *t.globs); heap_free_array(ctx, t.fetches, rows, sizeof *t.fetches); heap_free_array(ctx, t.deps, rows, sizeof *t.deps); return BUILD_ERR; } - t.cap_config_keys = rows; + t.cap_configs = rows; t.cap_sources = rows; t.cap_globs = rows; t.cap_fetches = rows; t.cap_deps = rows; if (build_shallow_parse(data, len, &t, err, sizeof err) != BUILD_OK) { - heap_free_array(ctx, t.config_keys, rows, sizeof *t.config_keys); + heap_free_array(ctx, t.configs, rows, sizeof *t.configs); heap_free_array(ctx, t.sources, rows, sizeof *t.sources); heap_free_array(ctx, t.globs, rows, sizeof *t.globs); heap_free_array(ctx, t.fetches, rows, sizeof *t.fetches); @@ -382,7 +382,7 @@ static int BUILD_MAYBE_UNUSED parse_trace_header(const KitContext* ctx, snprintf(out->target, sizeof out->target, "%s", t.target); out->kind = (uint8_t)BUILD_TRACE_SHALLOW; memcpy(out->output, t.output, BUILD_HASH_LEN); - heap_free_array(ctx, t.config_keys, rows, sizeof *t.config_keys); + heap_free_array(ctx, t.configs, rows, sizeof *t.configs); heap_free_array(ctx, t.sources, rows, sizeof *t.sources); heap_free_array(ctx, t.globs, rows, sizeof *t.globs); heap_free_array(ctx, t.fetches, rows, sizeof *t.fetches); @@ -395,8 +395,8 @@ static int BUILD_MAYBE_UNUSED parse_trace_header(const KitContext* ctx, BuildShallowTrace t; size_t rows = count_lines(data, len); memset(&t, 0, sizeof t); - t.config_keys = (BuildConfigKey*)heap_array( - ctx, rows, sizeof *t.config_keys, _Alignof(BuildConfigKey)); + t.configs = (BuildConfigLeaf*)heap_array( + ctx, rows, sizeof *t.configs, _Alignof(BuildConfigLeaf)); t.sources = (BuildSourceLeaf*)heap_array(ctx, rows, sizeof *t.sources, _Alignof(BuildSourceLeaf)); t.globs = (BuildGlobLeaf*)heap_array(ctx, rows, sizeof *t.globs, @@ -405,21 +405,21 @@ static int BUILD_MAYBE_UNUSED parse_trace_header(const KitContext* ctx, _Alignof(BuildFetchLeaf)); t.deps = (BuildDepEdge*)heap_array(ctx, rows, sizeof *t.deps, _Alignof(BuildDepEdge)); - if (!t.config_keys || !t.sources || !t.globs || !t.fetches || !t.deps) { - heap_free_array(ctx, t.config_keys, rows, sizeof *t.config_keys); + if (!t.configs || !t.sources || !t.globs || !t.fetches || !t.deps) { + heap_free_array(ctx, t.configs, rows, sizeof *t.configs); heap_free_array(ctx, t.sources, rows, sizeof *t.sources); heap_free_array(ctx, t.globs, rows, sizeof *t.globs); heap_free_array(ctx, t.fetches, rows, sizeof *t.fetches); heap_free_array(ctx, t.deps, rows, sizeof *t.deps); return BUILD_ERR; } - t.cap_config_keys = rows; + t.cap_configs = rows; t.cap_sources = rows; t.cap_globs = rows; t.cap_fetches = rows; t.cap_deps = rows; if (build_test_shallow_parse(data, len, &t, err, sizeof err) != BUILD_OK) { - heap_free_array(ctx, t.config_keys, rows, sizeof *t.config_keys); + heap_free_array(ctx, t.configs, rows, sizeof *t.configs); heap_free_array(ctx, t.sources, rows, sizeof *t.sources); heap_free_array(ctx, t.globs, rows, sizeof *t.globs); heap_free_array(ctx, t.fetches, rows, sizeof *t.fetches); @@ -429,7 +429,7 @@ static int BUILD_MAYBE_UNUSED parse_trace_header(const KitContext* ctx, snprintf(out->target, sizeof out->target, "%s", t.target); out->kind = (uint8_t)BUILD_BUNDLE_TEST_SHALLOW; memcpy(out->output, t.output, BUILD_HASH_LEN); - heap_free_array(ctx, t.config_keys, rows, sizeof *t.config_keys); + heap_free_array(ctx, t.configs, rows, sizeof *t.configs); heap_free_array(ctx, t.sources, rows, sizeof *t.sources); heap_free_array(ctx, t.globs, rows, sizeof *t.globs); heap_free_array(ctx, t.fetches, rows, sizeof *t.fetches); @@ -626,6 +626,8 @@ static int export_deepset_closure(KitBuildCoordinator* c, if (kit_cas_get_blob(c->cas, id, &fd) != KIT_OK) return BUILD_ERR; rows = count_lines(fd.data, fd.size); memset(&ds, 0, sizeof ds); + ds.configs = (BuildConfigLeaf*)heap_array(c->ctx, rows, sizeof *ds.configs, + _Alignof(BuildConfigLeaf)); ds.sources = (BuildSourceLeaf*)heap_array(c->ctx, rows, sizeof *ds.sources, _Alignof(BuildSourceLeaf)); ds.globs = (BuildGlobLeaf*)heap_array(c->ctx, rows, sizeof *ds.globs, @@ -634,7 +636,9 @@ static int export_deepset_closure(KitBuildCoordinator* c, _Alignof(BuildFetchLeaf)); ds.children = (uint8_t(*)[BUILD_HASH_LEN])heap_array( c->ctx, rows, sizeof *ds.children, _Alignof(uint8_t)); - if (!ds.sources || !ds.globs || !ds.fetches || !ds.children) goto out; + if (!ds.configs || !ds.sources || !ds.globs || !ds.fetches || !ds.children) + goto out; + ds.cap_configs = rows; ds.cap_sources = rows; ds.cap_globs = rows; ds.cap_fetches = rows; @@ -650,6 +654,7 @@ static int export_deepset_closure(KitBuildCoordinator* c, goto out; ok = BUILD_OK; out: + heap_free_array(c->ctx, ds.configs, rows, sizeof *ds.configs); heap_free_array(c->ctx, ds.sources, rows, sizeof *ds.sources); heap_free_array(c->ctx, ds.globs, rows, sizeof *ds.globs); heap_free_array(c->ctx, ds.fetches, rows, sizeof *ds.fetches); @@ -674,6 +679,8 @@ static int import_deepset_closure(KitBuildCoordinator* c, return BUILD_ERR; rows = count_lines(data, len); memset(&ds, 0, sizeof ds); + ds.configs = (BuildConfigLeaf*)heap_array(c->ctx, rows, sizeof *ds.configs, + _Alignof(BuildConfigLeaf)); ds.sources = (BuildSourceLeaf*)heap_array(c->ctx, rows, sizeof *ds.sources, _Alignof(BuildSourceLeaf)); ds.globs = (BuildGlobLeaf*)heap_array(c->ctx, rows, sizeof *ds.globs, @@ -682,7 +689,9 @@ static int import_deepset_closure(KitBuildCoordinator* c, _Alignof(BuildFetchLeaf)); ds.children = (uint8_t(*)[BUILD_HASH_LEN])heap_array( c->ctx, rows, sizeof *ds.children, _Alignof(uint8_t)); - if (!ds.sources || !ds.globs || !ds.fetches || !ds.children) goto out; + if (!ds.configs || !ds.sources || !ds.globs || !ds.fetches || !ds.children) + goto out; + ds.cap_configs = rows; ds.cap_sources = rows; ds.cap_globs = rows; ds.cap_fetches = rows; @@ -702,6 +711,7 @@ static int import_deepset_closure(KitBuildCoordinator* c, goto out; ok = BUILD_OK; out: + heap_free_array(c->ctx, ds.configs, rows, sizeof *ds.configs); heap_free_array(c->ctx, ds.sources, rows, sizeof *ds.sources); heap_free_array(c->ctx, ds.globs, rows, sizeof *ds.globs); heap_free_array(c->ctx, ds.fetches, rows, sizeof *ds.fetches); @@ -720,9 +730,7 @@ static int BUILD_MAYBE_UNUSED export_trace_refs(KitBuildCoordinator* c, BuildDeepTrace t; if (build_deep_parse(data, len, &t, err, sizeof err) != BUILD_OK) return BUILD_ERR; - if (payload_write_blob_from_cas(c, t.root_config, root, seen) != - BUILD_OK || - payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK || + if (payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK || export_deepset_closure(c, t.deepset, root, seen) != BUILD_OK) return BUILD_ERR; return BUILD_OK; @@ -733,9 +741,7 @@ static int BUILD_MAYBE_UNUSED export_trace_refs(KitBuildCoordinator* c, BuildDeepTrace t; if (build_test_deep_parse(data, len, &t, err, sizeof err) != BUILD_OK) return BUILD_ERR; - if (payload_write_blob_from_cas(c, t.root_config, root, seen) != - BUILD_OK || - payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK || + if (payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK || export_deepset_closure(c, t.deepset, root, seen) != BUILD_OK) return BUILD_ERR; return BUILD_OK; @@ -747,8 +753,8 @@ static int BUILD_MAYBE_UNUSED export_trace_refs(KitBuildCoordinator* c, size_t i; int ok = BUILD_ERR; memset(&t, 0, sizeof t); - t.config_keys = (BuildConfigKey*)heap_array( - c->ctx, rows, sizeof *t.config_keys, _Alignof(BuildConfigKey)); + t.configs = (BuildConfigLeaf*)heap_array( + c->ctx, rows, sizeof *t.configs, _Alignof(BuildConfigLeaf)); t.sources = (BuildSourceLeaf*)heap_array(c->ctx, rows, sizeof *t.sources, _Alignof(BuildSourceLeaf)); t.globs = (BuildGlobLeaf*)heap_array(c->ctx, rows, sizeof *t.globs, @@ -757,9 +763,9 @@ static int BUILD_MAYBE_UNUSED export_trace_refs(KitBuildCoordinator* c, _Alignof(BuildFetchLeaf)); t.deps = (BuildDepEdge*)heap_array(c->ctx, rows, sizeof *t.deps, _Alignof(BuildDepEdge)); - if (!t.config_keys || !t.sources || !t.globs || !t.fetches || !t.deps) + if (!t.configs || !t.sources || !t.globs || !t.fetches || !t.deps) goto shallow_out; - t.cap_config_keys = rows; + t.cap_configs = rows; t.cap_sources = rows; t.cap_globs = rows; t.cap_fetches = rows; @@ -784,7 +790,7 @@ static int BUILD_MAYBE_UNUSED export_trace_refs(KitBuildCoordinator* c, goto shallow_out; ok = BUILD_OK; shallow_out: - heap_free_array(c->ctx, t.config_keys, rows, sizeof *t.config_keys); + heap_free_array(c->ctx, t.configs, rows, sizeof *t.configs); heap_free_array(c->ctx, t.sources, rows, sizeof *t.sources); heap_free_array(c->ctx, t.globs, rows, sizeof *t.globs); heap_free_array(c->ctx, t.fetches, rows, sizeof *t.fetches); @@ -799,8 +805,8 @@ shallow_out: size_t i; int ok = BUILD_ERR; memset(&t, 0, sizeof t); - t.config_keys = (BuildConfigKey*)heap_array( - c->ctx, rows, sizeof *t.config_keys, _Alignof(BuildConfigKey)); + t.configs = (BuildConfigLeaf*)heap_array( + c->ctx, rows, sizeof *t.configs, _Alignof(BuildConfigLeaf)); t.sources = (BuildSourceLeaf*)heap_array(c->ctx, rows, sizeof *t.sources, _Alignof(BuildSourceLeaf)); t.globs = (BuildGlobLeaf*)heap_array(c->ctx, rows, sizeof *t.globs, @@ -809,9 +815,9 @@ shallow_out: _Alignof(BuildFetchLeaf)); t.deps = (BuildDepEdge*)heap_array(c->ctx, rows, sizeof *t.deps, _Alignof(BuildDepEdge)); - if (!t.config_keys || !t.sources || !t.globs || !t.fetches || !t.deps) + if (!t.configs || !t.sources || !t.globs || !t.fetches || !t.deps) goto test_out; - t.cap_config_keys = rows; + t.cap_configs = rows; t.cap_sources = rows; t.cap_globs = rows; t.cap_fetches = rows; @@ -836,7 +842,7 @@ shallow_out: goto test_out; ok = BUILD_OK; test_out: - heap_free_array(c->ctx, t.config_keys, rows, sizeof *t.config_keys); + heap_free_array(c->ctx, t.configs, rows, sizeof *t.configs); heap_free_array(c->ctx, t.sources, rows, sizeof *t.sources); heap_free_array(c->ctx, t.globs, rows, sizeof *t.globs); heap_free_array(c->ctx, t.fetches, rows, sizeof *t.fetches); @@ -857,10 +863,6 @@ static int BUILD_MAYBE_UNUSED import_trace_refs(KitBuildCoordinator* c, KitFileData fd; if (build_deep_parse(data, len, &t, err, sizeof err) != BUILD_OK) return BUILD_ERR; - if (payload_install_blob(c, t.root_config, root, seen, NULL, NULL, &fd) != - BUILD_OK) - return BUILD_ERR; - release_file(c, &fd); if (payload_install_blob(c, t.argv, root, seen, NULL, NULL, &fd) != BUILD_OK) return BUILD_ERR; @@ -874,10 +876,6 @@ static int BUILD_MAYBE_UNUSED import_trace_refs(KitBuildCoordinator* c, KitFileData fd; if (build_test_deep_parse(data, len, &t, err, sizeof err) != BUILD_OK) return BUILD_ERR; - if (payload_install_blob(c, t.root_config, root, seen, NULL, NULL, &fd) != - BUILD_OK) - return BUILD_ERR; - release_file(c, &fd); if (payload_install_blob(c, t.argv, root, seen, NULL, NULL, &fd) != BUILD_OK) return BUILD_ERR; @@ -893,8 +891,8 @@ static int BUILD_MAYBE_UNUSED import_trace_refs(KitBuildCoordinator* c, KitFileData fd; memset(&t, 0, sizeof t); memset(&fd, 0, sizeof fd); - t.config_keys = (BuildConfigKey*)heap_array( - c->ctx, rows, sizeof *t.config_keys, _Alignof(BuildConfigKey)); + t.configs = (BuildConfigLeaf*)heap_array( + c->ctx, rows, sizeof *t.configs, _Alignof(BuildConfigLeaf)); t.sources = (BuildSourceLeaf*)heap_array(c->ctx, rows, sizeof *t.sources, _Alignof(BuildSourceLeaf)); t.globs = (BuildGlobLeaf*)heap_array(c->ctx, rows, sizeof *t.globs, @@ -903,9 +901,9 @@ static int BUILD_MAYBE_UNUSED import_trace_refs(KitBuildCoordinator* c, _Alignof(BuildFetchLeaf)); t.deps = (BuildDepEdge*)heap_array(c->ctx, rows, sizeof *t.deps, _Alignof(BuildDepEdge)); - if (!t.config_keys || !t.sources || !t.globs || !t.fetches || !t.deps) + if (!t.configs || !t.sources || !t.globs || !t.fetches || !t.deps) goto shallow_out; - t.cap_config_keys = rows; + t.cap_configs = rows; t.cap_sources = rows; t.cap_globs = rows; t.cap_fetches = rows; @@ -943,7 +941,7 @@ static int BUILD_MAYBE_UNUSED import_trace_refs(KitBuildCoordinator* c, ok = BUILD_OK; shallow_out: release_file(c, &fd); - heap_free_array(c->ctx, t.config_keys, rows, sizeof *t.config_keys); + heap_free_array(c->ctx, t.configs, rows, sizeof *t.configs); heap_free_array(c->ctx, t.sources, rows, sizeof *t.sources); heap_free_array(c->ctx, t.globs, rows, sizeof *t.globs); heap_free_array(c->ctx, t.fetches, rows, sizeof *t.fetches); @@ -960,8 +958,8 @@ shallow_out: KitFileData fd; memset(&t, 0, sizeof t); memset(&fd, 0, sizeof fd); - t.config_keys = (BuildConfigKey*)heap_array( - c->ctx, rows, sizeof *t.config_keys, _Alignof(BuildConfigKey)); + t.configs = (BuildConfigLeaf*)heap_array( + c->ctx, rows, sizeof *t.configs, _Alignof(BuildConfigLeaf)); t.sources = (BuildSourceLeaf*)heap_array(c->ctx, rows, sizeof *t.sources, _Alignof(BuildSourceLeaf)); t.globs = (BuildGlobLeaf*)heap_array(c->ctx, rows, sizeof *t.globs, @@ -970,9 +968,9 @@ shallow_out: _Alignof(BuildFetchLeaf)); t.deps = (BuildDepEdge*)heap_array(c->ctx, rows, sizeof *t.deps, _Alignof(BuildDepEdge)); - if (!t.config_keys || !t.sources || !t.globs || !t.fetches || !t.deps) + if (!t.configs || !t.sources || !t.globs || !t.fetches || !t.deps) goto test_out; - t.cap_config_keys = rows; + t.cap_configs = rows; t.cap_sources = rows; t.cap_globs = rows; t.cap_fetches = rows; @@ -1010,7 +1008,7 @@ shallow_out: ok = BUILD_OK; test_out: release_file(c, &fd); - heap_free_array(c->ctx, t.config_keys, rows, sizeof *t.config_keys); + heap_free_array(c->ctx, t.configs, rows, sizeof *t.configs); heap_free_array(c->ctx, t.sources, rows, sizeof *t.sources); heap_free_array(c->ctx, t.globs, rows, sizeof *t.globs); heap_free_array(c->ctx, t.fetches, rows, sizeof *t.fetches); diff --git a/src/build/coord.c b/src/build/coord.c @@ -42,6 +42,7 @@ struct BuildGlobMemo { typedef struct BuildDeepSetMemo { BuildLeafSet leaf; + BuildConfigLeaf* configs; BuildSourceLeaf* sources; BuildGlobLeaf* globs; BuildFetchLeaf* fetches; @@ -250,6 +251,9 @@ static void free_deepsets(KitBuildCoordinator* c) { n = c->deepsets; while (n) { BuildDeepSetMemo* next = n->next; + if (n->configs) + c->ctx->heap->free(c->ctx->heap, n->configs, + n->leaf.n_configs * sizeof *n->configs); if (n->sources) c->ctx->heap->free(c->ctx->heap, n->sources, n->leaf.n_sources * sizeof *n->sources); @@ -728,6 +732,13 @@ int build_coord_leafset_intern(KitBuildCoordinator* c, const BuildLeafSet* in, if (!n) return BUILD_ERR; memset(n, 0, sizeof *n); n->leaf = *in; + if (in->n_configs) { + n->configs = (BuildConfigLeaf*)c->ctx->heap->alloc( + c->ctx->heap, in->n_configs * sizeof *n->configs, + _Alignof(BuildConfigLeaf)); + if (!n->configs) goto oom; + memcpy(n->configs, in->configs, in->n_configs * sizeof *n->configs); + } if (in->n_sources) { n->sources = (BuildSourceLeaf*)c->ctx->heap->alloc( c->ctx->heap, in->n_sources * sizeof *n->sources, @@ -755,6 +766,7 @@ int build_coord_leafset_intern(KitBuildCoordinator* c, const BuildLeafSet* in, if (!n->children) goto oom; memcpy(n->children, in->children, in->n_children * sizeof *n->children); } + n->leaf.configs = n->configs; n->leaf.sources = n->sources; n->leaf.globs = n->globs; n->leaf.fetches = n->fetches; @@ -765,6 +777,9 @@ int build_coord_leafset_intern(KitBuildCoordinator* c, const BuildLeafSet* in, return BUILD_OK; oom: + if (n->configs) + c->ctx->heap->free(c->ctx->heap, n->configs, + in->n_configs * sizeof *n->configs); if (n->sources) c->ctx->heap->free(c->ctx->heap, n->sources, in->n_sources * sizeof *n->sources); @@ -781,10 +796,12 @@ oom: return BUILD_ERR; } -static void count_deepset_rows(const uint8_t* data, size_t len, size_t* ns, - size_t* ng, size_t* nf, size_t* nc) { +static void count_deepset_rows(const uint8_t* data, size_t len, size_t* ncfg, + size_t* ns, size_t* ng, size_t* nf, + size_t* nc) { size_t pos = 0; int sec = 0; + *ncfg = 0; *ns = 0; *ng = 0; *nf = 0; @@ -795,23 +812,27 @@ static void count_deepset_rows(const uint8_t* data, size_t len, size_t* ns, if (pos == len) break; if (pos > start) { size_t n = pos - start; - if (n == sizeof("[source]") - 1u && - memcmp(data + start, "[source]", n) == 0) { + if (n == sizeof("[config]") - 1u && + memcmp(data + start, "[config]", n) == 0) { sec = 1; + } else if (n == sizeof("[source]") - 1u && + memcmp(data + start, "[source]", n) == 0) { + sec = 2; } else if (n == sizeof("[glob]") - 1u && memcmp(data + start, "[glob]", n) == 0) { - sec = 2; + sec = 3; } else if (n == sizeof("[child]") - 1u && memcmp(data + start, "[child]", n) == 0) { - sec = 3; + sec = 4; } else if (n == sizeof("[fetch]") - 1u && memcmp(data + start, "[fetch]", n) == 0) { - sec = 4; + sec = 5; } else if (data[start] != '[' && data[start] != 'k') { - if (sec == 1) ++*ns; - if (sec == 2) ++*ng; - if (sec == 3) ++*nc; - if (sec == 4) ++*nf; + if (sec == 1) ++*ncfg; + if (sec == 2) ++*ns; + if (sec == 3) ++*ng; + if (sec == 4) ++*nc; + if (sec == 5) ++*nf; } } ++pos; @@ -825,12 +846,13 @@ int build_coord_deepset_load(KitBuildCoordinator* c, KitFileData fd; BuildDeepSet ds; BuildLeafSet leaf; + BuildConfigLeaf* configs = NULL; BuildSourceLeaf* sources = NULL; BuildGlobLeaf* globs = NULL; BuildFetchLeaf* fetches = NULL; uint8_t(*child_ids)[BUILD_HASH_LEN] = NULL; const BuildLeafSet** children = NULL; - size_t ns, ng, nf, nc, i; + size_t ncfg, ns, ng, nf, nc, i; int ok = BUILD_ERR; if (!c || !deepset_id || !out) return BUILD_ERR; for (memo = c->deepsets; memo; memo = memo->next) { @@ -843,7 +865,12 @@ int build_coord_deepset_load(KitBuildCoordinator* c, fd.size = 0; fd.token = NULL; if (kit_cas_get_blob(c->cas, deepset_id, &fd) != KIT_OK) return BUILD_ERR; - count_deepset_rows(fd.data, fd.size, &ns, &ng, &nf, &nc); + count_deepset_rows(fd.data, fd.size, &ncfg, &ns, &ng, &nf, &nc); + if (ncfg) { + configs = (BuildConfigLeaf*)c->ctx->heap->alloc( + c->ctx->heap, ncfg * sizeof *configs, _Alignof(BuildConfigLeaf)); + if (!configs) goto out_release; + } if (ns) { sources = (BuildSourceLeaf*)c->ctx->heap->alloc( c->ctx->heap, ns * sizeof *sources, _Alignof(BuildSourceLeaf)); @@ -867,6 +894,8 @@ int build_coord_deepset_load(KitBuildCoordinator* c, if (!child_ids || !children) goto out_release; } memset(&ds, 0, sizeof ds); + ds.configs = configs; + ds.cap_configs = ncfg; ds.sources = sources; ds.cap_sources = ns; ds.globs = globs; @@ -885,6 +914,8 @@ int build_coord_deepset_load(KitBuildCoordinator* c, memcpy(leaf.id, deepset_id, BUILD_HASH_LEN); memcpy(leaf.recipe, ds.recipe, BUILD_HASH_LEN); snprintf(leaf.target, sizeof leaf.target, "%s", ds.target); + leaf.configs = configs; + leaf.n_configs = ds.n_configs; leaf.sources = sources; leaf.n_sources = ds.n_sources; leaf.globs = globs; @@ -898,6 +929,8 @@ int build_coord_deepset_load(KitBuildCoordinator* c, out_release: kit_cas_release(c->cas, &fd); + if (configs) + c->ctx->heap->free(c->ctx->heap, configs, ncfg * sizeof *configs); if (sources) c->ctx->heap->free(c->ctx->heap, sources, ns * sizeof *sources); if (globs) c->ctx->heap->free(c->ctx->heap, globs, ng * sizeof *globs); diff --git a/src/build/coord.h b/src/build/coord.h @@ -42,6 +42,8 @@ typedef struct BuildLeafSet { uint8_t id[BUILD_HASH_LEN]; /* deep-set-id == CAS blob id of this node */ char target[BUILD_TARGET_MAX]; uint8_t recipe[BUILD_HASH_LEN]; /* recompute via defn to detect a repoint */ + BuildConfigLeaf* configs; /* DIRECT config observations */ + size_t n_configs; BuildSourceLeaf* sources; /* DIRECT leaves of this node */ size_t n_sources; BuildGlobLeaf* globs; @@ -202,10 +204,10 @@ int build_coord_deepset_load(KitBuildCoordinator*, const uint8_t deepset_id[BUILD_HASH_LEN], const BuildLeafSet** out); -/* Refresh-validity memo over the deepset DAG (keyed by deep-set-id): whether a - * subtree's leaves all still match the live workspace. build_leafset_refresh - * (resolve.h) reads/writes this so a subtree checked once is reused by every - * parent that points at it, and an unchanged subtree is skipped by id equality. */ +/* Refresh-validity slot over the deepset DAG (keyed by deep-set-id). Kept for + * coordinator-local callers that can prove the same validation context; config- + * aware deep refresh does not use this process-wide slot because validity + * depends on the current propagated config. */ int build_coord_deepset_valid_get(KitBuildCoordinator*, const uint8_t deepset_id[BUILD_HASH_LEN], int* known, int* valid); diff --git a/src/build/protocol.c b/src/build/protocol.c @@ -150,8 +150,16 @@ int build_proto_encode_req(const BuildReq* req, uint8_t* buf, size_t cap, c.pos = 0; c.err = 0; put_u8(&c, req->cmd); - if (req->cmd == BUILD_CMD_CONFIG_GET || req->cmd == BUILD_CMD_SOURCE || - req->cmd == BUILD_CMD_GLOB) { + if (req->cmd == BUILD_CMD_CONFIG_GET) { + if (!valid_arg_for_cmd(req->cmd, req->arg)) return BUILD_ERR; + if (req->argc > 1u || (req->argc && !req->argv)) return BUILD_ERR; + put_slice(&c, req->arg); + put_u16(&c, (uint16_t)req->argc); + if (req->argc) { + if (!text_valid(req->argv[0], BUILD_VAL_MAX, 1)) return BUILD_ERR; + put_slice(&c, req->argv[0]); + } + } else if (req->cmd == BUILD_CMD_SOURCE || req->cmd == BUILD_CMD_GLOB) { if (!valid_arg_for_cmd(req->cmd, req->arg)) return BUILD_ERR; put_slice(&c, req->arg); } else if (req->cmd == BUILD_CMD_FETCH) { @@ -206,8 +214,20 @@ int build_proto_decode_req(const uint8_t* buf, size_t len, BuildReq* out, c.err = 0; out->cmd = get_u8(&c); if (!known_cmd(out->cmd)) return BUILD_ERR; - if (out->cmd == BUILD_CMD_CONFIG_GET || out->cmd == BUILD_CMD_SOURCE || - out->cmd == BUILD_CMD_GLOB) { + if (out->cmd == BUILD_CMD_CONFIG_GET) { + uint16_t argc; + out->arg = get_slice(&c); + if (!valid_arg_for_cmd(out->cmd, out->arg)) return BUILD_ERR; + argc = get_u16(&c); + if (argc > 1u || (argc && (!argv_storage || argv_cap == 0u))) + return BUILD_ERR; + out->argv = argv_storage; + out->argc = argc; + if (argc) { + argv_storage[0] = get_slice(&c); + if (!text_valid(argv_storage[0], BUILD_VAL_MAX, 1)) return BUILD_ERR; + } + } else if (out->cmd == BUILD_CMD_SOURCE || out->cmd == BUILD_CMD_GLOB) { out->arg = get_slice(&c); if (!valid_arg_for_cmd(out->cmd, out->arg)) return BUILD_ERR; } else if (out->cmd == BUILD_CMD_FETCH) { @@ -259,7 +279,7 @@ int build_proto_decode_req(const uint8_t* buf, size_t len, BuildReq* out, static int status_known(uint8_t status) { return status == BUILD_RESP_OK || status == BUILD_RESP_UNSET || status == BUILD_RESP_ERROR || status == BUILD_RESP_GLOB_END || - status == BUILD_RESP_ABSENT; + status == BUILD_RESP_ABSENT || status == BUILD_RESP_DEFAULT; } int build_proto_encode_resp(const BuildReq* for_cmd, const BuildResp* resp, @@ -279,7 +299,8 @@ int build_proto_encode_resp(const BuildReq* for_cmd, const BuildResp* resp, put_slice(&c, resp->text); } else if (for_cmd->cmd == BUILD_CMD_CONFIG_GET) { if (resp->status == BUILD_RESP_UNSET) { - } else if (resp->status == BUILD_RESP_OK) { + } else if (resp->status == BUILD_RESP_OK || + resp->status == BUILD_RESP_DEFAULT) { if (!text_valid(resp->text, BUILD_VAL_MAX, 1)) return BUILD_ERR; put_slice(&c, resp->text); } else { @@ -351,7 +372,8 @@ int build_proto_decode_resp(const uint8_t* buf, size_t len, uint8_t cmd, if (!text_valid(out->text, BUILD_FRAME_MAX, 1)) return BUILD_ERR; } else if (cmd == BUILD_CMD_CONFIG_GET) { if (out->status == BUILD_RESP_UNSET) { - } else if (out->status == BUILD_RESP_OK) { + } else if (out->status == BUILD_RESP_OK || + out->status == BUILD_RESP_DEFAULT) { out->text = get_slice(&c); if (!text_valid(out->text, BUILD_VAL_MAX, 1)) return BUILD_ERR; } else { diff --git a/src/build/protocol.h b/src/build/protocol.h @@ -44,7 +44,7 @@ #define BUILD_FRAME_MAX 65536u typedef enum BuildCmd { - BUILD_CMD_CONFIG_GET = 1, /* arg: key -> value | unset */ + BUILD_CMD_CONFIG_GET = 1, /* arg: key + optional default -> value | unset */ BUILD_CMD_SOURCE = 2, /* arg: path -> blob-id + realpath */ BUILD_CMD_GLOB = 3, /* arg: pattern -> sorted match paths */ BUILD_CMD_NEED = 4, /* arg: target + k=v[] + argv[] -> tree-id + path (blocking) */ @@ -59,6 +59,7 @@ typedef enum BuildRespStatus { BUILD_RESP_ERROR = 2, /* the coordinator failed/refused the request */ BUILD_RESP_GLOB_END = 3, /* terminates a multi-frame glob match stream */ BUILD_RESP_ABSENT = 4, /* source on an absent path (not an error) */ + BUILD_RESP_DEFAULT = 5, /* config-get unset key returned supplied default */ } BuildRespStatus; /* A decoded request. `overrides` and `argv` (need / need-submit only) point into @@ -69,8 +70,8 @@ typedef struct BuildReq { KitSlice arg; /* key | path | pattern | target (unused for AWAIT) */ const KitBuildKV* overrides; /* need/submit: propagated-config overlay */ size_t noverrides; - const KitSlice* argv; /* need/submit: local argv; fetch: ordered URL hints */ - size_t argc; /* need/submit argv count; fetch URL count */ + const KitSlice* argv; /* config-get: optional default; need/submit: local argv; fetch: URL hints */ + size_t argc; /* config-get: 0/1; need/submit argv count; fetch URL count */ uint64_t token; /* need-await: which submitted need to collect */ } BuildReq; diff --git a/src/build/resolve.c b/src/build/resolve.c @@ -36,36 +36,44 @@ static size_t count_lines(const uint8_t* data, size_t len) { return n; } -static int config_values_match(const BuildConfig* a, const BuildConfig* b, - const char* key) { - KitSlice av, bv; - int ap = 0, bp = 0; - if (build_config_get(a, kit_slice_cstr(key), &av, &ap) != BUILD_OK || - build_config_get(b, kit_slice_cstr(key), &bv, &bp) != BUILD_OK) +static void hash_slice(KitSlice s, uint8_t out[BUILD_HASH_LEN]) { + KitBlobInfo bi; + static const uint8_t empty = 0; + kit_blob_info(&bi, s.len ? s.data : &empty, s.len); + memcpy(out, bi.id, BUILD_HASH_LEN); +} + +static int config_observation_match(const BuildConfig* cfg, + const BuildConfigLeaf* obs) { + KitSlice value; + uint8_t value_hash[BUILD_HASH_LEN]; + int present = 0; + if (!cfg || !obs) return 0; + if (build_config_get(cfg, kit_slice_cstr(obs->key), &value, &present) != + BUILD_OK) return 0; - if (ap != bp) return 0; - if (!ap) return 1; - return kit_slice_eq(av, bv) ? 1 : 0; + if (present) { + hash_slice(value, value_hash); + if (obs->present) return build_id_eq(value_hash, obs->value_hash); + return obs->has_default && build_id_eq(value_hash, obs->default_hash); + } + if (obs->present) return 0; + return 1; } static int shallow_direct_match(KitBuildCoordinator* c, const BuildShallowTrace* st, const BuildConfig* cfg, const uint8_t argv_id[BUILD_HASH_LEN]) { - BuildConfigEntry old_entries[128]; - BuildConfig old_cfg; uint8_t recipe[BUILD_HASH_LEN]; size_t i; - build_config_init(&old_cfg, old_entries, - sizeof old_entries / sizeof old_entries[0]); if (!build_id_eq(st->argv, argv_id)) return 0; if (build_coord_recipe_id(c, kit_slice_cstr(st->target), recipe) != BUILD_OK || !build_id_eq(recipe, st->recipe)) return 0; - if (build_coord_config_by_id(c, st->config, &old_cfg) != BUILD_OK) return 0; - for (i = 0; i < st->n_config_keys; ++i) { - if (!config_values_match(cfg, &old_cfg, st->config_keys[i].name)) return 0; + for (i = 0; i < st->n_configs; ++i) { + if (!config_observation_match(cfg, &st->configs[i])) return 0; } for (i = 0; i < st->n_sources; ++i) { uint8_t blob[BUILD_HASH_LEN]; @@ -136,8 +144,8 @@ static int try_shallow_trace(KitBuildCoordinator* c, KitSlice target, return BUILD_ERR; memcpy(out->output_tree, st->output, BUILD_HASH_LEN); memset(&log, 0, sizeof log); - log.config_keys = st->config_keys; - log.n_config_keys = st->n_config_keys; + log.configs = st->configs; + log.n_configs = st->n_configs; log.sources = st->sources; log.n_sources = st->n_sources; log.globs = st->globs; @@ -199,8 +207,8 @@ static int try_test_shallow_trace(KitBuildCoordinator* c, KitSlice target, out->status = KIT_TEST_PASS; out->exit_code = 0; memset(&log, 0, sizeof log); - log.config_keys = st->config_keys; - log.n_config_keys = st->n_config_keys; + log.configs = st->configs; + log.n_configs = st->n_configs; log.sources = st->sources; log.n_sources = st->n_sources; log.globs = st->globs; @@ -415,10 +423,9 @@ scan_record: if (build_deep_parse(fd.data, fd.size, &dt, NULL, 0) == BUILD_OK && strlen(dt.target) == target.len && memcmp(dt.target, target.s, target.len) == 0 && - build_id_eq(dt.root_config, config_id) && build_id_eq(dt.argv, argv_id) && build_coord_deepset_load(c, dt.deepset, &leaf) == BUILD_OK && - build_leafset_refresh(c, leaf, &match) == BUILD_OK && match && + build_leafset_refresh(c, leaf, cfg, &match) == BUILD_OK && match && build_materialize(c, dt.output, out->path, sizeof out->path) == BUILD_OK) { memcpy(out->output_tree, dt.output, BUILD_HASH_LEN); @@ -436,7 +443,7 @@ scan_record: } for (i = 0; i < rec.n_rows; ++i) { KitFileData fd; - BuildConfigKey keys[128]; + BuildConfigLeaf configs[128]; BuildSourceLeaf sources[128]; BuildGlobLeaf globs[128]; BuildFetchLeaf fetches[128]; @@ -452,8 +459,8 @@ scan_record: continue; rows = count_lines(fd.data, fd.size); memset(&st, 0, sizeof st); - st.config_keys = keys; - st.cap_config_keys = sizeof keys / sizeof keys[0]; + st.configs = configs; + st.cap_configs = sizeof configs / sizeof configs[0]; st.sources = sources; st.cap_sources = sizeof sources / sizeof sources[0]; st.globs = globs; @@ -462,7 +469,7 @@ scan_record: st.cap_fetches = sizeof fetches / sizeof fetches[0]; st.deps = deps; st.cap_deps = sizeof deps / sizeof deps[0]; - if (rows <= sizeof keys / sizeof keys[0] && + if (rows <= sizeof configs / sizeof configs[0] && build_shallow_parse(fd.data, fd.size, &st, NULL, 0) == BUILD_OK && strlen(st.target) == target.len && memcmp(st.target, target.s, target.len) == 0 && @@ -535,10 +542,9 @@ scan_record: if (build_test_deep_parse(fd.data, fd.size, &dt, NULL, 0) == BUILD_OK && strlen(dt.target) == target.len && memcmp(dt.target, target.s, target.len) == 0 && - build_id_eq(dt.root_config, config_id) && build_id_eq(dt.argv, argv_id) && build_coord_deepset_load(c, dt.deepset, &leaf) == BUILD_OK && - build_leafset_refresh(c, leaf, &match) == BUILD_OK && match && + build_leafset_refresh(c, leaf, cfg, &match) == BUILD_OK && match && build_materialize(c, dt.output, out->path, sizeof out->path) == BUILD_OK) { out->status = KIT_TEST_PASS; @@ -559,7 +565,7 @@ scan_record: } for (i = 0; i < rec.n_rows; ++i) { KitFileData fd; - BuildConfigKey keys[128]; + BuildConfigLeaf configs[128]; BuildSourceLeaf sources[128]; BuildGlobLeaf globs[128]; BuildFetchLeaf fetches[128]; @@ -575,8 +581,8 @@ scan_record: continue; nrows = count_lines(fd.data, fd.size); memset(&st, 0, sizeof st); - st.config_keys = keys; - st.cap_config_keys = sizeof keys / sizeof keys[0]; + st.configs = configs; + st.cap_configs = sizeof configs / sizeof configs[0]; st.sources = sources; st.cap_sources = sizeof sources / sizeof sources[0]; st.globs = globs; @@ -585,7 +591,7 @@ scan_record: st.cap_fetches = sizeof fetches / sizeof fetches[0]; st.deps = deps; st.cap_deps = sizeof deps / sizeof deps[0]; - if (nrows <= sizeof keys / sizeof keys[0] && + if (nrows <= sizeof configs / sizeof configs[0] && build_test_shallow_parse(fd.data, fd.size, &st, NULL, 0) == BUILD_OK && strlen(st.target) == target.len && @@ -670,6 +676,8 @@ int build_leafset_union(KitBuildCoordinator* c, const BuildLeafSet* direct, memset(&ds, 0, sizeof ds); snprintf(ds.target, sizeof ds.target, "%s", direct->target); memcpy(ds.recipe, direct->recipe, BUILD_HASH_LEN); + ds.configs = direct->configs; + ds.n_configs = direct->n_configs; ds.sources = direct->sources; ds.n_sources = direct->n_sources; ds.globs = direct->globs; @@ -691,6 +699,8 @@ int build_leafset_union(KitBuildCoordinator* c, const BuildLeafSet* direct, build_deepset_id(bytes, len, leaf.id); snprintf(leaf.target, sizeof leaf.target, "%s", direct->target); memcpy(leaf.recipe, direct->recipe, BUILD_HASH_LEN); + leaf.configs = direct->configs; + leaf.n_configs = direct->n_configs; leaf.sources = direct->sources; leaf.n_sources = direct->n_sources; leaf.globs = direct->globs; @@ -713,24 +723,19 @@ out: } int build_leafset_refresh(KitBuildCoordinator* c, const BuildLeafSet* leafset, - int* all_match) { + const BuildConfig* cfg, int* all_match) { size_t i; KitSlice target; uint8_t recipe[BUILD_HASH_LEN]; - int known = 0, valid = 0; - if (!c || !leafset || !all_match) return BUILD_ERR; + if (!c || !leafset || !cfg || !all_match) return BUILD_ERR; *all_match = 0; - if (build_coord_deepset_valid_get(c, leafset->id, &known, &valid) != - BUILD_OK) - return BUILD_ERR; - if (known) { - *all_match = valid; - return BUILD_OK; - } target = kit_slice_cstr(leafset->target); if (build_coord_recipe_id(c, target, recipe) != BUILD_OK || !build_id_eq(recipe, leafset->recipe)) goto done; + for (i = 0; i < leafset->n_configs; ++i) { + if (!config_observation_match(cfg, &leafset->configs[i])) goto done; + } for (i = 0; i < leafset->n_sources; ++i) { uint8_t blob[BUILD_HASH_LEN]; int present = 0; @@ -756,13 +761,12 @@ int build_leafset_refresh(KitBuildCoordinator* c, const BuildLeafSet* leafset, } for (i = 0; i < leafset->n_children; ++i) { int child_match = 0; - if (build_leafset_refresh(c, leafset->children[i], &child_match) != + if (build_leafset_refresh(c, leafset->children[i], cfg, &child_match) != BUILD_OK) return BUILD_ERR; if (!child_match) goto done; } *all_match = 1; done: - build_coord_deepset_valid_set(c, leafset->id, *all_match); return BUILD_OK; } diff --git a/src/build/resolve.h b/src/build/resolve.h @@ -155,6 +155,6 @@ int build_leafset_union(KitBuildCoordinator*, const BuildLeafSet* direct, * divergence (Phase 1 then gives up). Phase 1 calls build_coord_deepset_load to * turn the trace's deep-set-id into the node passed here. */ int build_leafset_refresh(KitBuildCoordinator*, const BuildLeafSet*, - int* all_match); + const BuildConfig*, int* all_match); #endif diff --git a/src/build/runner.c b/src/build/runner.c @@ -28,20 +28,53 @@ static int target_copy(KitSlice target, char out[BUILD_TARGET_MAX]) { return BUILD_OK; } -static int append_config_key(BuildDepLog* log, KitSlice key) { - BuildConfigKey* row; +static void hash_slice(KitSlice s, uint8_t out[BUILD_HASH_LEN]) { + KitBlobInfo bi; + static const uint8_t empty = 0; + kit_blob_info(&bi, s.len ? s.data : &empty, s.len); + memcpy(out, bi.id, BUILD_HASH_LEN); +} + +static int config_leaf_eq(const BuildConfigLeaf* a, const BuildConfigLeaf* b) { + return strcmp(a->key, b->key) == 0 && a->present == b->present && + a->has_default == b->has_default && + build_id_eq(a->value_hash, b->value_hash) && + build_id_eq(a->default_hash, b->default_hash); +} + +static int append_config_observation(BuildDepLog* log, const BuildConfig* cfg, + KitSlice key, KitSlice default_value, + int has_default) { + BuildConfigLeaf leaf; + BuildConfigLeaf* row; + KitSlice actual; size_t i; if (!log || !key.s || key.len == 0u || key.len >= BUILD_KEY_MAX) return BUILD_ERR; - for (i = 0; i < log->n_config_keys; ++i) { - if (strlen(log->config_keys[i].name) == key.len && - memcmp(log->config_keys[i].name, key.s, key.len) == 0) + if (has_default && + (default_value.len >= BUILD_VAL_MAX || + (default_value.len && !default_value.s))) + return BUILD_ERR; + memset(&leaf, 0, sizeof leaf); + memcpy(leaf.key, key.s, key.len); + leaf.key[key.len] = '\0'; + actual = KIT_SLICE_NULL; + if (build_config_get(cfg, key, &actual, &leaf.present) != BUILD_OK) + return BUILD_ERR; + leaf.has_default = has_default ? 1 : 0; + if (leaf.present) { + hash_slice(actual, leaf.value_hash); + } else if (leaf.has_default) { + hash_slice(default_value, leaf.value_hash); + } + if (leaf.has_default) hash_slice(default_value, leaf.default_hash); + for (i = 0; i < log->n_configs; ++i) { + if (config_leaf_eq(&log->configs[i], &leaf)) return BUILD_OK; } - if (log->n_config_keys >= log->cap_config_keys) return BUILD_ERR; - row = &log->config_keys[log->n_config_keys++]; - memcpy(row->name, key.s, key.len); - row->name[key.len] = '\0'; + if (log->n_configs >= log->cap_configs) return BUILD_ERR; + row = &log->configs[log->n_configs++]; + *row = leaf; return BUILD_OK; } @@ -347,7 +380,7 @@ static int build_run_recipe_impl(KitBuildCoordinator* c, KitSlice target, size_t argc = 0, nenv = 0, i; int exit_code = 1; int ok = BUILD_ERR; - BuildConfigKey config_keys[128]; + BuildConfigLeaf configs[128]; BuildSourceLeaf sources[128]; BuildGlobLeaf globs[128]; BuildFetchLeaf fetches[128]; @@ -379,8 +412,8 @@ static int build_run_recipe_impl(KitBuildCoordinator* c, KitSlice target, goto out_cleanup; memset(&log, 0, sizeof log); - log.config_keys = config_keys; - log.cap_config_keys = sizeof config_keys / sizeof config_keys[0]; + log.configs = configs; + log.cap_configs = sizeof configs / sizeof configs[0]; log.sources = sources; log.cap_sources = sizeof sources / sizeof sources[0]; log.globs = globs; @@ -411,7 +444,8 @@ static int build_run_recipe_impl(KitBuildCoordinator* c, KitSlice target, const char* key = cfg->entries[i].key; size_t prefix_len = sizeof(KIT_BUILD_ENV_PREFIX) - 1u; if (strncmp(key, KIT_BUILD_ENV_PREFIX, prefix_len) != 0) continue; - if (append_config_key(&log, kit_slice_cstr(key)) != BUILD_OK) + if (append_config_observation(&log, cfg, kit_slice_cstr(key), + KIT_SLICE_NULL, 0) != BUILD_OK) goto out_cleanup; snprintf(env_keys[nenv - 3u], sizeof env_keys[nenv - 3u], "%s", key + prefix_len); @@ -477,7 +511,7 @@ static int build_run_test_recipe_impl(KitBuildCoordinator* c, KitSlice target, size_t argc = 0, nenv = 0, i; int exit_code = 1; int ok = BUILD_ERR; - BuildConfigKey config_keys[128]; + BuildConfigLeaf configs[128]; BuildSourceLeaf sources[128]; BuildGlobLeaf globs[128]; BuildFetchLeaf fetches[128]; @@ -517,8 +551,8 @@ static int build_run_test_recipe_impl(KitBuildCoordinator* c, KitSlice target, goto out_cleanup; memset(&log, 0, sizeof log); - log.config_keys = config_keys; - log.cap_config_keys = sizeof config_keys / sizeof config_keys[0]; + log.configs = configs; + log.cap_configs = sizeof configs / sizeof configs[0]; log.sources = sources; log.cap_sources = sizeof sources / sizeof sources[0]; log.globs = globs; @@ -549,7 +583,8 @@ static int build_run_test_recipe_impl(KitBuildCoordinator* c, KitSlice target, const char* key = cfg->entries[i].key; size_t prefix_len = sizeof(KIT_BUILD_ENV_PREFIX) - 1u; if (strncmp(key, KIT_BUILD_ENV_PREFIX, prefix_len) != 0) continue; - if (append_config_key(&log, kit_slice_cstr(key)) != BUILD_OK) + if (append_config_observation(&log, cfg, kit_slice_cstr(key), + KIT_SLICE_NULL, 0) != BUILD_OK) goto out_cleanup; snprintf(env_keys[nenv - 3u], sizeof env_keys[nenv - 3u], "%s", key + prefix_len); @@ -652,8 +687,13 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn, KitSlice value; int present = 0; if (build_config_get(cfg, req.arg, &value, &present) != BUILD_OK || - append_config_key(log, req.arg) != BUILD_OK) { + append_config_observation(log, cfg, req.arg, + req.argc ? req.argv[0] : KIT_SLICE_NULL, + req.argc ? 1 : 0) != BUILD_OK) { resp_error(&resp, KIT_ERR, "config-get failed"); + } else if (!present && req.argc) { + resp.status = BUILD_RESP_DEFAULT; + resp.text = req.argv[0]; } else if (!present) { resp.status = BUILD_RESP_UNSET; } else { @@ -828,6 +868,8 @@ int build_runner_record_traces(KitBuildCoordinator* c, KitSlice target, if (target_copy(target, direct.target) != BUILD_OK) return BUILD_ERR; if (build_coord_recipe_id(c, target, direct.recipe) != BUILD_OK) return BUILD_ERR; + direct.configs = log->configs; + direct.n_configs = log->n_configs; direct.sources = log->sources; direct.n_sources = log->n_sources; direct.globs = log->globs; @@ -847,8 +889,8 @@ int build_runner_record_traces(KitBuildCoordinator* c, KitSlice target, if (store_config_blob(c, cfg, shallow.config) != BUILD_OK || store_argv_blob(c, argv, shallow.argv) != BUILD_OK) return BUILD_ERR; - shallow.config_keys = log->config_keys; - shallow.n_config_keys = log->n_config_keys; + shallow.configs = log->configs; + shallow.n_configs = log->n_configs; shallow.sources = log->sources; shallow.n_sources = log->n_sources; shallow.globs = log->globs; @@ -876,7 +918,6 @@ int build_runner_record_traces(KitBuildCoordinator* c, KitSlice target, if (target_copy(target, deep.target) != BUILD_OK) return BUILD_ERR; memcpy(deep.recipe, direct.recipe, BUILD_HASH_LEN); memcpy(deep.output, output, BUILD_HASH_LEN); - memcpy(deep.root_config, shallow.config, BUILD_HASH_LEN); memcpy(deep.argv, shallow.argv, BUILD_HASH_LEN); memcpy(deep.deepset, (*out_leafset)->id, BUILD_HASH_LEN); if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR; @@ -918,6 +959,8 @@ int build_runner_record_test_traces(KitBuildCoordinator* c, KitSlice target, if (target_copy(target, direct.target) != BUILD_OK) return BUILD_ERR; if (build_coord_recipe_id(c, target, direct.recipe) != BUILD_OK) return BUILD_ERR; + direct.configs = log->configs; + direct.n_configs = log->n_configs; direct.sources = log->sources; direct.n_sources = log->n_sources; direct.globs = log->globs; @@ -937,8 +980,8 @@ int build_runner_record_test_traces(KitBuildCoordinator* c, KitSlice target, if (store_config_blob(c, cfg, shallow.config) != BUILD_OK || store_argv_blob(c, argv, shallow.argv) != BUILD_OK) return BUILD_ERR; - shallow.config_keys = log->config_keys; - shallow.n_config_keys = log->n_config_keys; + shallow.configs = log->configs; + shallow.n_configs = log->n_configs; shallow.sources = log->sources; shallow.n_sources = log->n_sources; shallow.globs = log->globs; @@ -966,7 +1009,6 @@ int build_runner_record_test_traces(KitBuildCoordinator* c, KitSlice target, if (target_copy(target, deep.target) != BUILD_OK) return BUILD_ERR; memcpy(deep.recipe, direct.recipe, BUILD_HASH_LEN); memcpy(deep.output, result, BUILD_HASH_LEN); - memcpy(deep.root_config, shallow.config, BUILD_HASH_LEN); memcpy(deep.argv, shallow.argv, BUILD_HASH_LEN); memcpy(deep.deepset, (*out_leafset)->id, BUILD_HASH_LEN); if (kit_writer_mem(c->ctx->heap, &w) != KIT_OK || !w) return BUILD_ERR; diff --git a/src/build/runner.h b/src/build/runner.h @@ -46,8 +46,8 @@ typedef struct BuildPendingNeed { * pointer to the root deepset node, built by build_leafset_union over these direct * leaves + the child nodes). Sections hang off caller/arena storage (no VLAs). */ typedef struct BuildDepLog { - BuildConfigKey* config_keys; /* consumed propagated key names */ - size_t n_config_keys, cap_config_keys; + BuildConfigLeaf* configs; /* direct propagated-config observations */ + size_t n_configs, cap_configs; BuildSourceLeaf* sources; /* direct source reads (absent recorded) */ size_t n_sources, cap_sources; BuildGlobLeaf* globs; /* direct globs */ diff --git a/src/build/trace.c b/src/build/trace.c @@ -94,8 +94,6 @@ static int emit_text_kv(KitWriter* out, const char* key, const char* val) { return emit_bytes(out, line); } -static int str_cmp(const char* a, const char* b) { return strcmp(a, b); } - static int id_cmp(const uint8_t a[BUILD_HASH_LEN], const uint8_t b[BUILD_HASH_LEN]) { return memcmp(a, b, BUILD_HASH_LEN); @@ -113,6 +111,17 @@ static int fetch_cmp(const BuildFetchLeaf* a, const BuildFetchLeaf* b) { return id_cmp(a->blob, b->blob); } +static int config_cmp(const BuildConfigLeaf* a, const BuildConfigLeaf* b) { + int c = strcmp(a->key, b->key); + if (c != 0) return c; + if (a->present != b->present) return a->present < b->present ? -1 : 1; + if (a->has_default != b->has_default) + return a->has_default < b->has_default ? -1 : 1; + c = id_cmp(a->value_hash, b->value_hash); + if (c != 0) return c; + return id_cmp(a->default_hash, b->default_hash); +} + static int dep_cmp(const BuildDepEdge* a, const BuildDepEdge* b) { int c = strcmp(a->name, b->name); if (c != 0) return c; @@ -129,27 +138,49 @@ static int emit_section(KitWriter* out, const char* name) { return emit_bytes(out, line); } -static int emit_sorted_config_keys(const BuildConfigKey* rows, size_t n, - KitWriter* out, char* err, size_t errcap) { - const BuildConfigKey* prev = NULL; +static int emit_hash_or_dash(KitWriter* out, int has_hash, + const uint8_t h[BUILD_HASH_LEN]) { + char hex[BUILD_HEX_LEN]; + if (!has_hash) return emit_bytes(out, "-"); + hex_encode(hex, h); + return emit_bytes(out, hex); +} + +static int emit_config_row(KitWriter* out, const BuildConfigLeaf* row) { + if (emit_bytes(out, row->key) != BUILD_OK) return BUILD_ERR; + if (emit_bytes(out, row->present ? " present " : " unset ") != BUILD_OK) + return BUILD_ERR; + if (emit_hash_or_dash(out, row->present || row->has_default, + row->value_hash) != BUILD_OK) + return BUILD_ERR; + if (emit_bytes(out, " ") != BUILD_OK) return BUILD_ERR; + if (emit_hash_or_dash(out, row->has_default, row->default_hash) != BUILD_OK) + return BUILD_ERR; + return emit_bytes(out, "\n"); +} + +static int emit_sorted_configs(const BuildConfigLeaf* rows, size_t n, + KitWriter* out, char* err, size_t errcap) { + const BuildConfigLeaf* prev = NULL; size_t emitted = 0; if (n && !rows) return set_err(err, errcap, "missing config rows"); if (emit_section(out, "config") != BUILD_OK) return BUILD_ERR; while (emitted < n) { - const BuildConfigKey* best = NULL; + const BuildConfigLeaf* best = NULL; size_t i; for (i = 0; i < n; ++i) { - const BuildConfigKey* cur = &rows[i]; + const BuildConfigLeaf* cur = &rows[i]; int after_prev; - if (!valid_key(cur->name)) + if (!valid_key(cur->key)) return set_err(err, errcap, "bad config key"); - after_prev = !prev || str_cmp(prev->name, cur->name) < 0; - if (after_prev && (!best || str_cmp(cur->name, best->name) < 0)) - best = cur; + if ((cur->present != 0 && cur->present != 1) || + (cur->has_default != 0 && cur->has_default != 1)) + return set_err(err, errcap, "bad config flags"); + after_prev = !prev || config_cmp(prev, cur) < 0; + if (after_prev && (!best || config_cmp(cur, best) < 0)) best = cur; } if (!best) return set_err(err, errcap, "duplicate config key"); - if (emit_bytes(out, best->name) != BUILD_OK || emit_bytes(out, "\n") != BUILD_OK) - return BUILD_ERR; + if (emit_config_row(out, best) != BUILD_OK) return BUILD_ERR; prev = best; ++emitted; } @@ -392,8 +423,8 @@ static int shallow_emit_like(const BuildShallowTrace* t, KitWriter* out, if (emit_kv(out, result_key, t->output) != BUILD_OK) return BUILD_ERR; if (emit_kv(out, "config", t->config) != BUILD_OK) return BUILD_ERR; if (emit_kv(out, "argv", t->argv) != BUILD_OK) return BUILD_ERR; - if (emit_sorted_config_keys(t->config_keys, t->n_config_keys, out, err, - errcap) != BUILD_OK) + if (emit_sorted_configs(t->configs, t->n_configs, out, err, errcap) != + BUILD_OK) return BUILD_ERR; if (emit_sorted_sources(t->sources, t->n_sources, out, err, errcap) != BUILD_OK) @@ -429,8 +460,6 @@ static int deep_emit_like(const BuildDeepTrace* t, KitWriter* out, char* err, if (emit_text_kv(out, "target", t->target) != BUILD_OK) return BUILD_ERR; if (emit_kv(out, "recipe", t->recipe) != BUILD_OK) return BUILD_ERR; if (emit_kv(out, result_key, t->output) != BUILD_OK) return BUILD_ERR; - if (emit_kv(out, "root-config", t->root_config) != BUILD_OK) - return BUILD_ERR; if (emit_kv(out, "argv", t->argv) != BUILD_OK) return BUILD_ERR; if (emit_kv(out, "deepset", t->deepset) != BUILD_OK) return BUILD_ERR; return kit_writer_status(out) == KIT_OK ? BUILD_OK : BUILD_ERR; @@ -453,6 +482,9 @@ int build_deepset_emit(const BuildDeepSet* t, KitWriter* out, char* err, if (emit_bytes(out, BUILD_DEEPSET_MAGIC "\n") != BUILD_OK) return BUILD_ERR; if (emit_text_kv(out, "target", t->target) != BUILD_OK) return BUILD_ERR; if (emit_kv(out, "recipe", t->recipe) != BUILD_OK) return BUILD_ERR; + if (emit_sorted_configs(t->configs, t->n_configs, out, err, errcap) != + BUILD_OK) + return BUILD_ERR; if (emit_sorted_sources(t->sources, t->n_sources, out, err, errcap) != BUILD_OK) return BUILD_ERR; @@ -574,17 +606,70 @@ static int parse_top_hash(char** fields, size_t n, const char* key, return BUILD_OK; } -static int append_config_key(BuildShallowTrace* out, const char* name, char* err, - size_t errcap) { - BuildConfigKey* row; - if (out->n_config_keys >= out->cap_config_keys) - return set_err(err, errcap, "too many config keys"); - if (!valid_key(name)) return set_err(err, errcap, "bad config key"); - if (out->n_config_keys && - strcmp(out->config_keys[out->n_config_keys - 1u].name, name) >= 0) - return set_err(err, errcap, "non-canonical config keys"); - row = &out->config_keys[out->n_config_keys++]; - snprintf(row->name, sizeof row->name, "%s", name); +static int parse_hash_or_dash(const char* s, int has_hash, + uint8_t out[BUILD_HASH_LEN], char* err, + size_t errcap) { + if (!has_hash) { + if (strcmp(s, "-") != 0) return set_err(err, errcap, "bad config hash"); + memset(out, 0, BUILD_HASH_LEN); + return BUILD_OK; + } + if (hex_decode_strict(s, out) != BUILD_OK) + return set_err(err, errcap, "bad config hash"); + return BUILD_OK; +} + +static int parse_config_leaf(char** fields, size_t n, BuildConfigLeaf* row, + char* err, size_t errcap) { + if (expect_field_count(n, 4u, err, errcap) != BUILD_OK) return BUILD_ERR; + if (!valid_key(fields[0])) return set_err(err, errcap, "bad config key"); + snprintf(row->key, sizeof row->key, "%s", fields[0]); + if (strcmp(fields[1], "present") == 0) { + row->present = 1; + } else if (strcmp(fields[1], "unset") == 0) { + row->present = 0; + } else { + return set_err(err, errcap, "bad config presence"); + } + row->has_default = strcmp(fields[3], "-") != 0; + if (parse_hash_or_dash(fields[2], row->present || row->has_default, + row->value_hash, err, errcap) != BUILD_OK || + parse_hash_or_dash(fields[3], row->has_default, row->default_hash, err, + errcap) != BUILD_OK) + return BUILD_ERR; + if (!row->present && !row->has_default && strcmp(fields[2], "-") != 0) + return set_err(err, errcap, "bad unset config value"); + if (!row->present && row->has_default && + !build_id_eq(row->value_hash, row->default_hash)) + return set_err(err, errcap, "bad default config value"); + return BUILD_OK; +} + +static int append_shallow_config(BuildShallowTrace* out, char** fields, + size_t n, char* err, size_t errcap) { + BuildConfigLeaf* row; + if (out->n_configs >= out->cap_configs) + return set_err(err, errcap, "too many config rows"); + row = &out->configs[out->n_configs]; + if (parse_config_leaf(fields, n, row, err, errcap) != BUILD_OK) + return BUILD_ERR; + if (out->n_configs && config_cmp(&out->configs[out->n_configs - 1u], row) >= 0) + return set_err(err, errcap, "non-canonical config rows"); + ++out->n_configs; + return BUILD_OK; +} + +static int append_deepset_config(BuildDeepSet* out, char** fields, size_t n, + char* err, size_t errcap) { + BuildConfigLeaf* row; + if (out->n_configs >= out->cap_configs) + return set_err(err, errcap, "too many config rows"); + row = &out->configs[out->n_configs]; + if (parse_config_leaf(fields, n, row, err, errcap) != BUILD_OK) + return BUILD_ERR; + if (out->n_configs && config_cmp(&out->configs[out->n_configs - 1u], row) >= 0) + return set_err(err, errcap, "non-canonical config rows"); + ++out->n_configs; return BUILD_OK; } @@ -782,13 +867,13 @@ static int shallow_parse_like(const uint8_t* data, size_t len, TraceSection sec = TRACE_SEC_TOP; uint32_t seen = 0; if (!data || !out) return set_err(err, errcap, "missing shallow trace"); - if ((out->cap_config_keys && !out->config_keys) || + if ((out->cap_configs && !out->configs) || (out->cap_sources && !out->sources) || (out->cap_globs && !out->globs) || (out->cap_fetches && !out->fetches) || (out->cap_deps && !out->deps)) return set_err(err, errcap, "missing trace storage"); memset(out->target, 0, sizeof out->target); - out->n_config_keys = 0; + out->n_configs = 0; out->n_sources = 0; out->n_globs = 0; out->n_fetches = 0; @@ -857,8 +942,7 @@ static int shallow_parse_like(const uint8_t* data, size_t len, return set_err(err, errcap, "unexpected shallow field"); } } else if (sec == TRACE_SEC_CONFIG) { - if (expect_field_count(n, 1u, err, errcap) != BUILD_OK || - append_config_key(out, fields[0], err, errcap) != BUILD_OK) + if (append_shallow_config(out, fields, n, err, errcap) != BUILD_OK) return BUILD_ERR; } else if (sec == TRACE_SEC_SOURCE) { if (append_shallow_source(out, fields, n, err, errcap) != BUILD_OK) @@ -933,10 +1017,6 @@ static int deep_parse_like(const uint8_t* data, size_t len, if (parse_top_hash(fields, n, result_key, out->output, &seen, F_OUTPUT, err, errcap) != BUILD_OK) return BUILD_ERR; - } else if (!(seen & F_CONFIG)) { - if (parse_top_hash(fields, n, "root-config", out->root_config, &seen, - F_CONFIG, err, errcap) != BUILD_OK) - return BUILD_ERR; } else if (!(seen & F_ARGV)) { if (parse_top_hash(fields, n, "argv", out->argv, &seen, F_ARGV, err, errcap) != BUILD_OK) @@ -950,7 +1030,7 @@ static int deep_parse_like(const uint8_t* data, size_t len, } } if (p.first) return set_err(err, errcap, "bad deep magic/version"); - if (seen != (F_TARGET | F_RECIPE | F_OUTPUT | F_CONFIG | F_ARGV | F_DEEPSET)) + if (seen != (F_TARGET | F_RECIPE | F_OUTPUT | F_ARGV | F_DEEPSET)) return set_err(err, errcap, "missing deep field"); return BUILD_OK; } @@ -973,11 +1053,13 @@ int build_deepset_parse(const uint8_t* data, size_t len, BuildDeepSet* out, TraceSection sec = TRACE_SEC_TOP; uint32_t seen = 0; if (!data || !out) return set_err(err, errcap, "missing deepset"); - if ((out->cap_sources && !out->sources) || (out->cap_globs && !out->globs) || + if ((out->cap_configs && !out->configs) || + (out->cap_sources && !out->sources) || (out->cap_globs && !out->globs) || (out->cap_fetches && !out->fetches) || (out->cap_children && !out->children)) return set_err(err, errcap, "missing deepset storage"); memset(out->target, 0, sizeof out->target); + out->n_configs = 0; out->n_sources = 0; out->n_globs = 0; out->n_fetches = 0; @@ -988,7 +1070,7 @@ int build_deepset_parse(const uint8_t* data, size_t len, BuildDeepSet* out, p.first = 1; for (;;) { char* line; - char* fields[2]; + char* fields[4]; size_t n = 0; int r = parser_next(&p, &line, err, errcap); if (r < 0) return BUILD_ERR; @@ -1000,9 +1082,11 @@ int build_deepset_parse(const uint8_t* data, size_t len, BuildDeepSet* out, continue; } if (line[0] == '[') { - if (strcmp(line, "[source]") == 0 && sec == TRACE_SEC_TOP) { + if (strcmp(line, "[config]") == 0 && sec == TRACE_SEC_TOP) { if (seen != (F_TARGET | F_RECIPE)) return set_err(err, errcap, "missing deepset field"); + sec = TRACE_SEC_CONFIG; + } else if (strcmp(line, "[source]") == 0 && sec == TRACE_SEC_CONFIG) { sec = TRACE_SEC_SOURCE; } else if (strcmp(line, "[glob]") == 0 && sec == TRACE_SEC_SOURCE) { sec = TRACE_SEC_GLOB; @@ -1031,6 +1115,9 @@ int build_deepset_parse(const uint8_t* data, size_t len, BuildDeepSet* out, } else { return set_err(err, errcap, "unexpected deepset field"); } + } else if (sec == TRACE_SEC_CONFIG) { + if (append_deepset_config(out, fields, n, err, errcap) != BUILD_OK) + return BUILD_ERR; } else if (sec == TRACE_SEC_SOURCE) { if (append_deepset_source(out, fields, n, err, errcap) != BUILD_OK) return BUILD_ERR; diff --git a/src/build/trace.h b/src/build/trace.h @@ -63,13 +63,18 @@ typedef struct BuildDepEdge { uint8_t output_tree[BUILD_HASH_LEN]; } BuildDepEdge; -/* One consumed propagated-key NAME (shallow only). A key is "consumed" whether - * it was set or unset; its value lives in the config-id map this trace - * references (absent there == consumed-while-unset, so adding it later busts - * the trace). Sorted. */ -typedef struct BuildConfigKey { - char name[BUILD_KEY_MAX]; -} BuildConfigKey; +/* One observed propagated config lookup. `present` records whether the key was + * set in the underlying propagated config. `has_default` records whether the + * recipe supplied a default. `value_hash` is the returned value hash: actual + * value when present, default value when absent with a default, otherwise + * unused. `default_hash` is meaningful only when has_default. Sorted by key. */ +typedef struct BuildConfigLeaf { + char key[BUILD_KEY_MAX]; + uint8_t value_hash[BUILD_HASH_LEN]; + uint8_t default_hash[BUILD_HASH_LEN]; + int present; + int has_default; +} BuildConfigLeaf; /* The shallow trace: direct inputs + direct deps, for fine-grained rechecking. * `config` is this target's effective propagated config-id (full map blob); @@ -80,9 +85,9 @@ typedef struct BuildShallowTrace { uint8_t output[BUILD_HASH_LEN]; uint8_t config[BUILD_HASH_LEN]; uint8_t argv[BUILD_HASH_LEN]; - BuildConfigKey* config_keys; /* [config] section */ - size_t n_config_keys; - size_t cap_config_keys; + BuildConfigLeaf* configs; /* [config] section */ + size_t n_configs; + size_t cap_configs; BuildSourceLeaf* sources; /* [source] section */ size_t n_sources; size_t cap_sources; @@ -97,19 +102,18 @@ typedef struct BuildShallowTrace { size_t cap_deps; } BuildShallowTrace; -/* The deep trace: the (root config-id, argv) binding to an output, plus a single - * pointer to the TRANSITIVE input closure, held structurally as a deepset DAG - * (below) rather than inlined. If the request's config-id equals `root_config`, - * its argv-id equals `argv`, and refreshing the `deepset` closure finds nothing - * moved (every source/glob/fetch leaf still matches AND every node's recipe-id - * still recomputes the same), determinism makes the output identical — no recipe runs. +/* The deep trace: the argv binding to an output, plus a single pointer to the + * TRANSITIVE input closure, held structurally as a deepset DAG + * (below) rather than inlined. If the request's argv-id equals `argv`, and + * refreshing the `deepset` closure finds nothing moved (every config/source/ + * glob/fetch leaf still matches AND every node's recipe-id still recomputes the + * same), determinism makes the output identical — no recipe runs. * The deepset blob must be present to refresh; if absent (GC'd), the trace is * treated as absent and resolution falls through (fail-safe). */ typedef struct BuildDeepTrace { char target[BUILD_TARGET_MAX]; uint8_t recipe[BUILD_HASH_LEN]; uint8_t output[BUILD_HASH_LEN]; - uint8_t root_config[BUILD_HASH_LEN]; uint8_t argv[BUILD_HASH_LEN]; uint8_t deepset[BUILD_HASH_LEN]; /* deep-set-id of the closure (a CAS blob) */ } BuildDeepTrace; @@ -126,10 +130,14 @@ typedef struct BuildDeepTrace { * trace, which is a CLAIM in build/trace/. `target` is carried so refresh can * recompute the node's recipe-id through the live definition — catching a dep * repointed to a different-content recipe, while treating same-content as - * unchanged. Sorted: [source] by path, [glob] by pattern, [child] by id. */ + * unchanged. Sorted: [config] by observation row, [source] by path, [glob] by + * pattern, [fetch] by blob id, [child] by id. */ typedef struct BuildDeepSet { char target[BUILD_TARGET_MAX]; uint8_t recipe[BUILD_HASH_LEN]; + BuildConfigLeaf* configs; /* this node's DIRECT config observations */ + size_t n_configs; + size_t cap_configs; BuildSourceLeaf* sources; /* this node's DIRECT source leaves */ size_t n_sources; size_t cap_sources; diff --git a/test/build/build_public_link_test.c b/test/build/build_public_link_test.c @@ -193,8 +193,9 @@ static void test_client_transport(void) { EXPECT(kit_build_client_open(&g_u.ctx, &tr, &client) == KIT_OK && client && tt.dialed == 1, "client open dials env endpoint"); - EXPECT(kit_build_client_config_get(client, KIT_SLICE_LIT("mode"), &value, - &present) == KIT_OK && + EXPECT(kit_build_client_config_get(client, KIT_SLICE_LIT("mode"), + KIT_SLICE_NULL, 0, &value, &present) == + KIT_OK && present && kit_slice_eq(value, KIT_SLICE_LIT("debug")), "client config get"); EXPECT(kit_build_client_source(client, KIT_SLICE_LIT("src/main.c"), blob, @@ -272,8 +273,8 @@ static void test_public_symbols(void) { EXPECT(kit_build_client_open(NULL, NULL, &client) == KIT_INVALID && client == NULL, "client open rejects invalid args"); - EXPECT(kit_build_client_config_get(NULL, KIT_SLICE_LIT("x"), &value, - &present) == KIT_INVALID && + EXPECT(kit_build_client_config_get(NULL, KIT_SLICE_LIT("x"), KIT_SLICE_NULL, + 0, &value, &present) == KIT_INVALID && value.s == NULL && value.len == 0 && present == 0, "client config rejects invalid args"); EXPECT(kit_build_client_source(NULL, KIT_SLICE_LIT("x"), result.output_tree, diff --git a/test/build/build_pure_test.c b/test/build/build_pure_test.c @@ -163,11 +163,13 @@ static void test_defn(void) { static void test_trace(void) { BuildShallowTrace st; - BuildConfigKey keys[2]; + BuildConfigLeaf keys[2]; + BuildConfigLeaf ds_configs[2]; BuildSourceLeaf sources[2], parsed_sources[2]; BuildGlobLeaf globs[1], parsed_globs[1]; BuildDepEdge deps[1], parsed_deps[1]; - BuildConfigKey parsed_keys[2]; + BuildConfigLeaf parsed_keys[2]; + BuildDeepSet parsed_ds; BuildShallowTrace parsed; BuildDeepTrace deep, parsed_deep; BuildTargetRecord rec; @@ -193,6 +195,37 @@ static void test_trace(void) { "a.c -\n" "[glob]\n" "[dep]\n"; + static const char bad_config[] = + "kit-build-shallow 1\n" + "target //app:bin\n" + "recipe " + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\n" + "output " + "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20\n" + "config " + "02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021\n" + "argv " + "030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122\n" + "[config]\n" + "z present " + "0a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272829 -\n" + "a unset - -\n" + "[source]\n" + "[glob]\n" + "[dep]\n"; + static const char bad_deepset_config[] = + "kit-build-deepset 1\n" + "target //app:bin\n" + "recipe " + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\n" + "[config]\n" + "z present " + "0a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272829 -\n" + "a unset - -\n" + "[source]\n" + "[glob]\n" + "[fetch]\n" + "[child]\n"; memset(&st, 0, sizeof st); strcpy(st.target, "//app:bin"); @@ -200,11 +233,19 @@ static void test_trace(void) { fill_id(st.output, 1); fill_id(st.config, 2); fill_id(st.argv, 3); - st.config_keys = keys; - st.n_config_keys = 2; - st.cap_config_keys = 2; - strcpy(keys[0].name, "z"); - strcpy(keys[1].name, "a"); + st.configs = keys; + st.n_configs = 2; + st.cap_configs = 2; + strcpy(keys[0].key, "z"); + keys[0].present = 1; + keys[0].has_default = 0; + fill_id(keys[0].value_hash, 10); + memset(keys[0].default_hash, 0, sizeof keys[0].default_hash); + strcpy(keys[1].key, "a"); + keys[1].present = 0; + keys[1].has_default = 0; + memset(keys[1].value_hash, 0, sizeof keys[1].value_hash); + memset(keys[1].default_hash, 0, sizeof keys[1].default_hash); st.sources = sources; st.n_sources = 2; st.cap_sources = 2; @@ -234,8 +275,8 @@ static void test_trace(void) { build_trace_id(bytes.data, bytes.len, trace_id); memset(&parsed, 0, sizeof parsed); - parsed.config_keys = parsed_keys; - parsed.cap_config_keys = 2; + parsed.configs = parsed_keys; + parsed.cap_configs = 2; parsed.sources = parsed_sources; parsed.cap_sources = 2; parsed.globs = parsed_globs; @@ -245,7 +286,7 @@ static void test_trace(void) { EXPECT(build_shallow_parse(bytes.data, bytes.len, &parsed, err, sizeof err) == BUILD_OK, "parse shallow"); - EXPECT(strcmp(parsed.config_keys[0].name, "a") == 0 && + EXPECT(strcmp(parsed.configs[0].key, "a") == 0 && strcmp(parsed.sources[0].path, "src/a.c") == 0 && parsed.sources[0].absent && memcmp(parsed.deps[0].overlay_id, deps[0].overlay_id, @@ -254,6 +295,10 @@ static void test_trace(void) { EXPECT(build_shallow_parse((const uint8_t*)dup_source, sizeof dup_source - 1u, &parsed, err, sizeof err) == BUILD_ERR, "reject duplicate source"); + EXPECT(build_shallow_parse((const uint8_t*)bad_config, + sizeof bad_config - 1u, &parsed, err, + sizeof err) == BUILD_ERR, + "reject non-canonical shallow config"); kit_writer_close(w); w = NULL; @@ -268,8 +313,8 @@ static void test_trace(void) { !bytes_contains(bytes.data, bytes.len, "\noutput "), "test shallow uses result field"); memset(&parsed, 0, sizeof parsed); - parsed.config_keys = parsed_keys; - parsed.cap_config_keys = 2; + parsed.configs = parsed_keys; + parsed.cap_configs = 2; parsed.sources = parsed_sources; parsed.cap_sources = 2; parsed.globs = parsed_globs; @@ -289,7 +334,6 @@ static void test_trace(void) { strcpy(deep.target, "//app:bin"); fill_id(deep.recipe, 0); fill_id(deep.output, 1); - fill_id(deep.root_config, 2); fill_id(deep.argv, 3); fill_id(deep.deepset, 4); w = NULL; @@ -310,6 +354,14 @@ static void test_trace(void) { "build deep rejects test magic"); kit_writer_close(w); + memset(&parsed_ds, 0, sizeof parsed_ds); + parsed_ds.configs = ds_configs; + parsed_ds.cap_configs = 2; + EXPECT(build_deepset_parse((const uint8_t*)bad_deepset_config, + sizeof bad_deepset_config - 1u, &parsed_ds, err, + sizeof err) == BUILD_ERR, + "reject non-canonical deepset config"); + memset(&rec, 0, sizeof rec); strcpy(rec.target, "//app:bin"); rec.rows = rows; @@ -361,6 +413,30 @@ static void test_protocol(void) { "need roundtrip"); memset(&req, 0, sizeof req); + req.cmd = BUILD_CMD_CONFIG_GET; + req.arg = KIT_SLICE_LIT("mode"); + argv[0] = KIT_SLICE_LIT("fallback"); + req.argv = argv; + req.argc = 1; + EXPECT(build_proto_encode_req(&req, buf, sizeof buf, &n) == BUILD_OK && + build_proto_decode_req(buf, n, &dec_req, dec_overrides, 1, + dec_argv, 1) == BUILD_OK && + dec_req.cmd == BUILD_CMD_CONFIG_GET && + dec_req.argc == 1 && + kit_slice_eq(dec_req.argv[0], KIT_SLICE_LIT("fallback")), + "config default request roundtrip"); + memset(&resp, 0, sizeof resp); + resp.status = BUILD_RESP_DEFAULT; + resp.text = KIT_SLICE_LIT("fallback"); + EXPECT(build_proto_encode_resp(&req, &resp, buf, sizeof buf, &n) == + BUILD_OK && + build_proto_decode_resp(buf, n, BUILD_CMD_CONFIG_GET, &dec_resp, + NULL, NULL) == BUILD_OK && + dec_resp.status == BUILD_RESP_DEFAULT && + kit_slice_eq(dec_resp.text, KIT_SLICE_LIT("fallback")), + "config default response roundtrip"); + + memset(&req, 0, sizeof req); req.cmd = BUILD_CMD_GLOB; memset(&resp, 0, sizeof resp); resp.status = BUILD_RESP_OK; diff --git a/test/buildcoord/run.sh b/test/buildcoord/run.sh @@ -33,6 +33,8 @@ recipe recipes/absent.sh recipe recipes/app.sh [target //argv:echo] recipe recipes/argv.sh +[target //cfg:default] +recipe recipes/cfg_default.sh [target //cfg:probe] recipe recipes/cfg.sh [target //client:formats] @@ -83,6 +85,10 @@ recipe recipes/scope_parent_inherit.sh recipe recipes/scope_parent_override.sh [target //scope:plain] recipe recipes/scope_plain.sh +[target //shadow:leaf] +recipe recipes/shadow_leaf.sh +[target //shadow:parent] +recipe recipes/shadow_parent.sh [target //stable:dep] recipe recipes/stable_dep.sh [target //stable:parent] @@ -179,6 +185,14 @@ mode=$("$KIT" build config-get mode || true) printf 'mode:%s\n' "$mode" > "$KIT_BUILD_OUT/mode.txt" EOF +cat > "$ws/recipes/cfg_default.sh" <<'EOF' +#!/bin/sh +set -eu +mkdir -p "$KIT_BUILD_OUT" +value=$("$KIT" build config-get --default fallback missing-default) +printf 'default:%s\n' "$value" > "$KIT_BUILD_OUT/default.txt" +EOF + cat > "$ws/recipes/client_formats.sh" <<'EOF' #!/bin/sh set -eu @@ -412,6 +426,24 @@ mkdir -p "$KIT_BUILD_OUT" printf 'plain\n' > "$KIT_BUILD_OUT/plain.txt" EOF +cat > "$ws/recipes/shadow_leaf.sh" <<'EOF' +#!/bin/sh +set -eu +mkdir -p "$KIT_BUILD_OUT" +mode=$("$KIT" build config-get mode || true) +printf 'leaf:%s\n' "$mode" > "$KIT_BUILD_OUT/leaf.txt" +EOF + +cat > "$ws/recipes/shadow_parent.sh" <<'EOF' +#!/bin/sh +set -eu +mkdir -p "$KIT_BUILD_OUT" +mode=$("$KIT" build config-get mode || true) +dep_dir=$("$KIT" build need --config mode=child //shadow:leaf) +printf 'parent:%s\n' "$mode" > "$KIT_BUILD_OUT/parent.txt" +cat "$dep_dir/leaf.txt" > "$KIT_BUILD_OUT/leaf.txt" +EOF + cat > "$ws/recipes/stable_dep.sh" <<'EOF' #!/bin/sh set -eu @@ -831,8 +863,8 @@ contains "buildcoord-config-unread-cold-run" \ "$work/buildcoord-config-unread-cold.err" "recipes_run=1" build_assert_ok buildcoord-config-unread-change --stats --config flavor=two \ //scope:plain -contains "buildcoord-config-unread-change-shallow" \ - "$work/buildcoord-config-unread-change.err" "shallow_hits=1" +contains "buildcoord-config-unread-change-deep" \ + "$work/buildcoord-config-unread-change.err" "deep_hits=1" contains "buildcoord-config-unread-change-no-run" \ "$work/buildcoord-config-unread-change.err" "recipes_run=0" plain_change_path=$(tree_path_from "$work/buildcoord-config-unread-change.out") @@ -883,6 +915,45 @@ else "$work/config-override-tree.diag" fi +build_assert_ok buildcoord-config-shadow-parent-cold --stats --config mode=parent1 \ + //shadow:parent +contains "buildcoord-config-shadow-parent-cold-run" \ + "$work/buildcoord-config-shadow-parent-cold.err" "recipes_run=2" +shadow_parent1_path=$(tree_path_from \ + "$work/buildcoord-config-shadow-parent-cold.out") +contains "buildcoord-config-shadow-parent-cold-parent" \ + "$shadow_parent1_path/parent.txt" "parent:parent1" +contains "buildcoord-config-shadow-parent-cold-leaf" \ + "$shadow_parent1_path/leaf.txt" "leaf:child" +build_assert_ok buildcoord-config-shadow-parent-deep --stats --config mode=parent1 \ + //shadow:parent +contains "buildcoord-config-shadow-parent-deep-hit" \ + "$work/buildcoord-config-shadow-parent-deep.err" "deep_hits=1" +contains "buildcoord-config-shadow-parent-deep-no-run" \ + "$work/buildcoord-config-shadow-parent-deep.err" "recipes_run=0" +build_assert_ok buildcoord-config-shadow-leaf-direct-deep --stats \ + --config mode=child //shadow:leaf +contains "buildcoord-config-shadow-leaf-direct-deep-hit" \ + "$work/buildcoord-config-shadow-leaf-direct-deep.err" "deep_hits=1" +contains "buildcoord-config-shadow-leaf-direct-deep-no-run" \ + "$work/buildcoord-config-shadow-leaf-direct-deep.err" "recipes_run=0" +shadow_leaf_path=$(tree_path_from \ + "$work/buildcoord-config-shadow-leaf-direct-deep.out") +contains "buildcoord-config-shadow-leaf-direct-deep-output" \ + "$shadow_leaf_path/leaf.txt" "leaf:child" +build_assert_ok buildcoord-config-shadow-parent-change --stats --config mode=parent2 \ + //shadow:parent +contains "buildcoord-config-shadow-parent-change-rerun" \ + "$work/buildcoord-config-shadow-parent-change.err" "recipes_run=1" +contains "buildcoord-config-shadow-parent-change-child-hit" \ + "$work/buildcoord-config-shadow-parent-change.err" "deep_hits=1" +shadow_parent2_path=$(tree_path_from \ + "$work/buildcoord-config-shadow-parent-change.out") +contains "buildcoord-config-shadow-parent-change-parent" \ + "$shadow_parent2_path/parent.txt" "parent:parent2" +contains "buildcoord-config-shadow-parent-change-leaf" \ + "$shadow_parent2_path/leaf.txt" "leaf:child" + build_assert_ok buildcoord-stats-argv-cold --stats //argv:echo -- stat-one contains "buildcoord-stats-argv-cold-run" "$work/buildcoord-stats-argv-cold.err" \ "recipes_run=1" @@ -923,6 +994,36 @@ cfg_unset_again_path=$(tree_path_from \ contains "buildcoord-config-unset-restores-trace-output" \ "$cfg_unset_again_path/mode.txt" "mode:" +build_assert_ok buildcoord-config-default-cold --stats //cfg:default +contains "buildcoord-config-default-cold-run" \ + "$work/buildcoord-config-default-cold.err" "recipes_run=1" +cfg_default_cold_path=$(tree_path_from "$work/buildcoord-config-default-cold.out") +contains "buildcoord-config-default-cold-output" \ + "$cfg_default_cold_path/default.txt" "default:fallback" +build_assert_ok buildcoord-config-default-absent-hit --stats //cfg:default +contains "buildcoord-config-default-absent-hit-deep" \ + "$work/buildcoord-config-default-absent-hit.err" "deep_hits=1" +contains "buildcoord-config-default-absent-hit-no-run" \ + "$work/buildcoord-config-default-absent-hit.err" "recipes_run=0" +build_assert_ok buildcoord-config-default-present-fallback --stats \ + --config missing-default=fallback //cfg:default +contains "buildcoord-config-default-present-fallback-hit" \ + "$work/buildcoord-config-default-present-fallback.err" "deep_hits=1" +contains "buildcoord-config-default-present-fallback-no-run" \ + "$work/buildcoord-config-default-present-fallback.err" "recipes_run=0" +cfg_default_fallback_path=$(tree_path_from \ + "$work/buildcoord-config-default-present-fallback.out") +contains "buildcoord-config-default-present-fallback-output" \ + "$cfg_default_fallback_path/default.txt" "default:fallback" +build_assert_ok buildcoord-config-default-present-other --stats \ + --config missing-default=other //cfg:default +contains "buildcoord-config-default-present-other-run" \ + "$work/buildcoord-config-default-present-other.err" "recipes_run=1" +cfg_default_other_path=$(tree_path_from \ + "$work/buildcoord-config-default-present-other.out") +contains "buildcoord-config-default-present-other-output" \ + "$cfg_default_other_path/default.txt" "default:other" + build_assert_ok buildcoord-client-formats //client:formats client_formats_path=$(tree_path_from "$work/buildcoord-client-formats.out") contains "buildcoord-config-get-default" "$client_formats_path/default.txt" \