diff --git a/build.zig b/build.zig index ebf163bf5c136a61ecad9120c75c4215cdd8345e..e97fac39188412a77f57f3559247cf07a40c25ed 100644 --- a/build.zig +++ b/build.zig @@ -264,9 +264,8 @@ pub fn build(b: *std.Build) !void { std.process.exit(1); } - // Ensure git version changes get picked up - // https://codeberg.org/ziglang/zig/issues/35473 - b.graph.poisonCache(); + // Ensure git version changes get picked up. + b.dependOnFileContents(b.graph.path(.build_root, ".git/HEAD")); const version_string = b.fmt("{d}.{d}.{d}", .{ zig_version.major, zig_version.minor, zig_version.patch }); diff --git a/lib/compiler/configurer.zig b/lib/compiler/configurer.zig index f044dc311945d20b330d472553ad5dded0d3138f..809bb9e6263c3a9c0e128bb6a677589fef6054c9 100644 --- a/lib/compiler/configurer.zig +++ b/lib/compiler/configurer.zig @@ -133,7 +133,7 @@ pub fn main(init: process.Init.Minimal) !void { .off => .no_color, }; - try builder.runBuild(root); + builder.runBuild(root); if (builder.validateUserInputDidItFail()) { 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 { var s: Serialize = .{ .wc = wc, .arena = arena }; + try wc.path_deps.ensureTotalCapacityPrecise(gpa, graph.configure_dependencies.items.len); + for ( + graph.configure_dependencies.items, + wc.path_deps.addManyAsSliceAssumeCapacity(graph.configure_dependencies.items.len), + ) |src, *dest| { + dest.* = .{ + .flags = .{ + .base = switch (src.lazy_path) { + .src_path, .dependency => .build_root, + .generated => unreachable, + .cwd_relative => .cwd, + .relative => |r| r.base, + }, + .mode = src.mode, + }, + .sub = switch (src.lazy_path) { + .src_path => |sp| try wc.addString(sp.sub_path), + .generated => unreachable, + .cwd_relative => |sub_path| try wc.addString(sub_path), + .dependency => |d| try wc.addString(d.sub_path), + .relative => |r| try wc.addString(r.sub_path), + }, + .pkg = switch (src.lazy_path) { + .src_path => |sp| .init(try s.builderToPackage(sp.owner)), + .generated => unreachable, + .cwd_relative, .relative => .none, + .dependency => |d| .init(try s.builderToPackage(d.dependency.builder)), + }, + }; + } + // Starting from all top-level steps in `b`, traverse the entire step graph // and add all step dependencies implied by module graphs. const top_level_steps = b.top_level_steps.values(); diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 08fa2ed36583d58f32d01b64df4e05c9ab10ae00..84dd3f84169b776c3e5197ee02979af30d7eb143 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -64,6 +64,11 @@ pkg_hash: []const u8, /// A mapping from dependency names to package hashes. available_deps: AvailableDeps, +pub const ConfigureDependency = struct { + lazy_path: LazyPath, + mode: std.Build.Configuration.PathDep.Mode, +}; + pub const ReleaseMode = enum { off, any, @@ -102,6 +107,12 @@ pub const Graph = struct { /// Observing this data causes cache poisoning. See `CachePoison`. search_prefixes: std.ArrayList([]const u8) = .empty, + /// Populated by calling one of: + /// * `dependOnFileContents` + /// * `dependOnFileMetadata` + /// * `dependOnDirectory` + configure_dependencies: ArrayList(ConfigureDependency) = .empty, + /// If the cache is poisoned means that the **configure logic** had side /// effects, or otherwise did something that could not be tracked by the /// cache system. @@ -165,7 +176,7 @@ pub const Graph = struct { /// A path whose components and contents are known at some point during /// `Step` resolution, relative to the provided base directory. - pub fn path(graph: *Graph, base: Configuration.Path.Base, sub_path: []const u8) LazyPath { + pub fn path(graph: *Graph, base: Configuration.LazyPath.Relative.Base, sub_path: []const u8) LazyPath { return .{ .relative = .{ .base = base, .sub_path = @This().dupePath(graph, sub_path), @@ -204,6 +215,9 @@ pub const Graph = struct { /// did something that could not be tracked by the cache system. /// /// See `CachePoison` documentation for more details. + /// + /// As an alternative to calling this function, consider these APIs instead: + /// * `dependOnFileContents` pub fn poisonCache(graph: *Graph) void { switch (graph.cache_poison) { .pure => graph.cache_poison = .poisoned, @@ -2318,14 +2332,13 @@ fn dependencyInner( .root_dir = .{ .path = build_root_string, .handle = Io.Dir.cwd().openDir(io, build_root_string, .{}) catch |err| - process.fatal("unable to open {s}: {t}", .{ build_root_string, err }), + process.fatal("failed to open {q}: {t}", .{ build_root_string, err }), }, }; - const sub_builder = b.createChild(name, dep_root, pkg_hash, pkg_deps, user_input_options) catch - @panic("unhandled error"); + const sub_builder = b.createChild(name, dep_root, pkg_hash, pkg_deps, user_input_options) catch @panic("OOM"); if (build_zig) |bz| { - sub_builder.runBuild(bz) catch @panic("unhandled error"); + sub_builder.runBuild(bz); if (sub_builder.validateUserInputDidItFail()) { std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); @@ -2343,11 +2356,10 @@ fn dependencyInner( } /// Build system implementation detail. -pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void { +pub fn runBuild(b: *Build, build_zig: anytype) void { switch (@typeInfo(@typeInfo(@TypeOf(build_zig.build)).@"fn".return_type.?)) { - .void => build_zig.build(b), - .error_union => try build_zig.build(b), - else => @compileError("expected return type of build to be 'void' or '!void'"), + .error_union => return build_zig.build(b) catch unreachable, + else => return build_zig.build(b), } } @@ -2411,7 +2423,7 @@ pub const LazyPath = union(enum) { }, relative: struct { - base: Configuration.Path.Base, + base: Configuration.LazyPath.Relative.Base, sub_path: []const u8 = "", pub fn eql(a: @This(), b: @This()) bool { @@ -2717,6 +2729,95 @@ pub fn systemIntegrationOption( } } +/// Indicates that the build.zig logic depends on a particular file's contents. +/// +/// If the file is created, deleted, or has its contents changed, the configure +/// phase will be repeated. If the inode or mtime change, but the file contents +/// remain the same, it will not cause the configure logic to be repeated. +/// +/// This is an alternative to `Graph.poisonCache` that avoids making every invocation +/// of `zig build` into a cache miss. +/// +/// Only a subset of `LazyPath` are supported: +/// - Relative to cwd +/// - Relative to any package root +/// - Relative to zig cache or zig installation +/// +/// If the file would be inside one of the search prefixes, then the dependency +/// cannot be tracked; `Graph.poisonCache` must be used instead. +pub fn dependOnFileContents(b: *Build, lazy_path: LazyPath) void { + validateConfigureDependency(lazy_path); + const graph = b.graph; + graph.configure_dependencies.append(graph.arena, .{ + .lazy_path = lazy_path.dupe(graph), + .mode = .contents, + }) catch @panic("OOM"); +} + +/// Indicates that the build.zig logic depends on a particular file's size, +/// inode, mtime, and contents. +/// +/// If the file is created, deleted, has its contents changed, or the inode +/// changes, or the mtime changes, the configure phase will be repeated. +/// +/// This is an alternative to `Graph.poisonCache` that avoids making every invocation +/// of `zig build` into a cache miss. +/// +/// Only a subset of `LazyPath` are supported: +/// - Relative to cwd +/// - Relative to any package root +/// - Relative to zig cache or zig installation +/// +/// If the file would be inside one of the search prefixes, then the dependency +/// cannot be tracked; `Graph.poisonCache` must be used instead. +pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void { + validateConfigureDependency(lazy_path); + const graph = b.graph; + graph.configure_dependencies.append(graph.arena, .{ + .lazy_path = lazy_path.dupe(graph), + .mode = .metadata, + }) catch @panic("OOM"); +} + +/// Indicates that the build.zig logic depends on a particular directory's entries. +/// +/// This is an alternative to `Graph.poisonCache` that avoids making every invocation +/// of `zig build` into a cache miss. +/// +/// If any file is created, deleted, or renamed in this directory, the +/// configure phase will be repeated. +/// +/// Only a subset of `LazyPath` are supported: +/// - Relative to cwd +/// - Relative to any package root +/// - Relative to zig cache or zig installation +/// +/// If the directory would be inside one of the search prefixes, then the dependency +/// cannot be tracked; `Graph.poisonCache` must be used instead. +pub fn dependOnDirectory(b: *Build, lazy_path: LazyPath) void { + validateConfigureDependency(lazy_path); + const graph = b.graph; + graph.configure_dependencies.append(graph.arena, .{ + .lazy_path = lazy_path.dupe(graph), + .mode = .directory, + }) catch @panic("OOM"); +} + +fn validateConfigureDependency(lazy_path: LazyPath) void { + switch (lazy_path) { + .src_path, .cwd_relative, .dependency => {}, // OK + .generated => @panic("configure phase cannot depend on files generated during make phase"), + .relative => |relative| switch (relative.base) { + .cwd, .build_root, .local_cache, .global_cache, .zig_exe, .zig_lib => {}, // OK + .install_prefix, + .install_lib, + .install_bin, + .install_include, + => @panic("configure phase cannot depend on files installed during make phase"), + }, + } +} + test { _ = Cache; _ = Configuration; diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 33c3148dd22080b5916100f4c3e210dabe38e14d..0880d2abeaa9e4fa9580673b3a16f028c2b3d0f4 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -57,7 +57,7 @@ pub fn prefixes(cache: *const Cache) []const Directory { return cache.prefixes_buffer[0..cache.prefixes_len]; } -const PrefixedPath = struct { +pub const PrefixedPath = struct { prefix: u8, sub_path: []const u8, @@ -1000,18 +1000,21 @@ pub const Manifest = struct { /// other files will need to be recompiled if the imported file is changed. pub fn addFilePost(self: *Manifest, file_path: []const u8) !void { assert(self.manifest_file != null); - const gpa = self.cache.gpa; const prefixed_path = try self.cache.findPrefix(file_path); - errdefer gpa.free(prefixed_path.sub_path); + var keep = false; + defer if (!keep) gpa.free(prefixed_path.sub_path); + keep = try addPrefixedPathPost(self, prefixed_path); + } - const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); - errdefer _ = self.files.pop(); + pub fn addPrefixedPathPost(man: *Manifest, prefixed_path: PrefixedPath) !bool { + assert(man.manifest_file != null); + const gpa = man.cache.gpa; - if (gop.found_existing) { - gpa.free(prefixed_path.sub_path); - return; - } + const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); + errdefer _ = man.files.pop(); + + if (gop.found_existing) return false; gop.key_ptr.* = .{ .prefixed_path = prefixed_path, @@ -1022,10 +1025,11 @@ pub const Manifest = struct { .contents = null, }; - self.files.lockPointers(); - defer self.files.unlockPointers(); + man.files.lockPointers(); + defer man.files.unlockPointers(); - try self.populateFileHash(gop.key_ptr); + try man.populateFileHash(gop.key_ptr); + return true; } pub fn addPathPost(man: *Manifest, path: Path) !void { diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index 12746e4498f76b745898559660dfb03c4722c233..8e36cad974a76f24ef6e12f08802995c2b748544 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -10,8 +10,7 @@ const native_endian = builtin.target.cpu.arch.endian(); string_bytes: []u8, steps: []Step, -path_deps_base: []Path.Base, -path_deps_sub: []String, +path_deps: []PathDep, unlazy_deps: []String, system_integrations: []SystemIntegration, available_options: []AvailableOption, @@ -57,7 +56,7 @@ pub const Wip = struct { system_integrations: std.ArrayList(SystemIntegration) = .empty, available_options: std.ArrayList(AvailableOption) = .empty, steps: std.ArrayList(Step) = .empty, - path_deps: std.MultiArrayList(Path) = .empty, + path_deps: std.ArrayList(PathDep) = .empty, search_prefixes: std.ArrayList(String) = .empty, extra: std.ArrayList(u32) = .empty, next_generated_file_index: u32 = 0, @@ -154,7 +153,7 @@ pub const Wip = struct { const header: Header = .{ .string_bytes_len = @intCast(wip.string_bytes.items.len), .steps_len = @intCast(wip.steps.items.len), - .path_deps_len = @intCast(wip.path_deps.len), + .path_deps_len = @intCast(wip.path_deps.items.len), .unlazy_deps_len = @intCast(wip.unlazy_deps.items.len), .system_integrations_len = @intCast(wip.system_integrations.items.len), .available_options_len = @intCast(wip.available_options.items.len), @@ -171,8 +170,7 @@ pub const Wip = struct { @ptrCast(&header), wip.string_bytes.items, @ptrCast(wip.steps.items), - @ptrCast(wip.path_deps.items(.base)), - @ptrCast(wip.path_deps.items(.sub)), + @ptrCast(wip.path_deps.items), @ptrCast(wip.unlazy_deps.items), @ptrCast(wip.system_integrations.items), @ptrCast(wip.available_options.items), @@ -1551,9 +1549,22 @@ pub const LazyPath = union(@This().Tag) { pub const Flags = packed struct(u32) { tag: Tag = .relative, - base: Path.Base, + base: Base, _: u16 = 0, }; + + pub const Base = enum(u8) { + cwd, + local_cache, + global_cache, + build_root, + zig_exe, + zig_lib, + install_prefix, + install_lib, + install_bin, + install_include, + }; }; }; @@ -1597,6 +1608,26 @@ pub const Package = struct { return package.dep_prefix.slice(c); } }; + + pub const OptionalIndex = enum(u32) { + none = max_u32 - 1, + root = max_u32, + _, + + pub fn init(i: Index) OptionalIndex { + const result: OptionalIndex = @enumFromInt(@intFromEnum(i)); + assert(result != .none); + return result; + } + + pub fn unwrap(this: @This()) ?Index { + return switch (this) { + .none => null, + .root => .root, + _ => @enumFromInt(@intFromEnum(this)), + }; + } + }; }; pub const Module = struct { @@ -1833,24 +1864,20 @@ pub const OptionalStringList = enum(u32) { } }; -pub const Path = extern struct { - base: Base, +pub const PathDep = extern struct { + flags: Flags, sub: String, + pkg: Package.OptionalIndex, - pub const Base = enum(u8) { - cwd, - local_cache, - global_cache, - build_root, - zig_exe, - zig_lib, - install_prefix, - install_lib, - install_bin, - install_include, + pub const Flags = packed struct(u32) { + mode: Mode, + base: LazyPath.Relative.Base, + _: u16 = 0, }; - pub fn toCachePath(path: Path, c: *const Configuration, arena: Allocator) std.Build.Cache.Path { + pub const Mode = enum(u8) { directory, contents, metadata }; + + pub fn toCachePath(path: PathDep, c: *const Configuration, arena: Allocator) std.Build.Cache.Path { _ = c; _ = arena; _ = path; @@ -3430,8 +3457,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration { const result: Configuration = .{ .string_bytes = try arena.alloc(u8, header.string_bytes_len), .steps = try arena.alloc(Step, header.steps_len), - .path_deps_sub = try arena.alloc(String, header.path_deps_len), - .path_deps_base = try arena.alloc(Path.Base, header.path_deps_len), + .path_deps = try arena.alloc(PathDep, header.path_deps_len), .unlazy_deps = try arena.alloc(String, header.unlazy_deps_len), .system_integrations = try arena.alloc(SystemIntegration, header.system_integrations_len), .available_options = try arena.alloc(AvailableOption, header.available_options_len), @@ -3444,8 +3470,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration { var vecs = [_][]u8{ result.string_bytes, @ptrCast(result.steps), - @ptrCast(result.path_deps_base), - @ptrCast(result.path_deps_sub), + @ptrCast(result.path_deps), @ptrCast(result.unlazy_deps), @ptrCast(result.system_integrations), @ptrCast(result.available_options), diff --git a/src/main.zig b/src/main.zig index 4dd56dd3b0623c7836ce04604c9066b562a223a4..84b6553ff64750279537a242729f6ecf9c807639 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5454,6 +5454,9 @@ fn cmdBuild( } defer Fork.deinitList(forks.items); + var file_system_inputs: std.ArrayList(u8) = .empty; + defer file_system_inputs.deinit(gpa); + // This loop is re-evaluated when the build script exits with an indication that it // could not continue due to missing lazy dependencies. const configuration_path: Path, const poisoned: bool = cp: while (true) { @@ -5679,6 +5682,7 @@ fn cmdBuild( try root_mod.deps.put(arena, "@build", build_mod); + file_system_inputs.clearRetainingCapacity(); var create_diag: Compilation.CreateDiagnostic = undefined; const comp = Compilation.create(gpa, arena, io, &create_diag, .{ .libc_installation = libc_installation, @@ -5702,6 +5706,7 @@ fn cmdBuild( .reference_trace = reference_trace, .debug_compile_errors = debug_compile_errors, .environ_map = environ_map, + .file_system_inputs = &file_system_inputs, }) catch |err| switch (err) { error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}), else => |e| fatal("failed to create compilation: {t}", .{e}), @@ -5764,10 +5769,10 @@ fn cmdBuild( .argv = configure_argv.items, .stdout = .{ .file = config_tmp_file }, .progress_node = child_node, - }) catch |err| fatal("failed to spawn configure script {s}: {t}", .{ configure_argv.items[0], err }); + }) catch |err| fatal("failed to spawn configure script {q}: {t}", .{ configure_argv.items[0], err }); defer child.kill(io); break :term child.wait(io) catch |err| - fatal("failed to wait configure script {s}: {t}", .{ configure_argv.items[0], err }); + fatal("failed to wait configure script {q}: {t}", .{ configure_argv.items[0], err }); }; if (!term.success()) { // Failure to produce the configuration file. @@ -5816,6 +5821,21 @@ fn cmdBuild( try config_man.addPathPost(conf_path.toCachePath(&configuration, arena)); } + // We need to add to the configuration cache the source files of + // configurer itself, so that the maker process can watch the file system + // for those changes and restart itself. By doing this, we make it + // possible to bypass creating a Compilation for configurer on + // Configuration cache hit. + { + var it = mem.splitScalar(u8, file_system_inputs.items, 0); + while (it.next()) |input| { + _ = try config_man.addPrefixedPathPost(.{ + .prefix = input[0], + .sub_path = input[1..], + }); + } + } + // If it is poisoned, there is no point in moving it to cached // location. Just leave it in the tmp directory. if (configuration.poisoned) { @@ -6247,14 +6267,15 @@ fn jitCmdInner( child_argv.appendSliceAssumeCapacity(args); + if (EnvVar.ZIG_DEBUG_CMD.isSet(environ_map)) { + const cmd = try std.mem.join(arena, " ", child_argv.items); + std.debug.print("{s}\n", .{cmd}); + } + if (process.can_replace and options.capture == null) { - if (EnvVar.ZIG_DEBUG_CMD.isSet(environ_map)) { - const cmd = try std.mem.join(arena, " ", child_argv.items); - std.debug.print("{s}\n", .{cmd}); - } const err = process.replace(io, .{ .argv = child_argv.items, .environ_map = environ_map }); const cmd = try std.mem.join(arena, " ", child_argv.items); - fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd }); + fatal("the following command failed to execve with {t}:\n{s}", .{ err, cmd }); } if (!process.can_spawn) { @@ -6264,7 +6285,7 @@ fn jitCmdInner( }); } - switch (t: { + const term = t: { _ = try io.lockStderr(&.{}, .no_color); defer io.unlockStderr(); @@ -6282,28 +6303,13 @@ fn jitCmdInner( } break :t try child.wait(io); - }) { - .exited => |code| { - if (code == 0) { - if (options.capture != null) return; - return cleanExit(io); - } - const cmd = try std.mem.join(arena, " ", child_argv.items); - fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); - }, - .signal => |sig| { - const cmd = try std.mem.join(arena, " ", child_argv.items); - fatal("the following build command terminated with signal {t}:\n{s}", .{ sig, cmd }); - }, - .stopped => |sig| { - const cmd = try std.mem.join(arena, " ", child_argv.items); - fatal("the following build command stopped with signal {t}:\n{s}", .{ sig, cmd }); - }, - .unknown => { - const cmd = try std.mem.join(arena, " ", child_argv.items); - fatal("the following build command crashed:\n{s}", .{cmd}); - }, + }; + if (term.success()) { + if (options.capture != null) return; + return cleanExit(io); } + const cmd = try std.mem.join(arena, " ", child_argv.items); + fatal("the following build command {f}:\n{s}", .{ term, cmd }); } const info_zen = diff --git a/test/src/Cases.zig b/test/src/Cases.zig index f321d15851e117539f78f38681bd20cb8e06b1c4..af5dcfdddd6fd1c7671f88ff812637bfbcdfd794 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -316,20 +316,19 @@ pub fn addCompile( /// Each file should include a test manifest as a contiguous block of comments at /// the end of the file. The first line should be the test type, followed by a set of /// key-value config values, followed by a blank line, then the expected output. -pub fn addFromDir(ctx: *Cases, dir: Io.Dir, b: *std.Build) void { +pub fn addFromDir(ctx: *Cases, dir: Io.Dir, path_from_root: []const u8, b: *std.Build) void { var current_file: []const u8 = "none"; - ctx.addFromDirInner(dir, ¤t_file, b) catch |err| { - std.debug.panicExtra( - @returnAddress(), - "test harness failed to process file '{s}': {s}\n", - .{ current_file, @errorName(err) }, - ); + ctx.addFromDirInner(dir, path_from_root, ¤t_file, b) catch |err| { + std.debug.panicExtra(@returnAddress(), "test harness failed to process file {q}: {t}\n", .{ + current_file, err, + }); }; } fn addFromDirInner( ctx: *Cases, iterable_dir: Io.Dir, + path_from_root: []const u8, /// This is kept up to date with the currently being processed file so /// that if any errors occur the caller knows it happened during this file. current_file: *[]const u8, @@ -340,11 +339,19 @@ fn addFromDirInner( var filenames: ArrayList([]const u8) = .empty; while (try it.next(io)) |entry| { - if (entry.kind != .file) continue; - // Ignore stuff such as .swp files if (!knownFileExtension(entry.basename)) continue; - try filenames.append(ctx.arena, try ctx.arena.dupe(u8, entry.path)); + + switch (entry.kind) { + .file => { + b.dependOnFileContents(b.path(b.pathJoin(&.{ path_from_root, entry.path }))); + try filenames.append(ctx.arena, try ctx.arena.dupe(u8, entry.path)); + }, + .directory => { + b.dependOnDirectory(b.path(b.pathJoin(&.{ path_from_root, entry.path }))); + }, + else => continue, + } } for (filenames.items) |filename| { diff --git a/test/tests.zig b/test/tests.zig index 62d54e981d0bef3093ee02d5be8c0de8e421a52a..78f397d3f4e0c47053d98c5a51049a8dd7507fa9 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -3258,14 +3258,12 @@ pub fn addCases( var cases = @import("src/Cases.zig").init(gpa, arena, io); - // Ensure changes to these files get picked up - // https://codeberg.org/ziglang/zig/issues/35473 - b.graph.poisonCache(); + b.dependOnDirectory(b.path("test/cases")); var dir = try b.root.openDir(io, "test/cases", .{ .iterate = true }); defer dir.close(io); - cases.addFromDir(dir, b); + cases.addFromDir(dir, "test/cases", b); try @import("cases.zig").addCases(&cases, build_options, b); cases.lowerToBuildSteps( @@ -3320,22 +3318,28 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons }), }); - // Ensure changes to these files get picked up - // https://codeberg.org/ziglang/zig/issues/35473 - b.graph.poisonCache(); + b.dependOnDirectory(b.path("test/incremental")); var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true }); defer dir.close(io); var it = try dir.walk(b.graph.arena); while (try it.next(io)) |entry| { - if (entry.kind != .file) continue; if (std.mem.endsWith(u8, entry.basename, ".swp")) continue; for (test_filters) |test_filter| { if (std.mem.indexOf(u8, entry.path, test_filter)) |_| break; } else if (test_filters.len > 0) continue; + switch (entry.kind) { + .file => {}, + .directory => { + b.dependOnDirectory(b.path(b.pathJoin(&.{ "test", "incremental", entry.path }))); + }, + else => continue, + } + b.dependOnFileContents(b.path(b.pathJoin(&.{ "test", "incremental", entry.path }))); + for (incremental_targets) |target_str| { const run = b.addRunArtifact(incr_check); run.setName(b.fmt("incr-check {s} '{s}'", .{ target_str, entry.basename }));