authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-06-01 17:38:33-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-06-29 23:50:18-07:00
loga04f90c35b116ede4d81c8f03f08d0f79a908dea
treed360736177123470808f30e57145b5c3bf40805f
parentc616c00db613c2a6fe7d5acfdfb884c006baa600

introduce std.Build API for making configure script depend on fs


8 files changed, 275 insertions(+), 98 deletions(-)

build.zig+2-3
......@@ -264,9 +264,8 @@ pub fn build(b: *std.Build) !void {
264264 std.process.exit(1);
265265 }
266266
267 // Ensure git version changes get picked up
268 // https://codeberg.org/ziglang/zig/issues/35473
269 b.graph.poisonCache();
267 // Ensure git version changes get picked up.
268 b.dependOnFileContents(b.graph.path(.build_root, ".git/HEAD"));
270269
271270 const version_string = b.fmt("{d}.{d}.{d}", .{ zig_version.major, zig_version.minor, zig_version.patch });
272271
lib/compiler/configurer.zig+32-1
......@@ -133,7 +133,7 @@ pub fn main(init: process.Init.Minimal) !void {
133133 .off => .no_color,
134134 };
135135
136 try builder.runBuild(root);
136 builder.runBuild(root);
137137
138138 if (builder.validateUserInputDidItFail()) {
139139 fatal(" access the help menu with 'zig build -h'", .{});
......@@ -632,6 +632,37 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
632632
633633 var s: Serialize = .{ .wc = wc, .arena = arena };
634634
635 try wc.path_deps.ensureTotalCapacityPrecise(gpa, graph.configure_dependencies.items.len);
636 for (
637 graph.configure_dependencies.items,
638 wc.path_deps.addManyAsSliceAssumeCapacity(graph.configure_dependencies.items.len),
639 ) |src, *dest| {
640 dest.* = .{
641 .flags = .{
642 .base = switch (src.lazy_path) {
643 .src_path, .dependency => .build_root,
644 .generated => unreachable,
645 .cwd_relative => .cwd,
646 .relative => |r| r.base,
647 },
648 .mode = src.mode,
649 },
650 .sub = switch (src.lazy_path) {
651 .src_path => |sp| try wc.addString(sp.sub_path),
652 .generated => unreachable,
653 .cwd_relative => |sub_path| try wc.addString(sub_path),
654 .dependency => |d| try wc.addString(d.sub_path),
655 .relative => |r| try wc.addString(r.sub_path),
656 },
657 .pkg = switch (src.lazy_path) {
658 .src_path => |sp| .init(try s.builderToPackage(sp.owner)),
659 .generated => unreachable,
660 .cwd_relative, .relative => .none,
661 .dependency => |d| .init(try s.builderToPackage(d.dependency.builder)),
662 },
663 };
664 }
665
635666 // Starting from all top-level steps in `b`, traverse the entire step graph
636667 // and add all step dependencies implied by module graphs.
637668 const top_level_steps = b.top_level_steps.values();
lib/std/Build.zig+111-10
......@@ -64,6 +64,11 @@ pkg_hash: []const u8,
6464/// A mapping from dependency names to package hashes.
6565available_deps: AvailableDeps,
6666
67pub const ConfigureDependency = struct {
68 lazy_path: LazyPath,
69 mode: std.Build.Configuration.PathDep.Mode,
70};
71
6772pub const ReleaseMode = enum {
6873 off,
6974 any,
......@@ -102,6 +107,12 @@ pub const Graph = struct {
102107 /// Observing this data causes cache poisoning. See `CachePoison`.
103108 search_prefixes: std.ArrayList([]const u8) = .empty,
104109
110 /// Populated by calling one of:
111 /// * `dependOnFileContents`
112 /// * `dependOnFileMetadata`
113 /// * `dependOnDirectory`
114 configure_dependencies: ArrayList(ConfigureDependency) = .empty,
115
105116 /// If the cache is poisoned means that the **configure logic** had side
106117 /// effects, or otherwise did something that could not be tracked by the
107118 /// cache system.
......@@ -165,7 +176,7 @@ pub const Graph = struct {
165176
166177 /// A path whose components and contents are known at some point during
167178 /// `Step` resolution, relative to the provided base directory.
168 pub fn path(graph: *Graph, base: Configuration.Path.Base, sub_path: []const u8) LazyPath {
179 pub fn path(graph: *Graph, base: Configuration.LazyPath.Relative.Base, sub_path: []const u8) LazyPath {
169180 return .{ .relative = .{
170181 .base = base,
171182 .sub_path = @This().dupePath(graph, sub_path),
......@@ -204,6 +215,9 @@ pub const Graph = struct {
204215 /// did something that could not be tracked by the cache system.
205216 ///
206217 /// See `CachePoison` documentation for more details.
218 ///
219 /// As an alternative to calling this function, consider these APIs instead:
220 /// * `dependOnFileContents`
207221 pub fn poisonCache(graph: *Graph) void {
208222 switch (graph.cache_poison) {
209223 .pure => graph.cache_poison = .poisoned,
......@@ -2318,14 +2332,13 @@ fn dependencyInner(
23182332 .root_dir = .{
23192333 .path = build_root_string,
23202334 .handle = Io.Dir.cwd().openDir(io, build_root_string, .{}) catch |err|
2321 process.fatal("unable to open {s}: {t}", .{ build_root_string, err }),
2335 process.fatal("failed to open {q}: {t}", .{ build_root_string, err }),
23222336 },
23232337 };
23242338
2325 const sub_builder = b.createChild(name, dep_root, pkg_hash, pkg_deps, user_input_options) catch
2326 @panic("unhandled error");
2339 const sub_builder = b.createChild(name, dep_root, pkg_hash, pkg_deps, user_input_options) catch @panic("OOM");
23272340 if (build_zig) |bz| {
2328 sub_builder.runBuild(bz) catch @panic("unhandled error");
2341 sub_builder.runBuild(bz);
23292342
23302343 if (sub_builder.validateUserInputDidItFail()) {
23312344 std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
......@@ -2343,11 +2356,10 @@ fn dependencyInner(
23432356}
23442357
23452358/// Build system implementation detail.
2346pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void {
2359pub fn runBuild(b: *Build, build_zig: anytype) void {
23472360 switch (@typeInfo(@typeInfo(@TypeOf(build_zig.build)).@"fn".return_type.?)) {
2348 .void => build_zig.build(b),
2349 .error_union => try build_zig.build(b),
2350 else => @compileError("expected return type of build to be 'void' or '!void'"),
2361 .error_union => return build_zig.build(b) catch unreachable,
2362 else => return build_zig.build(b),
23512363 }
23522364}
23532365
......@@ -2411,7 +2423,7 @@ pub const LazyPath = union(enum) {
24112423 },
24122424
24132425 relative: struct {
2414 base: Configuration.Path.Base,
2426 base: Configuration.LazyPath.Relative.Base,
24152427 sub_path: []const u8 = "",
24162428
24172429 pub fn eql(a: @This(), b: @This()) bool {
......@@ -2717,6 +2729,95 @@ pub fn systemIntegrationOption(
27172729 }
27182730}
27192731
2732/// Indicates that the build.zig logic depends on a particular file's contents.
2733///
2734/// If the file is created, deleted, or has its contents changed, the configure
2735/// phase will be repeated. If the inode or mtime change, but the file contents
2736/// remain the same, it will not cause the configure logic to be repeated.
2737///
2738/// This is an alternative to `Graph.poisonCache` that avoids making every invocation
2739/// of `zig build` into a cache miss.
2740///
2741/// Only a subset of `LazyPath` are supported:
2742/// - Relative to cwd
2743/// - Relative to any package root
2744/// - Relative to zig cache or zig installation
2745///
2746/// If the file would be inside one of the search prefixes, then the dependency
2747/// cannot be tracked; `Graph.poisonCache` must be used instead.
2748pub fn dependOnFileContents(b: *Build, lazy_path: LazyPath) void {
2749 validateConfigureDependency(lazy_path);
2750 const graph = b.graph;
2751 graph.configure_dependencies.append(graph.arena, .{
2752 .lazy_path = lazy_path.dupe(graph),
2753 .mode = .contents,
2754 }) catch @panic("OOM");
2755}
2756
2757/// Indicates that the build.zig logic depends on a particular file's size,
2758/// inode, mtime, and contents.
2759///
2760/// If the file is created, deleted, has its contents changed, or the inode
2761/// changes, or the mtime changes, the configure phase will be repeated.
2762///
2763/// This is an alternative to `Graph.poisonCache` that avoids making every invocation
2764/// of `zig build` into a cache miss.
2765///
2766/// Only a subset of `LazyPath` are supported:
2767/// - Relative to cwd
2768/// - Relative to any package root
2769/// - Relative to zig cache or zig installation
2770///
2771/// If the file would be inside one of the search prefixes, then the dependency
2772/// cannot be tracked; `Graph.poisonCache` must be used instead.
2773pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void {
2774 validateConfigureDependency(lazy_path);
2775 const graph = b.graph;
2776 graph.configure_dependencies.append(graph.arena, .{
2777 .lazy_path = lazy_path.dupe(graph),
2778 .mode = .metadata,
2779 }) catch @panic("OOM");
2780}
2781
2782/// Indicates that the build.zig logic depends on a particular directory's entries.
2783///
2784/// This is an alternative to `Graph.poisonCache` that avoids making every invocation
2785/// of `zig build` into a cache miss.
2786///
2787/// If any file is created, deleted, or renamed in this directory, the
2788/// configure phase will be repeated.
2789///
2790/// Only a subset of `LazyPath` are supported:
2791/// - Relative to cwd
2792/// - Relative to any package root
2793/// - Relative to zig cache or zig installation
2794///
2795/// If the directory would be inside one of the search prefixes, then the dependency
2796/// cannot be tracked; `Graph.poisonCache` must be used instead.
2797pub fn dependOnDirectory(b: *Build, lazy_path: LazyPath) void {
2798 validateConfigureDependency(lazy_path);
2799 const graph = b.graph;
2800 graph.configure_dependencies.append(graph.arena, .{
2801 .lazy_path = lazy_path.dupe(graph),
2802 .mode = .directory,
2803 }) catch @panic("OOM");
2804}
2805
2806fn validateConfigureDependency(lazy_path: LazyPath) void {
2807 switch (lazy_path) {
2808 .src_path, .cwd_relative, .dependency => {}, // OK
2809 .generated => @panic("configure phase cannot depend on files generated during make phase"),
2810 .relative => |relative| switch (relative.base) {
2811 .cwd, .build_root, .local_cache, .global_cache, .zig_exe, .zig_lib => {}, // OK
2812 .install_prefix,
2813 .install_lib,
2814 .install_bin,
2815 .install_include,
2816 => @panic("configure phase cannot depend on files installed during make phase"),
2817 },
2818 }
2819}
2820
27202821test {
27212822 _ = Cache;
27222823 _ = Configuration;
lib/std/Build/Cache.zig+16-12
......@@ -57,7 +57,7 @@ pub fn prefixes(cache: *const Cache) []const Directory {
5757 return cache.prefixes_buffer[0..cache.prefixes_len];
5858}
5959
60const PrefixedPath = struct {
60pub const PrefixedPath = struct {
6161 prefix: u8,
6262 sub_path: []const u8,
6363
......@@ -1000,18 +1000,21 @@ pub const Manifest = struct {
10001000 /// other files will need to be recompiled if the imported file is changed.
10011001 pub fn addFilePost(self: *Manifest, file_path: []const u8) !void {
10021002 assert(self.manifest_file != null);
1003
10041003 const gpa = self.cache.gpa;
10051004 const prefixed_path = try self.cache.findPrefix(file_path);
1006 errdefer gpa.free(prefixed_path.sub_path);
1005 var keep = false;
1006 defer if (!keep) gpa.free(prefixed_path.sub_path);
1007 keep = try addPrefixedPathPost(self, prefixed_path);
1008 }
10071009
1008 const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});
1009 errdefer _ = self.files.pop();
1010 pub fn addPrefixedPathPost(man: *Manifest, prefixed_path: PrefixedPath) !bool {
1011 assert(man.manifest_file != null);
1012 const gpa = man.cache.gpa;
10101013
1011 if (gop.found_existing) {
1012 gpa.free(prefixed_path.sub_path);
1013 return;
1014 }
1014 const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});
1015 errdefer _ = man.files.pop();
1016
1017 if (gop.found_existing) return false;
10151018
10161019 gop.key_ptr.* = .{
10171020 .prefixed_path = prefixed_path,
......@@ -1022,10 +1025,11 @@ pub const Manifest = struct {
10221025 .contents = null,
10231026 };
10241027
1025 self.files.lockPointers();
1026 defer self.files.unlockPointers();
1028 man.files.lockPointers();
1029 defer man.files.unlockPointers();
10271030
1028 try self.populateFileHash(gop.key_ptr);
1031 try man.populateFileHash(gop.key_ptr);
1032 return true;
10291033 }
10301034
10311035 pub fn addPathPost(man: *Manifest, path: Path) !void {
lib/std/Build/Configuration.zig+50-25
......@@ -10,8 +10,7 @@ const native_endian = builtin.target.cpu.arch.endian();
1010
1111string_bytes: []u8,
1212steps: []Step,
13path_deps_base: []Path.Base,
14path_deps_sub: []String,
13path_deps: []PathDep,
1514unlazy_deps: []String,
1615system_integrations: []SystemIntegration,
1716available_options: []AvailableOption,
......@@ -57,7 +56,7 @@ pub const Wip = struct {
5756 system_integrations: std.ArrayList(SystemIntegration) = .empty,
5857 available_options: std.ArrayList(AvailableOption) = .empty,
5958 steps: std.ArrayList(Step) = .empty,
60 path_deps: std.MultiArrayList(Path) = .empty,
59 path_deps: std.ArrayList(PathDep) = .empty,
6160 search_prefixes: std.ArrayList(String) = .empty,
6261 extra: std.ArrayList(u32) = .empty,
6362 next_generated_file_index: u32 = 0,
......@@ -154,7 +153,7 @@ pub const Wip = struct {
154153 const header: Header = .{
155154 .string_bytes_len = @intCast(wip.string_bytes.items.len),
156155 .steps_len = @intCast(wip.steps.items.len),
157 .path_deps_len = @intCast(wip.path_deps.len),
156 .path_deps_len = @intCast(wip.path_deps.items.len),
158157 .unlazy_deps_len = @intCast(wip.unlazy_deps.items.len),
159158 .system_integrations_len = @intCast(wip.system_integrations.items.len),
160159 .available_options_len = @intCast(wip.available_options.items.len),
......@@ -171,8 +170,7 @@ pub const Wip = struct {
171170 @ptrCast(&header),
172171 wip.string_bytes.items,
173172 @ptrCast(wip.steps.items),
174 @ptrCast(wip.path_deps.items(.base)),
175 @ptrCast(wip.path_deps.items(.sub)),
173 @ptrCast(wip.path_deps.items),
176174 @ptrCast(wip.unlazy_deps.items),
177175 @ptrCast(wip.system_integrations.items),
178176 @ptrCast(wip.available_options.items),
......@@ -1551,9 +1549,22 @@ pub const LazyPath = union(@This().Tag) {
15511549
15521550 pub const Flags = packed struct(u32) {
15531551 tag: Tag = .relative,
1554 base: Path.Base,
1552 base: Base,
15551553 _: u16 = 0,
15561554 };
1555
1556 pub const Base = enum(u8) {
1557 cwd,
1558 local_cache,
1559 global_cache,
1560 build_root,
1561 zig_exe,
1562 zig_lib,
1563 install_prefix,
1564 install_lib,
1565 install_bin,
1566 install_include,
1567 };
15571568 };
15581569};
15591570
......@@ -1597,6 +1608,26 @@ pub const Package = struct {
15971608 return package.dep_prefix.slice(c);
15981609 }
15991610 };
1611
1612 pub const OptionalIndex = enum(u32) {
1613 none = max_u32 - 1,
1614 root = max_u32,
1615 _,
1616
1617 pub fn init(i: Index) OptionalIndex {
1618 const result: OptionalIndex = @enumFromInt(@intFromEnum(i));
1619 assert(result != .none);
1620 return result;
1621 }
1622
1623 pub fn unwrap(this: @This()) ?Index {
1624 return switch (this) {
1625 .none => null,
1626 .root => .root,
1627 _ => @enumFromInt(@intFromEnum(this)),
1628 };
1629 }
1630 };
16001631};
16011632
16021633pub const Module = struct {
......@@ -1833,24 +1864,20 @@ pub const OptionalStringList = enum(u32) {
18331864 }
18341865};
18351866
1836pub const Path = extern struct {
1837 base: Base,
1867pub const PathDep = extern struct {
1868 flags: Flags,
18381869 sub: String,
1870 pkg: Package.OptionalIndex,
18391871
1840 pub const Base = enum(u8) {
1841 cwd,
1842 local_cache,
1843 global_cache,
1844 build_root,
1845 zig_exe,
1846 zig_lib,
1847 install_prefix,
1848 install_lib,
1849 install_bin,
1850 install_include,
1872 pub const Flags = packed struct(u32) {
1873 mode: Mode,
1874 base: LazyPath.Relative.Base,
1875 _: u16 = 0,
18511876 };
18521877
1853 pub fn toCachePath(path: Path, c: *const Configuration, arena: Allocator) std.Build.Cache.Path {
1878 pub const Mode = enum(u8) { directory, contents, metadata };
1879
1880 pub fn toCachePath(path: PathDep, c: *const Configuration, arena: Allocator) std.Build.Cache.Path {
18541881 _ = c;
18551882 _ = arena;
18561883 _ = path;
......@@ -3430,8 +3457,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {
34303457 const result: Configuration = .{
34313458 .string_bytes = try arena.alloc(u8, header.string_bytes_len),
34323459 .steps = try arena.alloc(Step, header.steps_len),
3433 .path_deps_sub = try arena.alloc(String, header.path_deps_len),
3434 .path_deps_base = try arena.alloc(Path.Base, header.path_deps_len),
3460 .path_deps = try arena.alloc(PathDep, header.path_deps_len),
34353461 .unlazy_deps = try arena.alloc(String, header.unlazy_deps_len),
34363462 .system_integrations = try arena.alloc(SystemIntegration, header.system_integrations_len),
34373463 .available_options = try arena.alloc(AvailableOption, header.available_options_len),
......@@ -3444,8 +3470,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {
34443470 var vecs = [_][]u8{
34453471 result.string_bytes,
34463472 @ptrCast(result.steps),
3447 @ptrCast(result.path_deps_base),
3448 @ptrCast(result.path_deps_sub),
3473 @ptrCast(result.path_deps),
34493474 @ptrCast(result.unlazy_deps),
34503475 @ptrCast(result.system_integrations),
34513476 @ptrCast(result.available_options),
src/main.zig+35-29
......@@ -5454,6 +5454,9 @@ fn cmdBuild(
54545454 }
54555455 defer Fork.deinitList(forks.items);
54565456
5457 var file_system_inputs: std.ArrayList(u8) = .empty;
5458 defer file_system_inputs.deinit(gpa);
5459
54575460 // This loop is re-evaluated when the build script exits with an indication that it
54585461 // could not continue due to missing lazy dependencies.
54595462 const configuration_path: Path, const poisoned: bool = cp: while (true) {
......@@ -5679,6 +5682,7 @@ fn cmdBuild(
56795682
56805683 try root_mod.deps.put(arena, "@build", build_mod);
56815684
5685 file_system_inputs.clearRetainingCapacity();
56825686 var create_diag: Compilation.CreateDiagnostic = undefined;
56835687 const comp = Compilation.create(gpa, arena, io, &create_diag, .{
56845688 .libc_installation = libc_installation,
......@@ -5702,6 +5706,7 @@ fn cmdBuild(
57025706 .reference_trace = reference_trace,
57035707 .debug_compile_errors = debug_compile_errors,
57045708 .environ_map = environ_map,
5709 .file_system_inputs = &file_system_inputs,
57055710 }) catch |err| switch (err) {
57065711 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),
57075712 else => |e| fatal("failed to create compilation: {t}", .{e}),
......@@ -5764,10 +5769,10 @@ fn cmdBuild(
57645769 .argv = configure_argv.items,
57655770 .stdout = .{ .file = config_tmp_file },
57665771 .progress_node = child_node,
5767 }) catch |err| fatal("failed to spawn configure script {s}: {t}", .{ configure_argv.items[0], err });
5772 }) catch |err| fatal("failed to spawn configure script {q}: {t}", .{ configure_argv.items[0], err });
57685773 defer child.kill(io);
57695774 break :term child.wait(io) catch |err|
5770 fatal("failed to wait configure script {s}: {t}", .{ configure_argv.items[0], err });
5775 fatal("failed to wait configure script {q}: {t}", .{ configure_argv.items[0], err });
57715776 };
57725777 if (!term.success()) {
57735778 // Failure to produce the configuration file.
......@@ -5816,6 +5821,21 @@ fn cmdBuild(
58165821 try config_man.addPathPost(conf_path.toCachePath(&configuration, arena));
58175822 }
58185823
5824 // We need to add to the configuration cache the source files of
5825 // configurer itself, so that the maker process can watch the file system
5826 // for those changes and restart itself. By doing this, we make it
5827 // possible to bypass creating a Compilation for configurer on
5828 // Configuration cache hit.
5829 {
5830 var it = mem.splitScalar(u8, file_system_inputs.items, 0);
5831 while (it.next()) |input| {
5832 _ = try config_man.addPrefixedPathPost(.{
5833 .prefix = input[0],
5834 .sub_path = input[1..],
5835 });
5836 }
5837 }
5838
58195839 // If it is poisoned, there is no point in moving it to cached
58205840 // location. Just leave it in the tmp directory.
58215841 if (configuration.poisoned) {
......@@ -6247,14 +6267,15 @@ fn jitCmdInner(
62476267
62486268 child_argv.appendSliceAssumeCapacity(args);
62496269
6270 if (EnvVar.ZIG_DEBUG_CMD.isSet(environ_map)) {
6271 const cmd = try std.mem.join(arena, " ", child_argv.items);
6272 std.debug.print("{s}\n", .{cmd});
6273 }
6274
62506275 if (process.can_replace and options.capture == null) {
6251 if (EnvVar.ZIG_DEBUG_CMD.isSet(environ_map)) {
6252 const cmd = try std.mem.join(arena, " ", child_argv.items);
6253 std.debug.print("{s}\n", .{cmd});
6254 }
62556276 const err = process.replace(io, .{ .argv = child_argv.items, .environ_map = environ_map });
62566277 const cmd = try std.mem.join(arena, " ", child_argv.items);
6257 fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });
6278 fatal("the following command failed to execve with {t}:\n{s}", .{ err, cmd });
62586279 }
62596280
62606281 if (!process.can_spawn) {
......@@ -6264,7 +6285,7 @@ fn jitCmdInner(
62646285 });
62656286 }
62666287
6267 switch (t: {
6288 const term = t: {
62686289 _ = try io.lockStderr(&.{}, .no_color);
62696290 defer io.unlockStderr();
62706291
......@@ -6282,28 +6303,13 @@ fn jitCmdInner(
62826303 }
62836304
62846305 break :t try child.wait(io);
6285 }) {
6286 .exited => |code| {
6287 if (code == 0) {
6288 if (options.capture != null) return;
6289 return cleanExit(io);
6290 }
6291 const cmd = try std.mem.join(arena, " ", child_argv.items);
6292 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
6293 },
6294 .signal => |sig| {
6295 const cmd = try std.mem.join(arena, " ", child_argv.items);
6296 fatal("the following build command terminated with signal {t}:\n{s}", .{ sig, cmd });
6297 },
6298 .stopped => |sig| {
6299 const cmd = try std.mem.join(arena, " ", child_argv.items);
6300 fatal("the following build command stopped with signal {t}:\n{s}", .{ sig, cmd });
6301 },
6302 .unknown => {
6303 const cmd = try std.mem.join(arena, " ", child_argv.items);
6304 fatal("the following build command crashed:\n{s}", .{cmd});
6305 },
6306 };
6307 if (term.success()) {
6308 if (options.capture != null) return;
6309 return cleanExit(io);
63066310 }
6311 const cmd = try std.mem.join(arena, " ", child_argv.items);
6312 fatal("the following build command {f}:\n{s}", .{ term, cmd });
63076313}
63086314
63096315const info_zen =
test/src/Cases.zig+17-10
......@@ -316,20 +316,19 @@ pub fn addCompile(
316316/// Each file should include a test manifest as a contiguous block of comments at
317317/// the end of the file. The first line should be the test type, followed by a set of
318318/// key-value config values, followed by a blank line, then the expected output.
319pub fn addFromDir(ctx: *Cases, dir: Io.Dir, b: *std.Build) void {
319pub fn addFromDir(ctx: *Cases, dir: Io.Dir, path_from_root: []const u8, b: *std.Build) void {
320320 var current_file: []const u8 = "none";
321 ctx.addFromDirInner(dir, &current_file, b) catch |err| {
322 std.debug.panicExtra(
323 @returnAddress(),
324 "test harness failed to process file '{s}': {s}\n",
325 .{ current_file, @errorName(err) },
326 );
321 ctx.addFromDirInner(dir, path_from_root, &current_file, b) catch |err| {
322 std.debug.panicExtra(@returnAddress(), "test harness failed to process file {q}: {t}\n", .{
323 current_file, err,
324 });
327325 };
328326}
329327
330328fn addFromDirInner(
331329 ctx: *Cases,
332330 iterable_dir: Io.Dir,
331 path_from_root: []const u8,
333332 /// This is kept up to date with the currently being processed file so
334333 /// that if any errors occur the caller knows it happened during this file.
335334 current_file: *[]const u8,
......@@ -340,11 +339,19 @@ fn addFromDirInner(
340339 var filenames: ArrayList([]const u8) = .empty;
341340
342341 while (try it.next(io)) |entry| {
343 if (entry.kind != .file) continue;
344
345342 // Ignore stuff such as .swp files
346343 if (!knownFileExtension(entry.basename)) continue;
347 try filenames.append(ctx.arena, try ctx.arena.dupe(u8, entry.path));
344
345 switch (entry.kind) {
346 .file => {
347 b.dependOnFileContents(b.path(b.pathJoin(&.{ path_from_root, entry.path })));
348 try filenames.append(ctx.arena, try ctx.arena.dupe(u8, entry.path));
349 },
350 .directory => {
351 b.dependOnDirectory(b.path(b.pathJoin(&.{ path_from_root, entry.path })));
352 },
353 else => continue,
354 }
348355 }
349356
350357 for (filenames.items) |filename| {
test/tests.zig+12-8
......@@ -3258,14 +3258,12 @@ pub fn addCases(
32583258
32593259 var cases = @import("src/Cases.zig").init(gpa, arena, io);
32603260
3261 // Ensure changes to these files get picked up
3262 // https://codeberg.org/ziglang/zig/issues/35473
3263 b.graph.poisonCache();
3261 b.dependOnDirectory(b.path("test/cases"));
32643262
32653263 var dir = try b.root.openDir(io, "test/cases", .{ .iterate = true });
32663264 defer dir.close(io);
32673265
3268 cases.addFromDir(dir, b);
3266 cases.addFromDir(dir, "test/cases", b);
32693267 try @import("cases.zig").addCases(&cases, build_options, b);
32703268
32713269 cases.lowerToBuildSteps(
......@@ -3320,22 +3318,28 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons
33203318 }),
33213319 });
33223320
3323 // Ensure changes to these files get picked up
3324 // https://codeberg.org/ziglang/zig/issues/35473
3325 b.graph.poisonCache();
3321 b.dependOnDirectory(b.path("test/incremental"));
33263322
33273323 var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true });
33283324 defer dir.close(io);
33293325
33303326 var it = try dir.walk(b.graph.arena);
33313327 while (try it.next(io)) |entry| {
3332 if (entry.kind != .file) continue;
33333328 if (std.mem.endsWith(u8, entry.basename, ".swp")) continue;
33343329
33353330 for (test_filters) |test_filter| {
33363331 if (std.mem.indexOf(u8, entry.path, test_filter)) |_| break;
33373332 } else if (test_filters.len > 0) continue;
33383333
3334 switch (entry.kind) {
3335 .file => {},
3336 .directory => {
3337 b.dependOnDirectory(b.path(b.pathJoin(&.{ "test", "incremental", entry.path })));
3338 },
3339 else => continue,
3340 }
3341 b.dependOnFileContents(b.path(b.pathJoin(&.{ "test", "incremental", entry.path })));
3342
33393343 for (incremental_targets) |target_str| {
33403344 const run = b.addRunArtifact(incr_check);
33413345 run.setName(b.fmt("incr-check {s} '{s}'", .{ target_str, entry.basename }));