commit c04f2488cc1875033b4abeab995d2bdb10b40cc0
parent 82ef995c9c7a61e8fed52f30e3eb019bd08bf95d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 18 Jun 2026 02:52:18 -0700
build: replay need overlays during shallow hits
Diffstat:
11 files changed, 190 insertions(+), 32 deletions(-)
diff --git a/src/build/bundle.c b/src/build/bundle.c
@@ -655,7 +655,9 @@ static int BUILD_MAYBE_UNUSED export_trace_refs(KitBuildCoordinator* c,
payload_write_blob_from_cas(c, t.argv, root, seen) != BUILD_OK)
goto shallow_out;
for (i = 0u; i < t.n_deps; ++i) {
- if (payload_write_blob_from_cas(c, t.deps[i].config_id, root, seen) !=
+ if (payload_write_blob_from_cas(c, t.deps[i].overlay_id, root, seen) !=
+ BUILD_OK ||
+ payload_write_blob_from_cas(c, t.deps[i].config_id, root, seen) !=
BUILD_OK ||
payload_write_blob_from_cas(c, t.deps[i].argv_id, root, seen) !=
BUILD_OK)
@@ -726,6 +728,10 @@ static int BUILD_MAYBE_UNUSED import_trace_refs(KitBuildCoordinator* c,
goto shallow_out;
release_file(c, &fd);
for (i = 0u; i < t.n_deps; ++i) {
+ if (payload_install_blob(c, t.deps[i].overlay_id, root, seen, NULL, NULL,
+ &fd) != BUILD_OK)
+ goto shallow_out;
+ release_file(c, &fd);
if (payload_install_blob(c, t.deps[i].config_id, root, seen, NULL, NULL,
&fd) != BUILD_OK)
goto shallow_out;
diff --git a/src/build/bundle.h b/src/build/bundle.h
@@ -18,10 +18,10 @@
* <kit/package.h> manifest + minisign + trust machinery wholesale: a signed
* `kit-build-traces 1` manifest lists (target, kind, trace-id, output-tree-id)
* claims, carried in a `.kpkg` alongside the trace bodies and the CAS blobs they
- * reference — the serialized config-map / argv blobs (required, else an imported
- * shallow trace cannot replay its `need`s) AND the deepset closure blobs a deep
- * trace points at (required, else an imported deep trace cannot refresh) — plus
- * optionally the referenced output trees/blobs.
+ * reference — the serialized config-map / argv blobs and need-overlay blobs
+ * (required, else an imported shallow trace cannot replay its `need`s) AND the
+ * deepset closure blobs a deep trace points at (required, else an imported deep
+ * trace cannot refresh) — plus optionally the referenced output trees/blobs.
*
* The security split is precise: a local build can deep/shallow-HIT on a remote
* builder's trace (obtaining the output without running the recipe) while
diff --git a/src/build/cfg.c b/src/build/cfg.c
@@ -170,6 +170,20 @@ int build_config_overlay(const BuildConfig* base, const KitBuildKV* overrides,
return BUILD_OK;
}
+int build_config_overlay_map(const BuildConfig* base,
+ const BuildConfig* overrides, BuildConfig* out) {
+ size_t i;
+ if (!overrides || (overrides->n && !overrides->entries)) return BUILD_ERR;
+ if (build_config_overlay(base, NULL, 0, out) != BUILD_OK) return BUILD_ERR;
+ for (i = 0; i < overrides->n; ++i) {
+ if (build_config_set(out, kit_slice_cstr(overrides->entries[i].key),
+ kit_slice_cstr(overrides->entries[i].value)) !=
+ BUILD_OK)
+ return BUILD_ERR;
+ }
+ return BUILD_OK;
+}
+
static int config_validate(const BuildConfig* cfg) {
size_t i;
if (!cfg || (cfg->n && !cfg->entries)) return BUILD_ERR;
diff --git a/src/build/cfg.h b/src/build/cfg.h
@@ -73,6 +73,11 @@ int build_config_get(const BuildConfig*, KitSlice key, KitSlice* value,
int build_config_overlay(const BuildConfig* base, const KitBuildKV* overrides,
size_t noverrides, BuildConfig* out);
+/* Produce `out` = `base` with every entry in `overrides` applied. This is the
+ * replay form for a recorded need overlay recovered from its config-map blob. */
+int build_config_overlay_map(const BuildConfig* base,
+ const BuildConfig* overrides, BuildConfig* out);
+
/* Emit the canonical, byte-stable serialized form (BUILD_CONFIG_MAGIC version
* line, then sorted `key value` rows). Sorting/dedup happens here. */
int build_config_emit(const BuildConfig*, KitWriter* out);
diff --git a/src/build/resolve.c b/src/build/resolve.c
@@ -105,16 +105,20 @@ static int try_shallow_trace(KitBuildCoordinator* c, KitSlice target,
return BUILD_ERR;
for (i = 0; i < st->n_deps; ++i) {
BuildConfigEntry dep_cfg_entries[128];
+ BuildConfigEntry overlay_entries[64];
char dep_argv_entries[64][BUILD_VAL_MAX];
- BuildConfig dep_cfg;
+ BuildConfig dep_cfg, overlay_cfg;
BuildArgv dep_argv;
BuildResolved dep_r;
build_config_init(&dep_cfg, dep_cfg_entries,
sizeof dep_cfg_entries / sizeof dep_cfg_entries[0]);
+ build_config_init(&overlay_cfg, overlay_entries,
+ sizeof overlay_entries / sizeof overlay_entries[0]);
build_argv_init(&dep_argv, dep_argv_entries,
sizeof dep_argv_entries / sizeof dep_argv_entries[0]);
- if (build_coord_config_by_id(c, st->deps[i].config_id, &dep_cfg) !=
+ if (build_coord_config_by_id(c, st->deps[i].overlay_id, &overlay_cfg) !=
BUILD_OK ||
+ build_config_overlay_map(cfg, &overlay_cfg, &dep_cfg) != BUILD_OK ||
build_coord_argv_by_id(c, st->deps[i].argv_id, &dep_argv) !=
BUILD_OK ||
build_resolve(c, kit_slice_cstr(st->deps[i].name), &dep_cfg, &dep_argv,
diff --git a/src/build/runner.c b/src/build/runner.c
@@ -71,12 +71,13 @@ static int append_glob(BuildDepLog* log, KitSlice pattern,
}
static int append_dep(BuildDepLog* log, KitSlice target,
+ const uint8_t overlay_id[BUILD_HASH_LEN],
const uint8_t config_id[BUILD_HASH_LEN],
const uint8_t argv_id[BUILD_HASH_LEN],
const BuildResolved* r) {
BuildDepEdge* row;
if (!log || !target.s || target.len == 0u || target.len >= BUILD_TARGET_MAX ||
- !config_id || !argv_id || !r || !r->leafset)
+ !overlay_id || !config_id || !argv_id || !r || !r->leafset)
return BUILD_ERR;
if (log->n_deps >= log->cap_deps ||
log->n_children >= log->cap_children)
@@ -84,6 +85,7 @@ static int append_dep(BuildDepLog* log, KitSlice target,
row = &log->deps[log->n_deps++];
memcpy(row->name, target.s, target.len);
row->name[target.len] = '\0';
+ memcpy(row->overlay_id, overlay_id, BUILD_HASH_LEN);
memcpy(row->config_id, config_id, BUILD_HASH_LEN);
memcpy(row->argv_id, argv_id, BUILD_HASH_LEN);
memcpy(row->output_tree, r->output_tree, BUILD_HASH_LEN);
@@ -412,24 +414,32 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
}
} else if (req.cmd == BUILD_CMD_NEED) {
- BuildConfigEntry cfg_entries[128];
+ BuildConfigEntry cfg_entries[128], overlay_entries[64];
char argv_entries[64][BUILD_VAL_MAX];
- BuildConfig dep_cfg;
+ BuildConfig dep_cfg, overlay_cfg, empty_cfg;
BuildArgv dep_argv;
BuildResolved r;
- uint8_t cfg_id[BUILD_HASH_LEN], argv_id[BUILD_HASH_LEN];
+ uint8_t overlay_id[BUILD_HASH_LEN], cfg_id[BUILD_HASH_LEN],
+ argv_id[BUILD_HASH_LEN];
build_config_init(&dep_cfg, cfg_entries,
sizeof cfg_entries / sizeof cfg_entries[0]);
+ build_config_init(&overlay_cfg, overlay_entries,
+ sizeof overlay_entries / sizeof overlay_entries[0]);
+ build_config_init(&empty_cfg, NULL, 0);
build_argv_init(&dep_argv, argv_entries,
sizeof argv_entries / sizeof argv_entries[0]);
if (build_config_overlay(cfg, req.overrides, req.noverrides, &dep_cfg) !=
BUILD_OK ||
+ build_config_overlay(&empty_cfg, req.overrides, req.noverrides,
+ &overlay_cfg) != BUILD_OK ||
+ store_config_blob(c, &overlay_cfg, overlay_id) != BUILD_OK ||
build_argv_set(&dep_argv, req.argv, req.argc) != BUILD_OK ||
build_config_id(c->ctx->heap, &dep_cfg, cfg_id) != BUILD_OK ||
build_argv_id(c->ctx->heap, &dep_argv, argv_id) != BUILD_OK ||
build_resolve(c, req.arg, &dep_cfg, &dep_argv, chain, &r) !=
BUILD_OK ||
- append_dep(log, req.arg, cfg_id, argv_id, &r) != BUILD_OK) {
+ append_dep(log, req.arg, overlay_id, cfg_id, argv_id, &r) !=
+ BUILD_OK) {
resp_error(&resp, KIT_ERR, "need failed");
} else {
memcpy(resp.id, r.output_tree, BUILD_HASH_LEN);
@@ -437,21 +447,28 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
}
if (write_resp(c, conn, &req, &resp) != BUILD_OK) return BUILD_ERR;
} else if (req.cmd == BUILD_CMD_NEED_SUBMIT) {
- BuildConfigEntry cfg_entries[128];
+ BuildConfigEntry cfg_entries[128], overlay_entries[64];
char argv_entries[64][BUILD_VAL_MAX];
- BuildConfig dep_cfg;
+ BuildConfig dep_cfg, overlay_cfg, empty_cfg;
BuildArgv dep_argv;
BuildTargetFuture* f = NULL;
BuildPendingNeed* p;
- uint8_t cfg_id[BUILD_HASH_LEN], argv_id[BUILD_HASH_LEN];
+ uint8_t overlay_id[BUILD_HASH_LEN], cfg_id[BUILD_HASH_LEN],
+ argv_id[BUILD_HASH_LEN];
char err[128];
build_config_init(&dep_cfg, cfg_entries,
sizeof cfg_entries / sizeof cfg_entries[0]);
+ build_config_init(&overlay_cfg, overlay_entries,
+ sizeof overlay_entries / sizeof overlay_entries[0]);
+ build_config_init(&empty_cfg, NULL, 0);
build_argv_init(&dep_argv, argv_entries,
sizeof argv_entries / sizeof argv_entries[0]);
if (log->n_pending >= log->cap_pending ||
build_config_overlay(cfg, req.overrides, req.noverrides, &dep_cfg) !=
BUILD_OK ||
+ build_config_overlay(&empty_cfg, req.overrides, req.noverrides,
+ &overlay_cfg) != BUILD_OK ||
+ store_config_blob(c, &overlay_cfg, overlay_id) != BUILD_OK ||
build_argv_set(&dep_argv, req.argv, req.argc) != BUILD_OK ||
build_config_id(c->ctx->heap, &dep_cfg, cfg_id) != BUILD_OK ||
build_argv_id(c->ctx->heap, &dep_argv, argv_id) != BUILD_OK ||
@@ -463,6 +480,7 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
memset(p, 0, sizeof *p);
p->token = log->next_token++;
target_copy(req.arg, p->dep);
+ memcpy(p->overlay_id, overlay_id, BUILD_HASH_LEN);
memcpy(p->config_id, cfg_id, BUILD_HASH_LEN);
memcpy(p->argv_id, argv_id, BUILD_HASH_LEN);
p->future = f;
@@ -473,8 +491,8 @@ int build_runner_service(KitBuildCoordinator* c, KitBuildConn* conn,
BuildPendingNeed* p = find_pending(log, req.token);
BuildResolved r;
if (!p || build_coord_target_await(c, p->future, &r) != BUILD_OK ||
- append_dep(log, kit_slice_cstr(p->dep), p->config_id, p->argv_id,
- &r) != BUILD_OK) {
+ append_dep(log, kit_slice_cstr(p->dep), p->overlay_id, p->config_id,
+ p->argv_id, &r) != BUILD_OK) {
resp_error(&resp, KIT_ERR, "need-await failed");
} else {
memcpy(resp.id, r.output_tree, BUILD_HASH_LEN);
diff --git a/src/build/runner.h b/src/build/runner.h
@@ -31,6 +31,7 @@
typedef struct BuildPendingNeed {
uint64_t token;
char dep[BUILD_TARGET_MAX];
+ uint8_t overlay_id[BUILD_HASH_LEN];
uint8_t config_id[BUILD_HASH_LEN];
uint8_t argv_id[BUILD_HASH_LEN];
BuildTargetFuture* future; /* from build_coord_dispatch */
diff --git a/src/build/trace.c b/src/build/trace.c
@@ -112,6 +112,8 @@ static int glob_cmp(const BuildGlobLeaf* a, const BuildGlobLeaf* b) {
static int dep_cmp(const BuildDepEdge* a, const BuildDepEdge* b) {
int c = strcmp(a->name, b->name);
if (c != 0) return c;
+ c = id_cmp(a->overlay_id, b->overlay_id);
+ if (c != 0) return c;
c = id_cmp(a->config_id, b->config_id);
if (c != 0) return c;
return id_cmp(a->argv_id, b->argv_id);
@@ -224,12 +226,16 @@ static int emit_sorted_globs(const BuildGlobLeaf* rows, size_t n, KitWriter* out
}
static int emit_dep_row(KitWriter* out, const BuildDepEdge* row) {
- char cfg[BUILD_HEX_LEN], argv[BUILD_HEX_LEN], tree[BUILD_HEX_LEN];
+ char ovr[BUILD_HEX_LEN], cfg[BUILD_HEX_LEN], argv[BUILD_HEX_LEN],
+ tree[BUILD_HEX_LEN];
+ hex_encode(ovr, row->overlay_id);
hex_encode(cfg, row->config_id);
hex_encode(argv, row->argv_id);
hex_encode(tree, row->output_tree);
if (emit_bytes(out, row->name) != BUILD_OK) return BUILD_ERR;
if (emit_bytes(out, " ") != BUILD_OK) return BUILD_ERR;
+ if (emit_bytes(out, ovr) != BUILD_OK) return BUILD_ERR;
+ if (emit_bytes(out, " ") != BUILD_OK) return BUILD_ERR;
if (emit_bytes(out, cfg) != BUILD_OK) return BUILD_ERR;
if (emit_bytes(out, " ") != BUILD_OK) return BUILD_ERR;
if (emit_bytes(out, argv) != BUILD_OK) return BUILD_ERR;
@@ -423,7 +429,7 @@ typedef struct TraceParser {
size_t len;
size_t pos;
int first;
- char line[BUILD_PATH_MAX + 4u * BUILD_HEX_LEN + BUILD_TARGET_MAX + 16u];
+ char line[BUILD_PATH_MAX + 5u * BUILD_HEX_LEN + BUILD_TARGET_MAX + 16u];
} TraceParser;
static int parser_next(TraceParser* p, char** line, char* err, size_t errcap) {
@@ -602,15 +608,16 @@ static int append_deepset_glob(BuildDeepSet* out, char** fields, size_t n,
static int append_dep(BuildShallowTrace* out, char** fields, size_t n, char* err,
size_t errcap) {
BuildDepEdge* row;
- if (expect_field_count(n, 4u, err, errcap) != BUILD_OK) return BUILD_ERR;
+ if (expect_field_count(n, 5u, err, errcap) != BUILD_OK) return BUILD_ERR;
if (out->n_deps >= out->cap_deps)
return set_err(err, errcap, "too many dep rows");
if (!valid_target(fields[0])) return set_err(err, errcap, "bad dep target");
row = &out->deps[out->n_deps];
snprintf(row->name, sizeof row->name, "%s", fields[0]);
- if (hex_decode_strict(fields[1], row->config_id) != BUILD_OK ||
- hex_decode_strict(fields[2], row->argv_id) != BUILD_OK ||
- hex_decode_strict(fields[3], row->output_tree) != BUILD_OK)
+ if (hex_decode_strict(fields[1], row->overlay_id) != BUILD_OK ||
+ hex_decode_strict(fields[2], row->config_id) != BUILD_OK ||
+ hex_decode_strict(fields[3], row->argv_id) != BUILD_OK ||
+ hex_decode_strict(fields[4], row->output_tree) != BUILD_OK)
return set_err(err, errcap, "bad dep hash");
if (out->n_deps && dep_cmp(&out->deps[out->n_deps - 1u], row) >= 0)
return set_err(err, errcap, "non-canonical dep rows");
@@ -687,7 +694,7 @@ int build_shallow_parse(const uint8_t* data, size_t len, BuildShallowTrace* out,
p.first = 1;
for (;;) {
char* line;
- char* fields[4];
+ char* fields[5];
size_t n = 0;
int r = parser_next(&p, &line, err, errcap);
if (r < 0) return BUILD_ERR;
diff --git a/src/build/trace.h b/src/build/trace.h
@@ -42,12 +42,15 @@ typedef struct BuildGlobLeaf {
uint8_t result_hash[BUILD_HASH_LEN];
} BuildGlobLeaf;
-/* One target-dep edge (shallow only): replays one `need`. Carries the dep's
- * config-id AND argv-id (so it re-resolves under the exact (config, argv) it
- * used — the full resolution identity) and the output tree-id it produced.
- * Sorted by (name, config-id, argv-id). */
+/* One target-dep edge (shallow only): replays one `need`. Carries the explicit
+ * need overlay-id, the dep's effective config-id and argv-id, and the output
+ * tree-id it produced. Shallow replay recomputes the dep config as the current
+ * parent config overlaid by overlay-id; config-id is retained for sharing,
+ * exact trace description, and bundle closure. Sorted by
+ * (name, overlay-id, config-id, argv-id). */
typedef struct BuildDepEdge {
char name[BUILD_TARGET_MAX];
+ uint8_t overlay_id[BUILD_HASH_LEN];
uint8_t config_id[BUILD_HASH_LEN];
uint8_t argv_id[BUILD_HASH_LEN];
uint8_t output_tree[BUILD_HASH_LEN];
diff --git a/test/build/build_pure_test.c b/test/build/build_pure_test.c
@@ -210,9 +210,10 @@ static void test_trace(void) {
st.n_deps = 1;
st.cap_deps = 1;
strcpy(deps[0].name, "//lib:core");
- fill_id(deps[0].config_id, 6);
- fill_id(deps[0].argv_id, 7);
- fill_id(deps[0].output_tree, 8);
+ fill_id(deps[0].overlay_id, 6);
+ fill_id(deps[0].config_id, 7);
+ fill_id(deps[0].argv_id, 8);
+ fill_id(deps[0].output_tree, 9);
EXPECT(kit_writer_mem(&g_u.heap, &w) == KIT_OK && w, "trace writer");
EXPECT(build_shallow_emit(&st, w, err, sizeof err) == BUILD_OK,
@@ -234,7 +235,9 @@ static void test_trace(void) {
"parse shallow");
EXPECT(strcmp(parsed.config_keys[0].name, "a") == 0 &&
strcmp(parsed.sources[0].path, "src/a.c") == 0 &&
- parsed.sources[0].absent,
+ parsed.sources[0].absent &&
+ memcmp(parsed.deps[0].overlay_id, deps[0].overlay_id,
+ BUILD_HASH_LEN) == 0,
"shallow canonical sections");
EXPECT(build_shallow_parse((const uint8_t*)dup_source, sizeof dup_source - 1u,
&parsed, err, sizeof err) == BUILD_ERR,
diff --git a/test/buildcoord/run.sh b/test/buildcoord/run.sh
@@ -40,6 +40,14 @@ recipe recipes/lib.sh
recipe recipes/needargv.sh
[target //recipe:stamp]
recipe recipes/recipe.sh
+[target //scope:leaf]
+recipe recipes/scope_leaf.sh
+[target //scope:parent-inherit]
+recipe recipes/scope_parent_inherit.sh
+[target //scope:parent-override]
+recipe recipes/scope_parent_override.sh
+[target //scope:plain]
+recipe recipes/scope_plain.sh
[target //stable:dep]
recipe recipes/stable_dep.sh
[target //stable:parent]
@@ -118,6 +126,37 @@ mkdir -p "$KIT_BUILD_OUT"
printf 'recipe:v1\n' > "$KIT_BUILD_OUT/stamp.txt"
EOF
+cat > "$ws/recipes/scope_leaf.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+mode=$("$KIT" build config-get mode || true)
+printf 'mode:%s\n' "$mode" > "$KIT_BUILD_OUT/mode.txt"
+EOF
+
+cat > "$ws/recipes/scope_parent_inherit.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+dep_dir=$("$KIT" build need //scope:leaf)
+cat "$dep_dir/mode.txt" > "$KIT_BUILD_OUT/mode.txt"
+EOF
+
+cat > "$ws/recipes/scope_parent_override.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+dep_dir=$("$KIT" build need --config mode=forced //scope:leaf)
+cat "$dep_dir/mode.txt" > "$KIT_BUILD_OUT/mode.txt"
+EOF
+
+cat > "$ws/recipes/scope_plain.sh" <<'EOF'
+#!/bin/sh
+set -eu
+mkdir -p "$KIT_BUILD_OUT"
+printf 'plain\n' > "$KIT_BUILD_OUT/plain.txt"
+EOF
+
cat > "$ws/recipes/stable_dep.sh" <<'EOF'
#!/bin/sh
set -eu
@@ -324,6 +363,64 @@ cfg_change_path=$(tree_path_from "$work/buildcoord-stats-cfg-change.out")
contains "buildcoord-stats-cfg-change-output" "$cfg_change_path/mode.txt" \
"mode:release"
+build_assert_ok buildcoord-config-unread-cold --stats --config flavor=one \
+ //scope:plain
+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-no-run" \
+ "$work/buildcoord-config-unread-change.err" "recipes_run=0"
+plain_change_path=$(tree_path_from "$work/buildcoord-config-unread-change.out")
+contains "buildcoord-config-unread-change-output" "$plain_change_path/plain.txt" \
+ "plain"
+
+build_assert_ok buildcoord-config-inherit-cold --stats --config mode=debug \
+ //scope:parent-inherit
+inherit_debug_path=$(tree_path_from "$work/buildcoord-config-inherit-cold.out")
+contains "buildcoord-config-inherit-debug-output" "$inherit_debug_path/mode.txt" \
+ "mode:debug"
+build_assert_ok buildcoord-config-inherit-change --stats --config mode=release \
+ //scope:parent-inherit
+contains "buildcoord-config-inherit-change-runs" \
+ "$work/buildcoord-config-inherit-change.err" "recipes_run=2"
+inherit_release_path=$(tree_path_from "$work/buildcoord-config-inherit-change.out")
+contains "buildcoord-config-inherit-release-output" \
+ "$inherit_release_path/mode.txt" "mode:release"
+if [ "$inherit_release_path" != "$inherit_debug_path" ]; then
+ ok "buildcoord-config-inherit-new-tree"
+else
+ printf 'unchanged tree path=%s\n' "$inherit_release_path" \
+ > "$work/config-inherit-tree.diag"
+ not_ok "buildcoord-config-inherit-new-tree" \
+ "$work/config-inherit-tree.diag"
+fi
+
+build_assert_ok buildcoord-config-override-cold --stats --config mode=debug \
+ //scope:parent-override
+override_debug_path=$(tree_path_from "$work/buildcoord-config-override-cold.out")
+contains "buildcoord-config-override-debug-output" \
+ "$override_debug_path/mode.txt" "mode:forced"
+build_assert_ok buildcoord-config-override-change --stats --config mode=release \
+ //scope:parent-override
+contains "buildcoord-config-override-change-shallow" \
+ "$work/buildcoord-config-override-change.err" "shallow_hits=1"
+contains "buildcoord-config-override-change-no-run" \
+ "$work/buildcoord-config-override-change.err" "recipes_run=0"
+override_release_path=$(tree_path_from "$work/buildcoord-config-override-change.out")
+contains "buildcoord-config-override-release-output" \
+ "$override_release_path/mode.txt" "mode:forced"
+if [ "$override_release_path" = "$override_debug_path" ]; then
+ ok "buildcoord-config-override-reuses-tree"
+else
+ printf 'debug=%s\nrelease=%s\n' "$override_debug_path" \
+ "$override_release_path" > "$work/config-override-tree.diag"
+ not_ok "buildcoord-config-override-reuses-tree" \
+ "$work/config-override-tree.diag"
+fi
+
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"