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:
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" \