kit

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

commit 25c31e015969ea48307092158c5aaf6be03485d0
parent d02cb3a24fa7df0304e537501ec3bb3b90602d24
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 18 Jun 2026 09:32:50 -0700

Add build env passthrough shorthand

Diffstat:
Mdoc/BUILD_COORDINATOR.md | 17+++++++++++++----
Mdriver/cmd/build_coord.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mtest/buildcoord/run.sh | 14++++++++++++++
3 files changed, 109 insertions(+), 16 deletions(-)

diff --git a/doc/BUILD_COORDINATOR.md b/doc/BUILD_COORDINATOR.md @@ -103,6 +103,11 @@ Configuration has two scopes, distinguished by visibility: applied along the `need` path from the root. It is canonicalized to byte-stable text and content-addressed: `config-id = BLAKE2b(canonical map)`, stored as a CAS blob so any recorded `config-id` resolves back to the actual map. + Driver spelling is `--config K=V`. For recipe environment pass-through, + `--config env.NAME` copies the current process's `$NAME` value into config key + `env.NAME`; `--env NAME` and `--env NAME=VALUE` are shorthand for + `--config env.NAME` and `--config env.NAME=VALUE`. The pass-through forms + require `NAME` to be set in the current environment. - **Local configuration** is the target's **argv**, supplied entirely by the build request (both the top-level request and a `need` carry an optional argv); @@ -673,8 +678,8 @@ command. | `config-get <key>` | the propagated value (or *unset*) | config dep: key recorded as consumed; its value is the one in the effective `config-id` map (absent = unset) | | `source <path>` | `blob-id` (or *absent*) + a path to read | source dep: `(path, blob-id)`, or `(path, absent)` | | `glob <pattern>` | sorted list of matching paths (streamed) | glob dep: `(pattern, glob-result-hash)` | -| `need <target> [k=v…] [argv…]` | the dep's output `tree-id` + a path (blocks) | target dep edge: `(dep, dep-config-id, dep-argv-id, output-tree-id)` | -| `need-submit <target> [k=v…] [argv…]` | a **token** (does not block) | *(nothing yet — logged on await)* | +| `need <target> [k=v…] [env…] [argv…]` | the dep's output `tree-id` + a path (blocks) | target dep edge: `(dep, dep-config-id, dep-argv-id, output-tree-id)` | +| `need-submit <target> [k=v…] [env…] [argv…]` | a **token** (does not block) | *(nothing yet — logged on await)* | | `need-await <token>` | the submitted dep's output `tree-id` + a path | target dep edge: `(dep, dep-config-id, dep-argv-id, output-tree-id)` | - **`config-get`** reads the target's *effective propagated* config and records the @@ -687,6 +692,9 @@ command. `env.` prefix (e.g. `env.PATH`) become the recipe's environment variables, so a recipe's env is tracked, hermetic, and propagates like any config — the spawned process otherwise gets a *clean* environment (nothing ambient inherited). + `--env PATH` is the CLI shorthand for copying the driver's current `$PATH` into + config key `env.PATH`; `--env PATH=/bin` sets that key explicitly. If `$PATH` + is unset, `--env PATH` / `--config env.PATH` is rejected. - **`source`** hands back a path inside the **live workspace** (not a staged copy) plus the blob-id the read is pinned to; an absent path is reported and recorded as an *absent* leaf (creating it later busts the trace). Returning the @@ -697,8 +705,9 @@ command. **streamed** across as many frames as needed, so an arbitrarily large match set is not bounded by a single frame. - **`need`** is the dynamic-dependency primitive. The optional `k=v` pairs - **overlay** propagated config for that sub-build; an optional argv vector sets - the dep's **local argv** (omitted ⇒ the empty argv; local argv never + **overlay** propagated config for that sub-build; `--env NAME` / + `--env NAME=value` are accepted as env-prefixed config overlays; an optional + argv vector sets the dep's **local argv** (omitted ⇒ the empty argv; local argv never propagates). The coordinator resolves `(dep, cfg ⊕ overrides, argv)` recursively, returns its output `tree-id` and a readable path, and records the edge with the dep's `config-id` and `argv-id` so the shallow path can re-resolve diff --git a/driver/cmd/build_coord.c b/driver/cmd/build_coord.c @@ -15,6 +15,7 @@ #define BUILD_TOOL "build" #define BUILD_DEFAULT_DEF "BUILD.kit" #define BUILD_MAX_CONFIG 128u +#define BUILD_MAX_CONFIG_KEY 64u #define BUILD_MAX_ARGS 128u typedef struct BuildCli { @@ -23,6 +24,7 @@ typedef struct BuildCli { const char* def; const char* target; KitBuildKV config[BUILD_MAX_CONFIG]; + char config_keys[BUILD_MAX_CONFIG][BUILD_MAX_CONFIG_KEY]; size_t nconfig; KitSlice args[BUILD_MAX_ARGS]; size_t nargs; @@ -37,17 +39,21 @@ void driver_help_build(void) { "\n" "USAGE\n" " kit build [--store DIR] [--root DIR] [--def FILE]\n" - " [--config K=V]... [--verify] [--stats] TARGET [-- ARG...]\n" + " [--config K=V|env.NAME]... [--env NAME[=V]]...\n" + " [--verify] [--stats] TARGET [-- ARG...]\n" " kit build test [--store DIR] [--root DIR] [--def FILE]\n" - " [--config K=V]... [--verify] [--stats] TARGET [-- ARG...]\n" + " [--config K=V|env.NAME]... [--env NAME[=V]]...\n" + " [--verify] [--stats] TARGET [-- ARG...]\n" " kit build config-get KEY # inside a build recipe\n" " kit build source PATH # inside a build recipe\n" " kit build fetch BLOB URL... # inside a build recipe\n" " kit build depfile [--lines] FILE # inside a build recipe\n" " kit build glob PATTERN # inside a build recipe\n" - " kit build need [--config K=V]... TARGET [-- ARG...]\n" + " kit build need [--config K=V|env.NAME]... [--env NAME[=V]]...\n" + " TARGET [-- ARG...]\n" " # inside a build recipe\n" - " kit build need-submit [--config K=V]... TARGET [-- ARG...]\n" + " kit build need-submit [--config K=V|env.NAME]... [--env NAME[=V]]...\n" + " TARGET [-- ARG...]\n" " # inside a build recipe\n" " kit build need-await TOKEN # inside a build recipe\n" "\n" @@ -56,6 +62,8 @@ void driver_help_build(void) { " --root DIR Workspace root (default: .)\n" " --def FILE Build definition path (default: BUILD.kit)\n" " --config K=V Seed propagated configuration\n" + " --config env.N Seed env.N from current $N\n" + " --env N[=V] Shorthand for --config env.N[=V]\n" " --verify Verify the returned output tree in the CAS\n" " --stats Print build-resolution counters to stderr\n"); } @@ -91,16 +99,68 @@ static int build_join_path(char* out, size_t cap, const char* a, return 0; } +static int build_add_config(BuildCli* cli, KitSlice key, KitSlice value) { + if (!cli || !key.s || key.len == 0u || cli->nconfig >= BUILD_MAX_CONFIG) + return 1; + cli->config[cli->nconfig].key = key; + cli->config[cli->nconfig].value = value; + ++cli->nconfig; + return 0; +} + +static int build_env_name_valid(const char* name, size_t len) { + size_t i; + if (!name || len == 0u) return 0; + for (i = 0; i < len; ++i) { + unsigned char c = (unsigned char)name[i]; + if (c == '\0' || c == '=' || c == '\n' || c == '\r') return 0; + } + return 1; +} + +static int build_add_env_config(BuildCli* cli, const char* name, size_t name_len, + const char* value) { + char* key; + if (!cli || !build_env_name_valid(name, name_len) || !value || + cli->nconfig >= BUILD_MAX_CONFIG || + sizeof(KIT_BUILD_ENV_PREFIX) - 1u + name_len >= BUILD_MAX_CONFIG_KEY) + return 1; + key = cli->config_keys[cli->nconfig]; + memcpy(key, KIT_BUILD_ENV_PREFIX, sizeof(KIT_BUILD_ENV_PREFIX) - 1u); + memcpy(key + sizeof(KIT_BUILD_ENV_PREFIX) - 1u, name, name_len); + key[sizeof(KIT_BUILD_ENV_PREFIX) - 1u + name_len] = '\0'; + return build_add_config(cli, kit_slice_cstr(key), kit_slice_cstr(value)); +} + static int build_parse_config(BuildCli* cli, const char* text) { const char* eq; + size_t env_prefix_len = sizeof(KIT_BUILD_ENV_PREFIX) - 1u; if (!cli || !text) return 1; eq = driver_strchr(text, '='); - if (!eq || eq == text || cli->nconfig >= BUILD_MAX_CONFIG) return 1; - cli->config[cli->nconfig].key.s = text; - cli->config[cli->nconfig].key.len = (size_t)(eq - text); - cli->config[cli->nconfig].value = kit_slice_cstr(eq + 1); - ++cli->nconfig; - return 0; + if (!eq) { + const char* value; + if (!driver_strneq(text, KIT_BUILD_ENV_PREFIX, env_prefix_len) || + text[env_prefix_len] == '\0') + return 1; + value = driver_getenv(text + env_prefix_len); + if (!value) return 1; + return build_add_config(cli, kit_slice_cstr(text), kit_slice_cstr(value)); + } + if (eq == text) return 1; + return build_add_config(cli, (KitSlice){.s = text, .len = (size_t)(eq - text)}, + kit_slice_cstr(eq + 1)); +} + +static int build_parse_env(BuildCli* cli, const char* text) { + const char* eq; + if (!cli || !text) return 1; + eq = driver_strchr(text, '='); + if (!eq) { + const char* value = driver_getenv(text); + if (!value) return 1; + return build_add_env_config(cli, text, driver_strlen(text), value); + } + return build_add_env_config(cli, text, (size_t)(eq - text), eq + 1); } static int build_client_glob_print(void* user, KitSlice path) { @@ -139,7 +199,12 @@ static int build_client_need_parse(BuildCli* cli, int argc, char** argv, const char* a = argv[i]; if (driver_streq(a, "--config") && i + 1 < argc) { if (build_parse_config(cli, argv[++i]) != 0) { - driver_errf(BUILD_TOOL, "bad --config, expected K=V"); + driver_errf(BUILD_TOOL, "bad --config, expected K=V or env.NAME"); + return 2; + } + } else if (driver_streq(a, "--env") && i + 1 < argc) { + if (build_parse_env(cli, argv[++i]) != 0) { + driver_errf(BUILD_TOOL, "bad --env, expected NAME or NAME=VALUE"); return 2; } } else if (driver_streq(a, "--")) { @@ -395,7 +460,12 @@ static int build_parse_args(BuildCli* cli, int argc, char** argv, int first_arg, cli->def = argv[++i]; } else if (driver_streq(a, "--config") && i + 1 < argc) { if (build_parse_config(cli, argv[++i]) != 0) { - driver_errf(BUILD_TOOL, "bad --config, expected K=V"); + driver_errf(BUILD_TOOL, "bad --config, expected K=V or env.NAME"); + return 2; + } + } else if (driver_streq(a, "--env") && i + 1 < argc) { + if (build_parse_env(cli, argv[++i]) != 0) { + driver_errf(BUILD_TOOL, "bad --env, expected NAME or NAME=VALUE"); return 2; } } else if (driver_streq(a, "--")) { diff --git a/test/buildcoord/run.sh b/test/buildcoord/run.sh @@ -1123,6 +1123,20 @@ env_two_path=$(tree_path_from "$work/buildcoord-spec-env-change-invalidates.out" contains "buildcoord-spec-env-change-output" "$env_two_path/env.txt" \ "declared:two" +build_assert_ok buildcoord-env-shorthand-set --stats \ + --env DECLARED=three //env:probe +env_three_path=$(tree_path_from "$work/buildcoord-env-shorthand-set.out") +contains "buildcoord-env-shorthand-set-output" "$env_three_path/env.txt" \ + "declared:three" + +export DECLARED=from-current-env +build_assert_ok buildcoord-env-config-passthrough --stats \ + --config env.DECLARED //env:probe +unset DECLARED +env_current_path=$(tree_path_from "$work/buildcoord-env-config-passthrough.out") +contains "buildcoord-env-config-passthrough-output" \ + "$env_current_path/env.txt" "declared:from-current-env" + build_assert_ok buildcoord-spec-nondet-cold //nondet:counter run_fail "buildcoord-spec-verify-reruns-cache-hit" \ "$KIT" build --store "$store" --root "$ws" --def BUILD.kit \