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