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 {...@@ -264,9 +264,8 @@ pub fn build(b: *std.Build) !void {
264 std.process.exit(1);264 std.process.exit(1);
265 }265 }
266266
267 // Ensure git version changes get picked up267 // Ensure git version changes get picked up.
268 // https://codeberg.org/ziglang/zig/issues/35473268 b.dependOnFileContents(b.graph.path(.build_root, ".git/HEAD"));
269 b.graph.poisonCache();
270269
271 const version_string = b.fmt("{d}.{d}.{d}", .{ zig_version.major, zig_version.minor, zig_version.patch });270 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 {...@@ -133,7 +133,7 @@ pub fn main(init: process.Init.Minimal) !void {
133 .off => .no_color,133 .off => .no_color,
134 };134 };
135135
136 try builder.runBuild(root);136 builder.runBuild(root);
137137
138 if (builder.validateUserInputDidItFail()) {138 if (builder.validateUserInputDidItFail()) {
139 fatal(" access the help menu with 'zig build -h'", .{});139 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 {...@@ -632,6 +632,37 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
632632
633 var s: Serialize = .{ .wc = wc, .arena = arena };633 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
635 // Starting from all top-level steps in `b`, traverse the entire step graph666 // Starting from all top-level steps in `b`, traverse the entire step graph
636 // and add all step dependencies implied by module graphs.667 // and add all step dependencies implied by module graphs.
637 const top_level_steps = b.top_level_steps.values();668 const top_level_steps = b.top_level_steps.values();
lib/std/Build.zig+111-10
...@@ -64,6 +64,11 @@ pkg_hash: []const u8,...@@ -64,6 +64,11 @@ pkg_hash: []const u8,
64/// A mapping from dependency names to package hashes.64/// A mapping from dependency names to package hashes.
65available_deps: AvailableDeps,65available_deps: AvailableDeps,
6666
67pub const ConfigureDependency = struct {
68 lazy_path: LazyPath,
69 mode: std.Build.Configuration.PathDep.Mode,
70};
71
67pub const ReleaseMode = enum {72pub const ReleaseMode = enum {
68 off,73 off,
69 any,74 any,
...@@ -102,6 +107,12 @@ pub const Graph = struct {...@@ -102,6 +107,12 @@ pub const Graph = struct {
102 /// Observing this data causes cache poisoning. See `CachePoison`.107 /// Observing this data causes cache poisoning. See `CachePoison`.
103 search_prefixes: std.ArrayList([]const u8) = .empty,108 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
105 /// If the cache is poisoned means that the **configure logic** had side116 /// If the cache is poisoned means that the **configure logic** had side
106 /// effects, or otherwise did something that could not be tracked by the117 /// effects, or otherwise did something that could not be tracked by the
107 /// cache system.118 /// cache system.
...@@ -165,7 +176,7 @@ pub const Graph = struct {...@@ -165,7 +176,7 @@ pub const Graph = struct {
165176
166 /// A path whose components and contents are known at some point during177 /// A path whose components and contents are known at some point during
167 /// `Step` resolution, relative to the provided base directory.178 /// `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 {
169 return .{ .relative = .{180 return .{ .relative = .{
170 .base = base,181 .base = base,
171 .sub_path = @This().dupePath(graph, sub_path),182 .sub_path = @This().dupePath(graph, sub_path),
...@@ -204,6 +215,9 @@ pub const Graph = struct {...@@ -204,6 +215,9 @@ pub const Graph = struct {
204 /// did something that could not be tracked by the cache system.215 /// did something that could not be tracked by the cache system.
205 ///216 ///
206 /// See `CachePoison` documentation for more details.217 /// See `CachePoison` documentation for more details.
218 ///
219 /// As an alternative to calling this function, consider these APIs instead:
220 /// * `dependOnFileContents`
207 pub fn poisonCache(graph: *Graph) void {221 pub fn poisonCache(graph: *Graph) void {
208 switch (graph.cache_poison) {222 switch (graph.cache_poison) {
209 .pure => graph.cache_poison = .poisoned,223 .pure => graph.cache_poison = .poisoned,
...@@ -2318,14 +2332,13 @@ fn dependencyInner(...@@ -2318,14 +2332,13 @@ fn dependencyInner(
2318 .root_dir = .{2332 .root_dir = .{
2319 .path = build_root_string,2333 .path = build_root_string,
2320 .handle = Io.Dir.cwd().openDir(io, build_root_string, .{}) catch |err|2334 .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 }),
2322 },2336 },
2323 };2337 };
23242338
2325 const sub_builder = b.createChild(name, dep_root, pkg_hash, pkg_deps, user_input_options) catch2339 const sub_builder = b.createChild(name, dep_root, pkg_hash, pkg_deps, user_input_options) catch @panic("OOM");
2326 @panic("unhandled error");
2327 if (build_zig) |bz| {2340 if (build_zig) |bz| {
2328 sub_builder.runBuild(bz) catch @panic("unhandled error");2341 sub_builder.runBuild(bz);
23292342
2330 if (sub_builder.validateUserInputDidItFail()) {2343 if (sub_builder.validateUserInputDidItFail()) {
2331 std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });2344 std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
...@@ -2343,11 +2356,10 @@ fn dependencyInner(...@@ -2343,11 +2356,10 @@ fn dependencyInner(
2343}2356}
23442357
2345/// Build system implementation detail.2358/// Build system implementation detail.
2346pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void {2359pub fn runBuild(b: *Build, build_zig: anytype) void {
2347 switch (@typeInfo(@typeInfo(@TypeOf(build_zig.build)).@"fn".return_type.?)) {2360 switch (@typeInfo(@typeInfo(@TypeOf(build_zig.build)).@"fn".return_type.?)) {
2348 .void => build_zig.build(b),2361 .error_union => return build_zig.build(b) catch unreachable,
2349 .error_union => try build_zig.build(b),2362 else => return build_zig.build(b),
2350 else => @compileError("expected return type of build to be 'void' or '!void'"),
2351 }2363 }
2352}2364}
23532365
...@@ -2411,7 +2423,7 @@ pub const LazyPath = union(enum) {...@@ -2411,7 +2423,7 @@ pub const LazyPath = union(enum) {
2411 },2423 },
24122424
2413 relative: struct {2425 relative: struct {
2414 base: Configuration.Path.Base,2426 base: Configuration.LazyPath.Relative.Base,
2415 sub_path: []const u8 = "",2427 sub_path: []const u8 = "",
24162428
2417 pub fn eql(a: @This(), b: @This()) bool {2429 pub fn eql(a: @This(), b: @This()) bool {
...@@ -2717,6 +2729,95 @@ pub fn systemIntegrationOption(...@@ -2717,6 +2729,95 @@ pub fn systemIntegrationOption(
2717 }2729 }
2718}2730}
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
2720test {2821test {
2721 _ = Cache;2822 _ = Cache;
2722 _ = Configuration;2823 _ = Configuration;
lib/std/Build/Cache.zig+16-12
...@@ -57,7 +57,7 @@ pub fn prefixes(cache: *const Cache) []const Directory {...@@ -57,7 +57,7 @@ pub fn prefixes(cache: *const Cache) []const Directory {
57 return cache.prefixes_buffer[0..cache.prefixes_len];57 return cache.prefixes_buffer[0..cache.prefixes_len];
58}58}
5959
60const PrefixedPath = struct {60pub const PrefixedPath = struct {
61 prefix: u8,61 prefix: u8,
62 sub_path: []const u8,62 sub_path: []const u8,
6363
...@@ -1000,18 +1000,21 @@ pub const Manifest = struct {...@@ -1000,18 +1000,21 @@ pub const Manifest = struct {
1000 /// other files will need to be recompiled if the imported file is changed.1000 /// other files will need to be recompiled if the imported file is changed.
1001 pub fn addFilePost(self: *Manifest, file_path: []const u8) !void {1001 pub fn addFilePost(self: *Manifest, file_path: []const u8) !void {
1002 assert(self.manifest_file != null);1002 assert(self.manifest_file != null);
1003
1004 const gpa = self.cache.gpa;1003 const gpa = self.cache.gpa;
1005 const prefixed_path = try self.cache.findPrefix(file_path);1004 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{});1010 pub fn addPrefixedPathPost(man: *Manifest, prefixed_path: PrefixedPath) !bool {
1009 errdefer _ = self.files.pop();1011 assert(man.manifest_file != null);
1012 const gpa = man.cache.gpa;
10101013
1011 if (gop.found_existing) {1014 const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});
1012 gpa.free(prefixed_path.sub_path);1015 errdefer _ = man.files.pop();
1013 return;1016
1014 }1017 if (gop.found_existing) return false;
10151018
1016 gop.key_ptr.* = .{1019 gop.key_ptr.* = .{
1017 .prefixed_path = prefixed_path,1020 .prefixed_path = prefixed_path,
...@@ -1022,10 +1025,11 @@ pub const Manifest = struct {...@@ -1022,10 +1025,11 @@ pub const Manifest = struct {
1022 .contents = null,1025 .contents = null,
1023 };1026 };
10241027
1025 self.files.lockPointers();1028 man.files.lockPointers();
1026 defer self.files.unlockPointers();1029 defer man.files.unlockPointers();
10271030
1028 try self.populateFileHash(gop.key_ptr);1031 try man.populateFileHash(gop.key_ptr);
1032 return true;
1029 }1033 }
10301034
1031 pub fn addPathPost(man: *Manifest, path: Path) !void {1035 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();...@@ -10,8 +10,7 @@ const native_endian = builtin.target.cpu.arch.endian();
1010
11string_bytes: []u8,11string_bytes: []u8,
12steps: []Step,12steps: []Step,
13path_deps_base: []Path.Base,13path_deps: []PathDep,
14path_deps_sub: []String,
15unlazy_deps: []String,14unlazy_deps: []String,
16system_integrations: []SystemIntegration,15system_integrations: []SystemIntegration,
17available_options: []AvailableOption,16available_options: []AvailableOption,
...@@ -57,7 +56,7 @@ pub const Wip = struct {...@@ -57,7 +56,7 @@ pub const Wip = struct {
57 system_integrations: std.ArrayList(SystemIntegration) = .empty,56 system_integrations: std.ArrayList(SystemIntegration) = .empty,
58 available_options: std.ArrayList(AvailableOption) = .empty,57 available_options: std.ArrayList(AvailableOption) = .empty,
59 steps: std.ArrayList(Step) = .empty,58 steps: std.ArrayList(Step) = .empty,
60 path_deps: std.MultiArrayList(Path) = .empty,59 path_deps: std.ArrayList(PathDep) = .empty,
61 search_prefixes: std.ArrayList(String) = .empty,60 search_prefixes: std.ArrayList(String) = .empty,
62 extra: std.ArrayList(u32) = .empty,61 extra: std.ArrayList(u32) = .empty,
63 next_generated_file_index: u32 = 0,62 next_generated_file_index: u32 = 0,
...@@ -154,7 +153,7 @@ pub const Wip = struct {...@@ -154,7 +153,7 @@ pub const Wip = struct {
154 const header: Header = .{153 const header: Header = .{
155 .string_bytes_len = @intCast(wip.string_bytes.items.len),154 .string_bytes_len = @intCast(wip.string_bytes.items.len),
156 .steps_len = @intCast(wip.steps.items.len),155 .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),
158 .unlazy_deps_len = @intCast(wip.unlazy_deps.items.len),157 .unlazy_deps_len = @intCast(wip.unlazy_deps.items.len),
159 .system_integrations_len = @intCast(wip.system_integrations.items.len),158 .system_integrations_len = @intCast(wip.system_integrations.items.len),
160 .available_options_len = @intCast(wip.available_options.items.len),159 .available_options_len = @intCast(wip.available_options.items.len),
...@@ -171,8 +170,7 @@ pub const Wip = struct {...@@ -171,8 +170,7 @@ pub const Wip = struct {
171 @ptrCast(&header),170 @ptrCast(&header),
172 wip.string_bytes.items,171 wip.string_bytes.items,
173 @ptrCast(wip.steps.items),172 @ptrCast(wip.steps.items),
174 @ptrCast(wip.path_deps.items(.base)),173 @ptrCast(wip.path_deps.items),
175 @ptrCast(wip.path_deps.items(.sub)),
176 @ptrCast(wip.unlazy_deps.items),174 @ptrCast(wip.unlazy_deps.items),
177 @ptrCast(wip.system_integrations.items),175 @ptrCast(wip.system_integrations.items),
178 @ptrCast(wip.available_options.items),176 @ptrCast(wip.available_options.items),
...@@ -1551,9 +1549,22 @@ pub const LazyPath = union(@This().Tag) {...@@ -1551,9 +1549,22 @@ pub const LazyPath = union(@This().Tag) {
15511549
1552 pub const Flags = packed struct(u32) {1550 pub const Flags = packed struct(u32) {
1553 tag: Tag = .relative,1551 tag: Tag = .relative,
1554 base: Path.Base,1552 base: Base,
1555 _: u16 = 0,1553 _: u16 = 0,
1556 };1554 };
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 };
1557 };1568 };
1558};1569};
15591570
...@@ -1597,6 +1608,26 @@ pub const Package = struct {...@@ -1597,6 +1608,26 @@ pub const Package = struct {
1597 return package.dep_prefix.slice(c);1608 return package.dep_prefix.slice(c);
1598 }1609 }
1599 };1610 };
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 };
1600};1631};
16011632
1602pub const Module = struct {1633pub const Module = struct {
...@@ -1833,24 +1864,20 @@ pub const OptionalStringList = enum(u32) {...@@ -1833,24 +1864,20 @@ pub const OptionalStringList = enum(u32) {
1833 }1864 }
1834};1865};
18351866
1836pub const Path = extern struct {1867pub const PathDep = extern struct {
1837 base: Base,1868 flags: Flags,
1838 sub: String,1869 sub: String,
1870 pkg: Package.OptionalIndex,
18391871
1840 pub const Base = enum(u8) {1872 pub const Flags = packed struct(u32) {
1841 cwd,1873 mode: Mode,
1842 local_cache,1874 base: LazyPath.Relative.Base,
1843 global_cache,1875 _: u16 = 0,
1844 build_root,
1845 zig_exe,
1846 zig_lib,
1847 install_prefix,
1848 install_lib,
1849 install_bin,
1850 install_include,
1851 };1876 };
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 {
1854 _ = c;1881 _ = c;
1855 _ = arena;1882 _ = arena;
1856 _ = path;1883 _ = path;
...@@ -3430,8 +3457,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {...@@ -3430,8 +3457,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {
3430 const result: Configuration = .{3457 const result: Configuration = .{
3431 .string_bytes = try arena.alloc(u8, header.string_bytes_len),3458 .string_bytes = try arena.alloc(u8, header.string_bytes_len),
3432 .steps = try arena.alloc(Step, header.steps_len),3459 .steps = try arena.alloc(Step, header.steps_len),
3433 .path_deps_sub = try arena.alloc(String, header.path_deps_len),3460 .path_deps = try arena.alloc(PathDep, header.path_deps_len),
3434 .path_deps_base = try arena.alloc(Path.Base, header.path_deps_len),
3435 .unlazy_deps = try arena.alloc(String, header.unlazy_deps_len),3461 .unlazy_deps = try arena.alloc(String, header.unlazy_deps_len),
3436 .system_integrations = try arena.alloc(SystemIntegration, header.system_integrations_len),3462 .system_integrations = try arena.alloc(SystemIntegration, header.system_integrations_len),
3437 .available_options = try arena.alloc(AvailableOption, header.available_options_len),3463 .available_options = try arena.alloc(AvailableOption, header.available_options_len),
...@@ -3444,8 +3470,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {...@@ -3444,8 +3470,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {
3444 var vecs = [_][]u8{3470 var vecs = [_][]u8{
3445 result.string_bytes,3471 result.string_bytes,
3446 @ptrCast(result.steps),3472 @ptrCast(result.steps),
3447 @ptrCast(result.path_deps_base),3473 @ptrCast(result.path_deps),
3448 @ptrCast(result.path_deps_sub),
3449 @ptrCast(result.unlazy_deps),3474 @ptrCast(result.unlazy_deps),
3450 @ptrCast(result.system_integrations),3475 @ptrCast(result.system_integrations),
3451 @ptrCast(result.available_options),3476 @ptrCast(result.available_options),
src/main.zig+35-29
...@@ -5454,6 +5454,9 @@ fn cmdBuild(...@@ -5454,6 +5454,9 @@ fn cmdBuild(
5454 }5454 }
5455 defer Fork.deinitList(forks.items);5455 defer Fork.deinitList(forks.items);
54565456
5457 var file_system_inputs: std.ArrayList(u8) = .empty;
5458 defer file_system_inputs.deinit(gpa);
5459
5457 // This loop is re-evaluated when the build script exits with an indication that it5460 // This loop is re-evaluated when the build script exits with an indication that it
5458 // could not continue due to missing lazy dependencies.5461 // could not continue due to missing lazy dependencies.
5459 const configuration_path: Path, const poisoned: bool = cp: while (true) {5462 const configuration_path: Path, const poisoned: bool = cp: while (true) {
...@@ -5679,6 +5682,7 @@ fn cmdBuild(...@@ -5679,6 +5682,7 @@ fn cmdBuild(
56795682
5680 try root_mod.deps.put(arena, "@build", build_mod);5683 try root_mod.deps.put(arena, "@build", build_mod);
56815684
5685 file_system_inputs.clearRetainingCapacity();
5682 var create_diag: Compilation.CreateDiagnostic = undefined;5686 var create_diag: Compilation.CreateDiagnostic = undefined;
5683 const comp = Compilation.create(gpa, arena, io, &create_diag, .{5687 const comp = Compilation.create(gpa, arena, io, &create_diag, .{
5684 .libc_installation = libc_installation,5688 .libc_installation = libc_installation,
...@@ -5702,6 +5706,7 @@ fn cmdBuild(...@@ -5702,6 +5706,7 @@ fn cmdBuild(
5702 .reference_trace = reference_trace,5706 .reference_trace = reference_trace,
5703 .debug_compile_errors = debug_compile_errors,5707 .debug_compile_errors = debug_compile_errors,
5704 .environ_map = environ_map,5708 .environ_map = environ_map,
5709 .file_system_inputs = &file_system_inputs,
5705 }) catch |err| switch (err) {5710 }) catch |err| switch (err) {
5706 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),5711 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),
5707 else => |e| fatal("failed to create compilation: {t}", .{e}),5712 else => |e| fatal("failed to create compilation: {t}", .{e}),
...@@ -5764,10 +5769,10 @@ fn cmdBuild(...@@ -5764,10 +5769,10 @@ fn cmdBuild(
5764 .argv = configure_argv.items,5769 .argv = configure_argv.items,
5765 .stdout = .{ .file = config_tmp_file },5770 .stdout = .{ .file = config_tmp_file },
5766 .progress_node = child_node,5771 .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 });
5768 defer child.kill(io);5773 defer child.kill(io);
5769 break :term child.wait(io) catch |err|5774 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 });
5771 };5776 };
5772 if (!term.success()) {5777 if (!term.success()) {
5773 // Failure to produce the configuration file.5778 // Failure to produce the configuration file.
...@@ -5816,6 +5821,21 @@ fn cmdBuild(...@@ -5816,6 +5821,21 @@ fn cmdBuild(
5816 try config_man.addPathPost(conf_path.toCachePath(&configuration, arena));5821 try config_man.addPathPost(conf_path.toCachePath(&configuration, arena));
5817 }5822 }
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
5819 // If it is poisoned, there is no point in moving it to cached5839 // If it is poisoned, there is no point in moving it to cached
5820 // location. Just leave it in the tmp directory.5840 // location. Just leave it in the tmp directory.
5821 if (configuration.poisoned) {5841 if (configuration.poisoned) {
...@@ -6247,14 +6267,15 @@ fn jitCmdInner(...@@ -6247,14 +6267,15 @@ fn jitCmdInner(
62476267
6248 child_argv.appendSliceAssumeCapacity(args);6268 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
6250 if (process.can_replace and options.capture == null) {6275 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 }
6255 const err = process.replace(io, .{ .argv = child_argv.items, .environ_map = environ_map });6276 const err = process.replace(io, .{ .argv = child_argv.items, .environ_map = environ_map });
6256 const cmd = try std.mem.join(arena, " ", child_argv.items);6277 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 });
6258 }6279 }
62596280
6260 if (!process.can_spawn) {6281 if (!process.can_spawn) {
...@@ -6264,7 +6285,7 @@ fn jitCmdInner(...@@ -6264,7 +6285,7 @@ fn jitCmdInner(
6264 });6285 });
6265 }6286 }
62666287
6267 switch (t: {6288 const term = t: {
6268 _ = try io.lockStderr(&.{}, .no_color);6289 _ = try io.lockStderr(&.{}, .no_color);
6269 defer io.unlockStderr();6290 defer io.unlockStderr();
62706291
...@@ -6282,28 +6303,13 @@ fn jitCmdInner(...@@ -6282,28 +6303,13 @@ fn jitCmdInner(
6282 }6303 }
62836304
6284 break :t try child.wait(io);6305 break :t try child.wait(io);
6285 }) {6306 };
6286 .exited => |code| {6307 if (term.success()) {
6287 if (code == 0) {6308 if (options.capture != null) return;
6288 if (options.capture != null) return;6309 return cleanExit(io);
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 }6310 }
6311 const cmd = try std.mem.join(arena, " ", child_argv.items);
6312 fatal("the following build command {f}:\n{s}", .{ term, cmd });
6307}6313}
63086314
6309const info_zen =6315const info_zen =
test/src/Cases.zig+17-10
...@@ -316,20 +316,19 @@ pub fn addCompile(...@@ -316,20 +316,19 @@ pub fn addCompile(
316/// Each file should include a test manifest as a contiguous block of comments at316/// Each file should include a test manifest as a contiguous block of comments at
317/// the end of the file. The first line should be the test type, followed by a set of317/// the end of the file. The first line should be the test type, followed by a set of
318/// key-value config values, followed by a blank line, then the expected output.318/// 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 {
320 var current_file: []const u8 = "none";320 var current_file: []const u8 = "none";
321 ctx.addFromDirInner(dir, &current_file, b) catch |err| {321 ctx.addFromDirInner(dir, path_from_root, &current_file, b) catch |err| {
322 std.debug.panicExtra(322 std.debug.panicExtra(@returnAddress(), "test harness failed to process file {q}: {t}\n", .{
323 @returnAddress(),323 current_file, err,
324 "test harness failed to process file '{s}': {s}\n",324 });
325 .{ current_file, @errorName(err) },
326 );
327 };325 };
328}326}
329327
330fn addFromDirInner(328fn addFromDirInner(
331 ctx: *Cases,329 ctx: *Cases,
332 iterable_dir: Io.Dir,330 iterable_dir: Io.Dir,
331 path_from_root: []const u8,
333 /// This is kept up to date with the currently being processed file so332 /// This is kept up to date with the currently being processed file so
334 /// that if any errors occur the caller knows it happened during this file.333 /// that if any errors occur the caller knows it happened during this file.
335 current_file: *[]const u8,334 current_file: *[]const u8,
...@@ -340,11 +339,19 @@ fn addFromDirInner(...@@ -340,11 +339,19 @@ fn addFromDirInner(
340 var filenames: ArrayList([]const u8) = .empty;339 var filenames: ArrayList([]const u8) = .empty;
341340
342 while (try it.next(io)) |entry| {341 while (try it.next(io)) |entry| {
343 if (entry.kind != .file) continue;
344
345 // Ignore stuff such as .swp files342 // Ignore stuff such as .swp files
346 if (!knownFileExtension(entry.basename)) continue;343 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 }
348 }355 }
349356
350 for (filenames.items) |filename| {357 for (filenames.items) |filename| {
test/tests.zig+12-8
...@@ -3258,14 +3258,12 @@ pub fn addCases(...@@ -3258,14 +3258,12 @@ pub fn addCases(
32583258
3259 var cases = @import("src/Cases.zig").init(gpa, arena, io);3259 var cases = @import("src/Cases.zig").init(gpa, arena, io);
32603260
3261 // Ensure changes to these files get picked up3261 b.dependOnDirectory(b.path("test/cases"));
3262 // https://codeberg.org/ziglang/zig/issues/35473
3263 b.graph.poisonCache();
32643262
3265 var dir = try b.root.openDir(io, "test/cases", .{ .iterate = true });3263 var dir = try b.root.openDir(io, "test/cases", .{ .iterate = true });
3266 defer dir.close(io);3264 defer dir.close(io);
32673265
3268 cases.addFromDir(dir, b);3266 cases.addFromDir(dir, "test/cases", b);
3269 try @import("cases.zig").addCases(&cases, build_options, b);3267 try @import("cases.zig").addCases(&cases, build_options, b);
32703268
3271 cases.lowerToBuildSteps(3269 cases.lowerToBuildSteps(
...@@ -3320,22 +3318,28 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons...@@ -3320,22 +3318,28 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons
3320 }),3318 }),
3321 });3319 });
33223320
3323 // Ensure changes to these files get picked up3321 b.dependOnDirectory(b.path("test/incremental"));
3324 // https://codeberg.org/ziglang/zig/issues/35473
3325 b.graph.poisonCache();
33263322
3327 var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true });3323 var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true });
3328 defer dir.close(io);3324 defer dir.close(io);
33293325
3330 var it = try dir.walk(b.graph.arena);3326 var it = try dir.walk(b.graph.arena);
3331 while (try it.next(io)) |entry| {3327 while (try it.next(io)) |entry| {
3332 if (entry.kind != .file) continue;
3333 if (std.mem.endsWith(u8, entry.basename, ".swp")) continue;3328 if (std.mem.endsWith(u8, entry.basename, ".swp")) continue;
33343329
3335 for (test_filters) |test_filter| {3330 for (test_filters) |test_filter| {
3336 if (std.mem.indexOf(u8, entry.path, test_filter)) |_| break;3331 if (std.mem.indexOf(u8, entry.path, test_filter)) |_| break;
3337 } else if (test_filters.len > 0) continue;3332 } 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
3339 for (incremental_targets) |target_str| {3343 for (incremental_targets) |target_str| {
3340 const run = b.addRunArtifact(incr_check);3344 const run = b.addRunArtifact(incr_check);
3341 run.setName(b.fmt("incr-check {s} '{s}'", .{ target_str, entry.basename }));3345 run.setName(b.fmt("incr-check {s} '{s}'", .{ target_str, entry.basename }));