kit

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

commit 5706656aa786c39f181907b28e7a9a8d16cff7e0
parent e54596bdb09b87c46e0f184abaccc0a42e803bc8
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 18 Jun 2026 08:01:38 -0700

Add buildcoord depfile source reporting

Diffstat:
Mdriver/cmd/build_coord.c | 29++++++++++++++++++++++++++++-
Minclude/kit/build_coord.h | 15+++++++++++++++
Msrc/api/build_coord.c | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/buildcoord/run.sh | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 251 insertions(+), 2 deletions(-)

diff --git a/driver/cmd/build_coord.c b/driver/cmd/build_coord.c @@ -39,6 +39,7 @@ void driver_help_build(void) { " [--config K=V]... [--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 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" " # inside a build recipe\n" @@ -106,7 +107,8 @@ static int build_client_glob_print(void* user, KitSlice path) { static int build_client_verb(const char* s) { return driver_streq(s, "config-get") || driver_streq(s, "source") || - driver_streq(s, "glob") || driver_streq(s, "need") || + driver_streq(s, "depfile") || driver_streq(s, "glob") || + driver_streq(s, "need") || driver_streq(s, "need-submit") || driver_streq(s, "need-await"); } @@ -233,6 +235,31 @@ static int build_client_mode(int argc, char** argv, int verb_index) { } driver_printf("%.*s\n", KIT_SLICE_ARG(path)); rc = 0; + } else if (driver_streq(verb, "depfile")) { + DriverLoad load; + KitSlice depfile; + uint32_t depfile_flags = KIT_BUILD_DEPFILE_DEFAULT; + int file_arg = first_arg; + if (argc == first_arg + 2 && driver_streq(argv[first_arg], "--lines")) { + depfile_flags = KIT_BUILD_DEPFILE_LINES; + file_arg = first_arg + 1; + } else if (argc != first_arg + 1) { + driver_errf(BUILD_TOOL, "usage: kit build depfile [--lines] FILE"); + rc = 2; + goto out_client; + } + if (driver_load_bytes(&env.file_io, BUILD_TOOL, argv[file_arg], &load, + &depfile) != 0) { + rc = 1; + goto out_client; + } + st = kit_build_client_depfile_ex(client, depfile, depfile_flags); + driver_release_bytes(&env.file_io, &load); + if (st != KIT_OK) { + driver_errf(BUILD_TOOL, "depfile failed: %s", build_status_name(st)); + goto out_client; + } + rc = 0; } else if (driver_streq(verb, "glob")) { if (argc != first_arg + 1) { driver_errf(BUILD_TOOL, "usage: kit build glob PATTERN"); diff --git a/include/kit/build_coord.h b/include/kit/build_coord.h @@ -388,6 +388,21 @@ KIT_API KitStatus kit_build_client_source(KitBuildClient*, KitSlice path, uint8_t blob[KIT_BUILD_HASH_LEN], KitSlice* realpath); +typedef enum KitBuildDepfileFlags { + KIT_BUILD_DEPFILE_DEFAULT = 0, + KIT_BUILD_DEPFILE_LINES = 1u << 0, /* one source path per non-empty line */ +} KitBuildDepfileFlags; + +/* Parse depfile contents and log each prerequisite as a source dep. The default + * parser accepts Make/GCC-style depfiles: the target side of each rule is + * ignored; every dependency after an unescaped ':' is submitted through + * kit_build_client_source. */ +KIT_API KitStatus kit_build_client_depfile(KitBuildClient*, + KitSlice depfile_text); +KIT_API KitStatus kit_build_client_depfile_ex(KitBuildClient*, + KitSlice depfile_text, + uint32_t flags); + /* Report one matched path during a glob expansion. Return non-zero to stop. */ typedef int (*KitBuildGlobFn)(void* user, KitSlice path); diff --git a/src/api/build_coord.c b/src/api/build_coord.c @@ -216,6 +216,130 @@ KitStatus kit_build_client_source(KitBuildClient* c, KitSlice path, return KIT_OK; } +static KitStatus build_client_depfile_submit(KitBuildClient* c, + const char* path, size_t len) { + uint8_t blob[KIT_BUILD_HASH_LEN]; + KitSlice realpath; + KitSlice dep; + if (!c || !path || len == 0u || len >= BUILD_PATH_MAX) return KIT_INVALID; + dep.s = path; + dep.len = len; + return kit_build_client_source(c, dep, blob, &realpath); +} + +static int build_depfile_is_space(unsigned char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || + c == '\v'; +} + +static KitStatus build_client_depfile_make(KitBuildClient* c, + KitSlice depfile_text) { + char path[BUILD_PATH_MAX]; + size_t i = 0, npath = 0; + int in_deps = 0; + KitStatus st; + if (!c || (depfile_text.len && !depfile_text.s)) return KIT_INVALID; + +#define SUBMIT_PATH() \ + do { \ + if (npath != 0u) { \ + st = build_client_depfile_submit(c, path, npath); \ + if (st != KIT_OK) return st; \ + npath = 0u; \ + } \ + } while (0) +#define APPEND_PATH(ch_) \ + do { \ + if (npath + 1u >= sizeof path) return KIT_INVALID; \ + path[npath++] = (char)(ch_); \ + } while (0) + + while (i < depfile_text.len) { + unsigned char ch = (unsigned char)depfile_text.s[i++]; + if (ch == '\\') { + if (i >= depfile_text.len) { + if (in_deps) APPEND_PATH(ch); + continue; + } + ch = (unsigned char)depfile_text.s[i++]; + if (ch == '\r' && i < depfile_text.len && depfile_text.s[i] == '\n') + ++i; + if (ch == '\n' || ch == '\r') { + if (in_deps) SUBMIT_PATH(); + continue; + } + if (in_deps) APPEND_PATH(ch); + continue; + } + if (!in_deps) { + if (ch == ':') in_deps = 1; + continue; + } + if (ch == '#') { + SUBMIT_PATH(); + while (i < depfile_text.len) { + ch = (unsigned char)depfile_text.s[i++]; + if (ch == '\n' || ch == '\r') break; + } + in_deps = 0; + continue; + } + if (build_depfile_is_space(ch)) { + SUBMIT_PATH(); + if (ch == '\n' || ch == '\r') in_deps = 0; + continue; + } + APPEND_PATH(ch); + } + SUBMIT_PATH(); + +#undef APPEND_PATH +#undef SUBMIT_PATH + return KIT_OK; +} + +static KitStatus build_client_depfile_lines(KitBuildClient* c, + KitSlice depfile_text) { + size_t i = 0; + KitStatus st; + if (!c || (depfile_text.len && !depfile_text.s)) return KIT_INVALID; + while (i < depfile_text.len) { + size_t start, end; + while (i < depfile_text.len && + (depfile_text.s[i] == '\n' || depfile_text.s[i] == '\r')) + ++i; + start = i; + while (i < depfile_text.len && depfile_text.s[i] != '\n' && + depfile_text.s[i] != '\r') + ++i; + end = i; + if (end > start) { + st = build_client_depfile_submit(c, depfile_text.s + start, end - start); + if (st != KIT_OK) return st; + } + if (i < depfile_text.len && depfile_text.s[i] == '\r') { + ++i; + if (i < depfile_text.len && depfile_text.s[i] == '\n') ++i; + } else if (i < depfile_text.len && depfile_text.s[i] == '\n') { + ++i; + } + } + return KIT_OK; +} + +KitStatus kit_build_client_depfile_ex(KitBuildClient* c, KitSlice depfile_text, + uint32_t flags) { + if (flags & ~((uint32_t)KIT_BUILD_DEPFILE_LINES)) return KIT_INVALID; + if (flags & (uint32_t)KIT_BUILD_DEPFILE_LINES) + return build_client_depfile_lines(c, depfile_text); + return build_client_depfile_make(c, depfile_text); +} + +KitStatus kit_build_client_depfile(KitBuildClient* c, KitSlice depfile_text) { + return kit_build_client_depfile_ex(c, depfile_text, + KIT_BUILD_DEPFILE_DEFAULT); +} + typedef struct BuildClientGlobCb { KitBuildGlobFn cb; void* user; diff --git a/test/buildcoord/run.sh b/test/buildcoord/run.sh @@ -22,7 +22,8 @@ kit_report_init ws="$work/ws" store="$work/store" -mkdir -p "$ws/recipes" "$ws/src/globset" "$ws/src/tree/nested/deeper" "$store" +mkdir -p "$ws/recipes" "$ws/src/depfile" "$ws/src/globset" \ + "$ws/src/tree/nested/deeper" "$store" cat > "$ws/BUILD.kit" <<'EOF' kit-build 1 @@ -44,6 +45,10 @@ recipe recipes/cycle_self.sh recipe recipes/defn_v1.sh [target //defn:unused] recipe recipes/defn_unused_v1.sh +[target //depfile:lines] +recipe recipes/depfile_lines.sh +[target //depfile:probe] +recipe recipes/depfile.sh [target //env:probe] recipe recipes/env_probe.sh [target //fail:probe] @@ -89,6 +94,12 @@ EOF cat > "$ws/src/noop.txt" <<'EOF' first EOF +cat > "$ws/src/depfile/a.h" <<'EOF' +dep-a:v1 +EOF +cat > "$ws/src/depfile/b.h" <<'EOF' +dep-b:v1 +EOF cat > "$ws/src/globset/one.txt" <<'EOF' one EOF @@ -208,6 +219,30 @@ mkdir -p "$KIT_BUILD_OUT" printf 'unused:v2\n' > "$KIT_BUILD_OUT/unused.txt" EOF +cat > "$ws/recipes/depfile.sh" <<'EOF' +#!/bin/sh +set -eu +mkdir -p "$KIT_BUILD_OUT" +cat src/depfile/a.h src/depfile/b.h > "$KIT_BUILD_OUT/combined.txt" +cat > "$KIT_BUILD_OUT/probe.d" <<'DEPFILE' +obj/probe.o: src/depfile/a.h \ + src/depfile/b.h +DEPFILE +"$KIT" build depfile "$KIT_BUILD_OUT/probe.d" +EOF + +cat > "$ws/recipes/depfile_lines.sh" <<'EOF' +#!/bin/sh +set -eu +mkdir -p "$KIT_BUILD_OUT" +cat src/depfile/a.h src/depfile/b.h > "$KIT_BUILD_OUT/combined.txt" +cat > "$KIT_BUILD_OUT/probe.lines" <<'DEPFILE' +src/depfile/a.h +src/depfile/b.h +DEPFILE +"$KIT" build depfile --lines "$KIT_BUILD_OUT/probe.lines" +EOF + cat > "$ws/recipes/env_probe.sh" <<'EOF' #!/bin/sh set -eu @@ -532,6 +567,54 @@ contains "buildcoord-repeat-inputs-deep-hit" \ contains "buildcoord-repeat-inputs-deep-no-run" \ "$work/buildcoord-repeat-inputs-deep.err" "recipes_run=0" +build_assert_ok buildcoord-depfile-cold --stats //depfile:probe +contains "buildcoord-depfile-cold-run" "$work/buildcoord-depfile-cold.err" \ + "recipes_run=1" +depfile_cold_path=$(tree_path_from "$work/buildcoord-depfile-cold.out") +contains "buildcoord-depfile-cold-a" "$depfile_cold_path/combined.txt" \ + "dep-a:v1" +contains "buildcoord-depfile-cold-b" "$depfile_cold_path/combined.txt" \ + "dep-b:v1" +build_assert_ok buildcoord-depfile-deep --stats //depfile:probe +contains "buildcoord-depfile-deep-hit" "$work/buildcoord-depfile-deep.err" \ + "deep_hits=1" +contains "buildcoord-depfile-deep-no-run" "$work/buildcoord-depfile-deep.err" \ + "recipes_run=0" +cat > "$ws/src/depfile/a.h" <<'EOF' +dep-a:v2 +EOF +build_assert_ok buildcoord-depfile-edit --stats //depfile:probe +contains "buildcoord-depfile-edit-run" "$work/buildcoord-depfile-edit.err" \ + "recipes_run=1" +depfile_edit_path=$(tree_path_from "$work/buildcoord-depfile-edit.out") +contains "buildcoord-depfile-edit-output" "$depfile_edit_path/combined.txt" \ + "dep-a:v2" + +build_assert_ok buildcoord-depfile-lines-cold --stats //depfile:lines +contains "buildcoord-depfile-lines-cold-run" \ + "$work/buildcoord-depfile-lines-cold.err" "recipes_run=1" +depfile_lines_cold_path=$( + tree_path_from "$work/buildcoord-depfile-lines-cold.out") +contains "buildcoord-depfile-lines-cold-a" \ + "$depfile_lines_cold_path/combined.txt" "dep-a:v2" +contains "buildcoord-depfile-lines-cold-b" \ + "$depfile_lines_cold_path/combined.txt" "dep-b:v1" +build_assert_ok buildcoord-depfile-lines-deep --stats //depfile:lines +contains "buildcoord-depfile-lines-deep-hit" \ + "$work/buildcoord-depfile-lines-deep.err" "deep_hits=1" +contains "buildcoord-depfile-lines-deep-no-run" \ + "$work/buildcoord-depfile-lines-deep.err" "recipes_run=0" +cat > "$ws/src/depfile/b.h" <<'EOF' +dep-b:v2 +EOF +build_assert_ok buildcoord-depfile-lines-edit --stats //depfile:lines +contains "buildcoord-depfile-lines-edit-run" \ + "$work/buildcoord-depfile-lines-edit.err" "recipes_run=1" +depfile_lines_edit_path=$( + tree_path_from "$work/buildcoord-depfile-lines-edit.out") +contains "buildcoord-depfile-lines-edit-output" \ + "$depfile_lines_edit_path/combined.txt" "dep-b:v2" + build_assert_ok buildcoord-stats-cold --stats //stable:parent contains "buildcoord-stats-cold-recipes" "$work/buildcoord-stats-cold.err" \ "recipes_run=2"