From 42afa9a451abdacb07305969b8351227ff538ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Thu, 19 Feb 2026 11:59:43 +0000 Subject: [PATCH] build: split zig0 test into compile + link to fix caching The zig test step previously compiled Zig code and linked C objects in a single invocation. Since the Zig compiler hashes all link inputs (including .o file content) into one cache key, changing -Dzig0-cc or editing any C file invalidated the 6-minute Zig compilation cache. Split into two steps: emit the Zig test code as an object (cached independently of C objects), then link it with the C objects in a separate executable step. Manually set up the test runner IPC protocol via enableTestRunnerMode() to preserve build summary integration. Co-Authored-By: Claude Opus 4.6 --- build.zig | 57 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/build.zig b/build.zig index 55429f7b63..e3f1dbf370 100644 --- a/build.zig +++ b/build.zig @@ -1617,13 +1617,13 @@ fn addZig0TestStep( test_timeout: ?[]const u8, exe_options: *std.Build.Step.Options, ) void { + // Step 1: Compile Zig test code to .o (cached independently of C objects). const test_mod = b.createModule(.{ .root_source_file = b.path("stage0_test_root.zig"), .optimize = optimize, .target = target, }); test_mod.addIncludePath(b.path("stage0")); - addZig0CSources(b, test_mod, cc, optimize); test_mod.linkSystemLibrary("c", .{}); // Re-export module rooted in src/ (can resolve compiler-internal imports) @@ -1642,33 +1642,50 @@ fn addZig0TestStep( zig_internals_mod.addOptions("build_options", exe_options); test_mod.addImport("zig_internals", zig_internals_mod); - const timeout: ?[]const u8 = test_timeout orelse if (valgrind) null else "300"; - - const test_exe = b.addTest(.{ + const test_obj = b.addTest(.{ .root_module = test_mod, + .emit_object = true, .use_llvm = false, .use_lld = false, }); - if (valgrind) { - test_exe.setExecCmd(&.{ - "valgrind", - "--error-exitcode=2", - "--leak-check=full", - "--show-leak-kinds=all", - "--errors-for-leak-kinds=all", - "--track-fds=yes", - "--quiet", - null, - }); - } else { - test_exe.setExecCmd(&.{ "timeout", timeout orelse "10", null }); - } + + // Step 2: Link test_obj + C objects into final executable. + const link_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + link_mod.addObject(test_obj); + addZig0CSources(b, link_mod, cc, optimize); + link_mod.linkSystemLibrary("c", .{}); + + const test_exe = b.addExecutable(.{ + .name = "test", + .root_module = link_mod, + }); + + const timeout: ?[]const u8 = test_timeout orelse if (valgrind) null else "300"; + if (no_exec) { const install = b.addInstallArtifact(test_exe, .{}); step.dependOn(&install.step); } else { - const run = b.addRunArtifact(test_exe); - run.step.name = b.fmt("test ({s})", .{cc}); + // Step 3: Run with test IPC protocol. + const run = std.Build.Step.Run.create(b, b.fmt("test ({s})", .{cc})); + if (valgrind) { + run.addArgs(&.{ + "valgrind", + "--error-exitcode=2", + "--leak-check=full", + "--show-leak-kinds=all", + "--errors-for-leak-kinds=all", + "--track-fds=yes", + "--quiet", + }); + } else if (timeout) |t| { + run.addArgs(&.{ "timeout", t }); + } + run.addArtifactArg(test_exe); + run.enableTestRunnerMode(); step.dependOn(&run.step); } }