authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-21 22:00:29-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 18:54:36-07:00
log54bb8d2dd9369f5e5b43b4773878edc32fd3851e
tree00fba145dc090cc126b0b98c7b74f73cab8183af
parentdf8aaad05852b18e5a47aa09d5acdfd312583d1c

implement the concept of configure cache poisoning


6 files changed, 271 insertions(+), 126 deletions(-)

lib/compiler/Maker.zig+24-3
...@@ -425,6 +425,10 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -425,6 +425,10 @@ pub fn main(init: process.Init.Minimal) !void {
425 break :c Configuration.loadFile(arena, io, file) catch |err|425 break :c Configuration.loadFile(arena, io, file) catch |err|
426 fatal("failed to load configuration file {s}: {t}", .{ configure_path, err });426 fatal("failed to load configuration file {s}: {t}", .{ configure_path, err });
427 };427 };
428 // Technically if the configuration is marked as poisoned, we could
429 // already delete the file now, but we leave it around in case the
430 // maker process fails or crashes and it's helpful to be able to repeat
431 // execution of the command line or otherwise inspect the configuration file.
428 const c = &configuration;432 const c = &configuration;
429 var top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index) = .empty;433 var top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index) = .empty;
430 for (configuration.steps, 0..) |*conf_step, step_index_usize| {434 for (configuration.steps, 0..) |*conf_step, step_index_usize| {
...@@ -445,6 +449,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -445,6 +449,7 @@ pub fn main(init: process.Init.Minimal) !void {
445 break :sc .{449 break :sc .{
446 .configuration = configuration,450 .configuration = configuration,
447 .top_level_steps = top_level_steps,451 .top_level_steps = top_level_steps,
452 .path = configure_path,
448 };453 };
449 };454 };
450455
...@@ -455,7 +460,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -455,7 +460,7 @@ pub fn main(init: process.Init.Minimal) !void {
455 else => |e| return e,460 else => |e| return e,
456 };461 };
457 w.flush() catch return stdout_writer_allocation.err.?;462 w.flush() catch return stdout_writer_allocation.err.?;
458 return;463 return cleanExit(io, &scanned_config);
459 } else if (steps_menu) {464 } else if (steps_menu) {
460 var w = initStdoutWriter(io);465 var w = initStdoutWriter(io);
461 scanned_config.printSteps(&graph, w) catch |err| switch (err) {466 scanned_config.printSteps(&graph, w) catch |err| switch (err) {
...@@ -463,12 +468,12 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -463,12 +468,12 @@ pub fn main(init: process.Init.Minimal) !void {
463 else => |e| return e,468 else => |e| return e,
464 };469 };
465 w.flush() catch return stdout_writer_allocation.err.?;470 w.flush() catch return stdout_writer_allocation.err.?;
466 return;471 return cleanExit(io, &scanned_config);
467 } else if (print_configuration) {472 } else if (print_configuration) {
468 var w = initStdoutWriter(io);473 var w = initStdoutWriter(io);
469 scanned_config.print(w) catch return stdout_writer_allocation.err.?;474 scanned_config.print(w) catch return stdout_writer_allocation.err.?;
470 w.flush() catch return stdout_writer_allocation.err.?;475 w.flush() catch return stdout_writer_allocation.err.?;
471 return;476 return cleanExit(io, &scanned_config);
472 }477 }
473478
474 if (webui_listen != null) {479 if (webui_listen != null) {
...@@ -1000,6 +1005,8 @@ fn makeStepNames(...@@ -1000,6 +1005,8 @@ fn makeStepNames(
1000 if (maker.error_style.verboseContext()) break :code 1; // failure; print build command1005 if (maker.error_style.verboseContext()) break :code 1; // failure; print build command
1001 break :code 2; // failure; do not print build command1006 break :code 2; // failure; do not print build command
1002 };1007 };
1008 if (code == 0) removePoisonedConfiguration(io, maker.scanned_config);
1009 cleanup_task.await(io); // There is a defer above but an exit below.
1003 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};1010 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
1004 process.exit(code);1011 process.exit(code);
1005}1012}
...@@ -2000,3 +2007,17 @@ fn installSymLinksInner(...@@ -2000,3 +2007,17 @@ fn installSymLinksInner(
2000 return step.fail(maker, "unable to symlink {f} -> {s}: {t}", .{ name_only_path, filename_major_only, err });2007 return step.fail(maker, "unable to symlink {f} -> {s}: {t}", .{ name_only_path, filename_major_only, err });
2001 };2008 };
2002}2009}
2010
2011fn cleanExit(io: Io, scanned_config: *const ScannedConfig) void {
2012 removePoisonedConfiguration(io, scanned_config);
2013 return process.cleanExit(io);
2014}
2015
2016fn removePoisonedConfiguration(io: Io, scanned_config: *const ScannedConfig) void {
2017 if (scanned_config.configuration.poisoned) {
2018 // This configuration file was good for only 1 invocation of the maker
2019 // process. Delete it to save space on disk.
2020 Io.Dir.cwd().deleteFile(io, scanned_config.path) catch |err|
2021 log.warn("failed deleting poisoned configuration file {s}: {t}", .{ scanned_config.path, err });
2022 }
2023}
lib/compiler/Maker/ScannedConfig.zig+6
...@@ -9,6 +9,7 @@ const Graph = @import("Graph.zig");...@@ -9,6 +9,7 @@ const Graph = @import("Graph.zig");
99
10configuration: Configuration,10configuration: Configuration,
11top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index),11top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index),
12path: []const u8,
1213
13pub fn print(sc: *const ScannedConfig, w: *Writer) Writer.Error!void {14pub fn print(sc: *const ScannedConfig, w: *Writer) Writer.Error!void {
14 std.log.err("TODO also print paths", .{});15 std.log.err("TODO also print paths", .{});
...@@ -343,6 +344,11 @@ pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {...@@ -343,6 +344,11 @@ pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {
343 \\ --zig-lib-dir [arg] Override path to Zig lib directory344 \\ --zig-lib-dir [arg] Override path to Zig lib directory
344 \\ --build-runner [file] Override path to build runner345 \\ --build-runner [file] Override path to build runner
345 \\ --seed [integer] For shuffling dependency traversal order (default: random)346 \\ --seed [integer] For shuffling dependency traversal order (default: random)
347 \\ --cache-poison[=mode] Override configuration caching behavior
348 \\ pure (default) Avoid false positive cache hits
349 \\ poisoned Don't cache the configuration
350 \\ disallowed Panics when cache would be poisoned
351 \\ ignored A little poison never hurt anybody
346 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries352 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries
347 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)353 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)
348 \\ sha1, tree 20-byte cryptographic hash (ELF, WASM)354 \\ sha1, tree 20-byte cryptographic hash (ELF, WASM)
lib/compiler/configurer.zig+7
...@@ -114,6 +114,9 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -114,6 +114,9 @@ pub fn main(init: process.Init.Minimal) !void {
114 graph.system_package_mode = true;114 graph.system_package_mode = true;
115 } else if (mem.eql(u8, arg, "--verbose")) {115 } else if (mem.eql(u8, arg, "--verbose")) {
116 graph.verbose = true;116 graph.verbose = true;
117 } else if (mem.cutPrefix(u8, arg, "--cache-poison=")) |rest| {
118 graph.cache_poison = std.meta.stringToEnum(std.Build.Graph.CachePoison, rest) orelse
119 fatalWithHint("expected --cache-poison=[pure|poisoned|disallowed|ignored]; found: {s}", .{arg});
117 } else {120 } else {
118 fatalWithHint("unrecognized argument: {s}", .{arg});121 fatalWithHint("unrecognized argument: {s}", .{arg});
119 }122 }
...@@ -1222,6 +1225,10 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {...@@ -1222,6 +1225,10 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
1222 try wc.write(writer, .{1225 try wc.write(writer, .{
1223 .default_step = s.stepIndex(b.default_step),1226 .default_step = s.stepIndex(b.default_step),
1224 .generated_files_len = @intCast(graph.generated_files.items.len),1227 .generated_files_len = @intCast(graph.generated_files.items.len),
1228 .poisoned = switch (graph.cache_poison) {
1229 .pure, .disallowed, .ignored => false,
1230 .poisoned => true,
1231 },
1225 });1232 });
1226}1233}
12271234
lib/std/Build.zig+107-7
...@@ -98,6 +98,35 @@ pub const Graph = struct {...@@ -98,6 +98,35 @@ pub const Graph = struct {
98 generated_files: std.ArrayList(*Step),98 generated_files: std.ArrayList(*Step),
99 wip_configuration: Configuration.Wip,99 wip_configuration: Configuration.Wip,
100100
101 cache_poison: CachePoison = .pure,
102
103 /// If the cache is poisoned means that the **configure logic** had side
104 /// effects, or otherwise did something that could not be tracked by the
105 /// cache system.
106 ///
107 /// This is not to be confused with whether individual steps may have side
108 /// effects when being evaluated; it has to do with the logic inside build.zig
109 /// itself. For example, a `Run` step that prints "hello world" has side
110 /// effects *at make time* and therefore does not warrant setting this flag,
111 /// while checking for the existence of `scdoc` *at configure time* in order to
112 /// choose the default value for a configuration option does.
113 ///
114 /// Keeping the cache pure will make `zig build` faster, bypassing the
115 /// configurer process when identical configuration would be generated.
116 ///
117 /// When the cache is poisoned, the maker process will delete the build
118 /// configuration file upon ingesting it since it cannot be reused.
119 pub const CachePoison = enum {
120 pure,
121 poisoned,
122 /// Indicates the user would like to see a stack trace if the cache
123 /// would become poisoned.
124 disallowed,
125 /// Indicates the user would like to ignore the cache being poisoned
126 /// and cache anyway, opting into cache hits on stale configuration.
127 ignored,
128 };
129
101 pub fn addGeneratedFile(graph: *Graph, owner: *Step) Configuration.GeneratedFileIndex {130 pub fn addGeneratedFile(graph: *Graph, owner: *Step) Configuration.GeneratedFileIndex {
102 graph.generated_files.append(graph.arena, owner) catch @panic("OOM");131 graph.generated_files.append(graph.arena, owner) catch @panic("OOM");
103 return @enumFromInt(graph.generated_files.items.len - 1);132 return @enumFromInt(graph.generated_files.items.len - 1);
...@@ -169,6 +198,19 @@ pub const Graph = struct {...@@ -169,6 +198,19 @@ pub const Graph = struct {
169 const wc = &graph.wip_configuration;198 const wc = &graph.wip_configuration;
170 return wc.addString(bytes) catch @panic("OOM");199 return wc.addString(bytes) catch @panic("OOM");
171 }200 }
201
202 /// Indicates that the **configure logic** had side effects, or otherwise
203 /// did something that could not be tracked by the cache system.
204 ///
205 /// See `CachePoison` documentation for more details.
206 pub fn poisonCache(graph: *Graph) void {
207 switch (graph.cache_poison) {
208 .pure => graph.cache_poison = .poisoned,
209 .poisoned => return,
210 .disallowed => @panic("cache poisoned"),
211 .ignored => log.warn("ignoring cache poisoning", .{}),
212 }
213 }
172};214};
173215
174const AvailableDeps = []const struct { []const u8, []const u8 };216const AvailableDeps = []const struct { []const u8, []const u8 };
...@@ -798,11 +840,23 @@ pub fn createModule(b: *Build, options: Module.CreateOptions) *Module {...@@ -798,11 +840,23 @@ pub fn createModule(b: *Build, options: Module.CreateOptions) *Module {
798 return Module.create(b, options);840 return Module.create(b, options);
799}841}
800842
801/// Initializes a `Step.Run` with argv, which must at least have the path to the843/// Creates a step that executes a process on the host system.
802/// executable. More command line arguments can be added with `addArg`,844///
803/// `addArgs`, and `addArtifactArg`.845/// `argv` is one or more command line arguments passed to the executed
804/// Be careful using this function, as it introduces a system dependency.846/// process. The first element is the name of the executable to run. More
805/// To run an executable built with zig build, see `Step.Compile.run`.847/// command line arguments can be added with methods of `Step.Run`, such as:
848/// * `Step.Run.addArgs`
849/// * `Step.Run.addArtifactArg`
850/// * `Step.Run.addFileArg`
851/// * `Step.Run.addOutputFileArg`
852///
853/// This function introduces a system dependency, compromising reproducibility
854/// and making it more difficult to set up one's computer in order to build the
855/// project from source.
856///
857/// See also:
858/// * `addRunArtifact`
859/// * `addRunFile`
806pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run {860pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run {
807 assert(argv.len >= 1);861 assert(argv.len >= 1);
808 const run_step = Step.Run.create(b, b.fmt("run {s}", .{argv[0]}));862 const run_step = Step.Run.create(b, b.fmt("run {s}", .{argv[0]}));
...@@ -818,8 +872,11 @@ pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run {...@@ -818,8 +872,11 @@ pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run {
818///872///
819/// This is declarative; it constructs a build step that may or may not be run873/// This is declarative; it constructs a build step that may or may not be run
820/// depending on the options provided by the user to the build command.874/// depending on the options provided by the user to the build command.
875///
876/// See also:
877/// * `addSystemCommand`
878/// * `addRunFile`
821pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {879pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
822
823 // Avoid the common case of the step name looking like "run test test".880 // Avoid the common case of the step name looking like "run test test".
824 const step_name = if (exe.kind.isTest() and mem.eql(u8, exe.name, "test"))881 const step_name = if (exe.kind.isTest() and mem.eql(u8, exe.name, "test"))
825 b.fmt("run {t}", .{exe.kind})882 b.fmt("run {t}", .{exe.kind})
...@@ -879,6 +936,19 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {...@@ -879,6 +936,19 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
879 return run_step;936 return run_step;
880}937}
881938
939/// Creates a step that executes the provided file.
940///
941/// Add more command line arguments via methods of `Step.Run`.
942///
943/// See also:
944/// * `addSystemCommand`
945/// * `addRunArtifact`
946pub fn addRunFile(b: *Build, executable: LazyPath) *Step.Run {
947 const run_step = Step.Run.create(b, b.fmt("run {f}", .{executable.fmt(b.graph)}));
948 run_step.addFileArg(executable);
949 return run_step;
950}
951
882/// Using the `values` provided, produces a C header file, possibly based on a952/// Using the `values` provided, produces a C header file, possibly based on a
883/// template input file (e.g. config.h.in).953/// template input file (e.g. config.h.in).
884/// When an input template file is provided, this function will fail the build954/// When an input template file is provided, this function will fail the build
...@@ -1641,11 +1711,41 @@ pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 {...@@ -1641,11 +1711,41 @@ pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 {
1641///1711///
1642/// Returns the `LazyPath` of the found executable. The search only takes place1712/// Returns the `LazyPath` of the found executable. The search only takes place
1643/// if the `LazyPath` will be used by a depending `Step`.1713/// if the `LazyPath` will be used by a depending `Step`.
1644pub fn findProgram(b: *Build, names: []const []const u8) LazyPath {1714///
1715/// This API is useful in the following cases:
1716/// * The binary is not named the same across all systems (for example "python"
1717/// vs "python3").
1718/// * The binary may be produced by building from source rather than being
1719/// globally installed and will therefore be possibly found in one of the
1720/// search prefix paths.
1721///
1722/// See also:
1723/// * `findProgram`
1724pub fn findProgramLazy(b: *Build, names: []const []const u8) LazyPath {
1725 const graph = b.graph;
1726 const wc = &graph.wip_configuration;
1727 const string_list = wc.addStringList(names) catch @panic("OOM");
1728 _ = string_list;
1729 @panic("TODO");
1730}
1731
1732/// Immediately (in the configure phase), searches for an executable on the host
1733/// that has more than one possible name.
1734///
1735/// Names are searched in order, observing search prefixes first and then PATH
1736/// environment variable.
1737///
1738/// Calling this function poisons the configuration cache. For more
1739/// information, see `Graph.CachePoison` documentation.
1740///
1741/// See also:
1742/// * `findProgramLazy`
1743pub fn findProgram(b: *Build, names: []const []const u8) ?[]const u8 {
1645 const graph = b.graph;1744 const graph = b.graph;
1646 const wc = &graph.wip_configuration;1745 const wc = &graph.wip_configuration;
1647 const string_list = wc.addStringList(names) catch @panic("OOM");1746 const string_list = wc.addStringList(names) catch @panic("OOM");
1648 _ = string_list;1747 _ = string_list;
1748 graph.poisonCache();
1649 @panic("TODO");1749 @panic("TODO");
1650}1750}
16511751
lib/std/Build/Configuration.zig+13
...@@ -17,6 +17,7 @@ search_prefixes: []String,...@@ -17,6 +17,7 @@ search_prefixes: []String,
17extra: []u32,17extra: []u32,
18default_step: Step.Index,18default_step: Step.Index,
19generated_files_len: u32,19generated_files_len: u32,
20poisoned: bool,
2021
21/// The field order here matches `Configuration` which documents the order in22/// The field order here matches `Configuration` which documents the order in
22/// the serialized format.23/// the serialized format.
...@@ -34,6 +35,12 @@ pub const Header = extern struct {...@@ -34,6 +35,12 @@ pub const Header = extern struct {
34 /// There is not actually any data stored for this - it just provides a way35 /// There is not actually any data stored for this - it just provides a way
35 /// for maker process to preallocate an array for these.36 /// for maker process to preallocate an array for these.
36 generated_files_len: u32,37 generated_files_len: u32,
38 flags: Flags,
39
40 pub const Flags = packed struct(u32) {
41 poisoned: bool,
42 _: u31 = 0,
43 };
37};44};
3845
39pub const Wip = struct {46pub const Wip = struct {
...@@ -52,6 +59,7 @@ pub const Wip = struct {...@@ -52,6 +59,7 @@ pub const Wip = struct {
52 search_prefixes: std.ArrayList(String) = .empty,59 search_prefixes: std.ArrayList(String) = .empty,
53 extra: std.ArrayList(u32) = .empty,60 extra: std.ArrayList(u32) = .empty,
54 next_generated_file_index: u32 = 0,61 next_generated_file_index: u32 = 0,
62 cache_poison: bool = false,
5563
56 const DedupeTable = std.HashMapUnmanaged(ExtraSlice, void, ExtraSlice.Context, std.hash_map.default_max_load_percentage);64 const DedupeTable = std.HashMapUnmanaged(ExtraSlice, void, ExtraSlice.Context, std.hash_map.default_max_load_percentage);
57 const TargetsTable = std.HashMapUnmanaged(TargetQuery.Index, void, TargetsTableContext, std.hash_map.default_max_load_percentage);65 const TargetsTable = std.HashMapUnmanaged(TargetQuery.Index, void, TargetsTableContext, std.hash_map.default_max_load_percentage);
...@@ -137,6 +145,7 @@ pub const Wip = struct {...@@ -137,6 +145,7 @@ pub const Wip = struct {
137 pub const Static = struct {145 pub const Static = struct {
138 default_step: Step.Index,146 default_step: Step.Index,
139 generated_files_len: u32,147 generated_files_len: u32,
148 poisoned: bool,
140 };149 };
141150
142 pub fn write(wip: *Wip, w: *Io.Writer, static: Static) Io.Writer.Error!void {151 pub fn write(wip: *Wip, w: *Io.Writer, static: Static) Io.Writer.Error!void {
...@@ -152,6 +161,9 @@ pub const Wip = struct {...@@ -152,6 +161,9 @@ pub const Wip = struct {
152161
153 .default_step = static.default_step,162 .default_step = static.default_step,
154 .generated_files_len = static.generated_files_len,163 .generated_files_len = static.generated_files_len,
164 .flags = .{
165 .poisoned = static.poisoned,
166 },
155 };167 };
156 var buffers = [_][]const u8{168 var buffers = [_][]const u8{
157 @ptrCast(&header),169 @ptrCast(&header),
...@@ -3325,6 +3337,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {...@@ -3325,6 +3337,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {
3325 .extra = try arena.alloc(u32, header.extra_len),3337 .extra = try arena.alloc(u32, header.extra_len),
3326 .default_step = header.default_step,3338 .default_step = header.default_step,
3327 .generated_files_len = header.generated_files_len,3339 .generated_files_len = header.generated_files_len,
3340 .poisoned = header.flags.poisoned,
3328 };3341 };
3329 var vecs = [_][]u8{3342 var vecs = [_][]u8{
3330 result.string_bytes,3343 result.string_bytes,
src/main.zig+114-116
...@@ -4970,6 +4970,7 @@ fn cmdBuild(...@@ -4970,6 +4970,7 @@ fn cmdBuild(
4970 var system_pkg_dir_path: ?[]const u8 = null;4970 var system_pkg_dir_path: ?[]const u8 = null;
4971 var debug_target: ?[]const u8 = null;4971 var debug_target: ?[]const u8 = null;
4972 var debug_libc_paths_file: ?[]const u8 = null;4972 var debug_libc_paths_file: ?[]const u8 = null;
4973 var cache_poison: std.Build.Graph.CachePoison = .pure;
49734974
4974 const self_exe_path = try process.executablePathAlloc(io, arena);4975 const self_exe_path = try process.executablePathAlloc(io, arena);
4975 const default_seed = try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)});4976 const default_seed = try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)});
...@@ -5039,6 +5040,21 @@ fn cmdBuild(...@@ -5039,6 +5040,21 @@ fn cmdBuild(
5039 try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));5040 try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
5040 configure_argv.appendAssumeCapacity(arg);5041 configure_argv.appendAssumeCapacity(arg);
5041 continue;5042 continue;
5043 } else if (mem.eql(u8, arg, "--cache-poison")) {
5044 cache_poison = .poisoned;
5045 configure_argv.appendAssumeCapacity("--cache-poison=poisoned");
5046 continue;
5047 } else if (mem.cutPrefix(u8, arg, "--cache-poison=")) |rest| {
5048 // Allow the configurer process to report parse failure.
5049 if (std.meta.stringToEnum(std.Build.Graph.CachePoison, rest)) |poison| {
5050 cache_poison = poison;
5051 }
5052 configure_argv.appendAssumeCapacity(arg);
5053 continue;
5054 } else if (mem.eql(u8, arg, "--verbose")) {
5055 // Intentionally is added both to make and configure but
5056 // does not go into the cache hash.
5057 configure_argv.appendAssumeCapacity(arg);
5042 } else if (mem.eql(u8, arg, "--build-file")) {5058 } else if (mem.eql(u8, arg, "--build-file")) {
5043 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});5059 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
5044 i += 1;5060 i += 1;
...@@ -5069,10 +5085,6 @@ fn cmdBuild(...@@ -5069,10 +5085,6 @@ fn cmdBuild(
5069 i += 1;5085 i += 1;
5070 override_global_cache_dir = args[i];5086 override_global_cache_dir = args[i];
5071 continue;5087 continue;
5072 } else if (mem.eql(u8, arg, "--verbose")) {
5073 // Intentionally is added both to make and configure but
5074 // does not go into the cache hash.
5075 configure_argv.appendAssumeCapacity(arg);
5076 } else if (mem.eql(u8, arg, "-freference-trace")) {5088 } else if (mem.eql(u8, arg, "-freference-trace")) {
5077 reference_trace = 256;5089 reference_trace = 256;
5078 } else if (mem.eql(u8, arg, "--fetch")) {5090 } else if (mem.eql(u8, arg, "--fetch")) {
...@@ -5229,6 +5241,10 @@ fn cmdBuild(...@@ -5229,6 +5241,10 @@ fn cmdBuild(
5229 for (cached_passthru_configure.items) |i|5241 for (cached_passthru_configure.items) |i|
5230 config_man.hash.addBytes(configure_argv.items[i]);5242 config_man.hash.addBytes(configure_argv.items[i]);
52315243
5244 // Prevents a `zig build` from getting a false positive cache hit following
5245 // a `zig build --cache-poison=ignored`.
5246 config_man.hash.add(cache_poison == .ignored);
5247
5232 // Normally the build runner is compiled for the host target but here is5248 // Normally the build runner is compiled for the host target but here is
5233 // some code to help when debugging edits to the build runner so that you5249 // some code to help when debugging edits to the build runner so that you
5234 // can make sure it compiles successfully on other targets.5250 // can make sure it compiles successfully on other targets.
...@@ -5338,7 +5354,7 @@ fn cmdBuild(...@@ -5338,7 +5354,7 @@ fn cmdBuild(
53385354
5339 // This loop is re-evaluated when the build script exits with an indication that it5355 // This loop is re-evaluated when the build script exits with an indication that it
5340 // could not continue due to missing lazy dependencies.5356 // could not continue due to missing lazy dependencies.
5341 const configuration_path: Path = cp: while (true) {5357 const configuration_path: Path, const poisoned: bool = cp: while (true) {
5342 // We want to release all the locks before executing the child process, so we make a nice5358 // We want to release all the locks before executing the child process, so we make a nice
5343 // big block here to ensure the cleanup gets run when we extract out our argv.5359 // big block here to ensure the cleanup gets run when we extract out our argv.
5344 {5360 {
...@@ -5609,12 +5625,18 @@ fn cmdBuild(...@@ -5609,12 +5625,18 @@ fn cmdBuild(
5609 _ = try config_man.addFilePath(exe_path, null);5625 _ = try config_man.addFilePath(exe_path, null);
5610 configure_argv.items[0] = try exe_path.toString(arena);5626 configure_argv.items[0] = try exe_path.toString(arena);
56115627
5612 if (try config_man.hit()) {5628 switch (cache_poison) {
5613 const digest = config_man.final();5629 .pure, .disallowed, .ignored => if (try config_man.hit()) {
5614 break :cp .{5630 const digest = config_man.final();
5615 .root_dir = dirs.local_cache,5631 break :cp .{
5616 .sub_path = try std.fmt.allocPrint(arena, "o/{s}", .{&digest}),5632 .{
5617 };5633 .root_dir = dirs.local_cache,
5634 .sub_path = try std.fmt.allocPrint(arena, "o/{s}", .{&digest}),
5635 },
5636 false,
5637 };
5638 },
5639 .poisoned => {}, // Don't bother checking for cache hit.
5618 }5640 }
5619 }5641 }
56205642
...@@ -5636,7 +5658,7 @@ fn cmdBuild(...@@ -5636,7 +5658,7 @@ fn cmdBuild(
5636 );5658 );
5637 defer config_tmp_file.close(io);5659 defer config_tmp_file.close(io);
56385660
5639 switch (term: {5661 const term = term: {
5640 const child_node = root_prog_node.start("Run Configure Script", 0);5662 const child_node = root_prog_node.start("Run Configure Script", 0);
5641 defer child_node.end();5663 defer child_node.end();
5642 var child = std.process.spawn(io, .{5664 var child = std.process.spawn(io, .{
...@@ -5647,101 +5669,86 @@ fn cmdBuild(...@@ -5647,101 +5669,86 @@ fn cmdBuild(
5647 defer child.kill(io);5669 defer child.kill(io);
5648 break :term child.wait(io) catch |err|5670 break :term child.wait(io) catch |err|
5649 fatal("failed to wait configure script {s}: {t}", .{ configure_argv.items[0], err });5671 fatal("failed to wait configure script {s}: {t}", .{ configure_argv.items[0], err });
5650 }) {5672 };
5651 .exited => |code| {5673 if (!term.success()) {
5652 if (code != 0) {5674 // Failure to produce the configuration file.
5653 // Failure to produce the configuration file.5675 const cmd = try std.mem.join(arena, " ", configure_argv.items);
5654 const cmd = try std.mem.join(arena, " ", configure_argv.items);5676 fatal("the following configure command {f}:\n{s}", .{ term, cmd });
5655 fatal("the following configure command failed with exit code {d}:\n{s}", .{ code, cmd });5677 }
5656 }5678 // Even though the file is designed to be sent directly to make
5657 // Even though the file is designed to be sent directly to make5679 // runner, we must load it now because:
5658 // runner, we must load it now because:5680 // * If it contains additional file dependencies, we need to
5659 // * If it contains additional file dependencies, we need to5681 // add them to `config_man` before obtaining the final digest.
5660 // add them to `config_man` before obtaining the final digest.5682 // * If it contains a set of lazy packages that need to be
5661 // * If it contains a set of lazy packages that need to be5683 // fetched, we need to fetch those now and re-run configure.
5662 // fetched, we need to fetch those now and re-run configure.5684 var configuration = std.Build.Configuration.loadFile(arena, io, config_tmp_file) catch |err|
5663 var configuration = std.Build.Configuration.loadFile(arena, io, config_tmp_file) catch |err|5685 fatal("failed to load configuration file {f}: {t}", .{ config_tmp_path, err });
5664 fatal("failed to load configuration file {f}: {t}", .{ config_tmp_path, err });5686
56655687 if (configuration.unlazy_deps.len != 0) {
5666 if (configuration.unlazy_deps.len != 0) {5688 if (!dev.env.supports(.fetch_command)) process.exit(1);
5667 if (!dev.env.supports(.fetch_command)) process.exit(1);5689 var any_errors = false;
5668 var any_errors = false;5690 for (configuration.unlazy_deps) |hash_string| {
5669 for (configuration.unlazy_deps) |hash_string| {5691 const hash = hash_string.slice(&configuration);
5670 const hash = hash_string.slice(&configuration);5692 assert(hash.len != 0);
5671 assert(hash.len != 0);5693 if (hash.len > Package.Hash.max_len) {
5672 if (hash.len > Package.Hash.max_len) {5694 std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{ hash.len, hash });
5673 std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{5695 any_errors = true;
5674 hash.len, hash,5696 continue;
5675 });
5676 any_errors = true;
5677 continue;
5678 }
5679 try unlazy_set.put(arena, .fromSlice(hash), {});
5680 }
5681 if (any_errors) process.exit(1);
5682 if (system_pkg_dir_path) |p| {
5683 // In this mode, the system needs to provide these packages; they
5684 // cannot be fetched by Zig.
5685 const s = fs.path.sep_str;
5686 for (unlazy_set.keys()) |*hash| {
5687 std.log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{
5688 p, hash.toSlice(),
5689 });
5690 }
5691 std.log.info("remote package fetching disabled due to --system mode", .{});
5692 std.log.info("dependencies might be avoidable depending on build configuration", .{});
5693 process.exit(1);
5694 }
5695 continue :cp;
5696 }5697 }
56975698 try unlazy_set.put(arena, .fromSlice(hash), {});
5698 for (configuration.path_deps_base, configuration.path_deps_sub) |base, sub| {5699 }
5699 const conf_path: std.Build.Configuration.Path = .{ .base = base, .sub = sub };5700 if (any_errors) process.exit(1);
5700 try config_man.addPathPost(conf_path.toCachePath(&configuration, arena));5701 if (system_pkg_dir_path) |p| {
5702 // In this mode, the system needs to provide these packages; they
5703 // cannot be fetched by Zig.
5704 const s = fs.path.sep_str;
5705 for (unlazy_set.keys()) |*hash| {
5706 std.log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ p, hash.toSlice() });
5701 }5707 }
5708 std.log.info("remote package fetching disabled due to --system mode", .{});
5709 std.log.info("dependencies might be avoidable depending on build configuration", .{});
5710 process.exit(1);
5711 }
5712 continue :cp;
5713 }
57025714
5703 const digest = config_man.final();5715 for (configuration.path_deps_base, configuration.path_deps_sub) |base, sub| {
5704 const final_path: Path = .{5716 const conf_path: std.Build.Configuration.Path = .{ .base = base, .sub = sub };
5705 .root_dir = dirs.local_cache,5717 try config_man.addPathPost(conf_path.toCachePath(&configuration, arena));
5706 .sub_path = try std.fmt.allocPrint(arena, "o/{s}", .{&digest}),5718 }
5707 };
5708 Io.Dir.rename(
5709 config_tmp_path.root_dir.handle,
5710 config_tmp_path.sub_path,
5711 final_path.root_dir.handle,
5712 final_path.sub_path,
5713 io,
5714 ) catch |err| {
5715 fatal("failed to rename configuration file from {f} into {f}: {t}", .{
5716 config_tmp_path, final_path, err,
5717 });
5718 };
5719 config_man.writeManifest() catch |err| warn("failed to write cache manifest: {t}", .{err});
57205719
5721 break :cp final_path;5720 // If it is poisoned, there is no point in moving it to cached
5722 },5721 // location. Just leave it in the tmp directory.
5723 .signal => |sig| {5722 if (configuration.poisoned) {
5724 const cmd = try std.mem.join(arena, " ", configure_argv.items);5723 break :cp .{ config_tmp_path, true };
5725 fatal("the following configure command terminated with signal {t}:\n{s}", .{ sig, cmd });5724 } else {
5726 },5725 const digest = config_man.final();
5727 .stopped => |sig| {5726 const final_path: Path = .{
5728 const cmd = try std.mem.join(arena, " ", configure_argv.items);5727 .root_dir = dirs.local_cache,
5729 fatal("the following build command stopped with signal {t}:\n{s}", .{ sig, cmd });5728 .sub_path = try std.fmt.allocPrint(arena, "o/{s}", .{&digest}),
5730 },5729 };
5731 .unknown => {5730 Io.Dir.rename(
5732 const cmd = try std.mem.join(arena, " ", configure_argv.items);5731 config_tmp_path.root_dir.handle,
5733 fatal("the following build command crashed:\n{s}", .{cmd});5732 config_tmp_path.sub_path,
5734 },5733 final_path.root_dir.handle,
5734 final_path.sub_path,
5735 io,
5736 ) catch |err| {
5737 fatal("failed to rename configuration file from {f} into {f}: {t}", .{
5738 config_tmp_path, final_path, err,
5739 });
5740 };
5741 config_man.writeManifest() catch |err| warn("failed to write cache manifest: {t}", .{err});
5742 break :cp .{ final_path, false };
5735 }5743 }
5736 };5744 };
57375745
5738 {5746 {
5739 // Release all file system locks just before running the maker process.5747 // Release all file system locks just before running the maker process.
5740 var configuration_lock = config_man.toOwnedLock();5748 var configuration_lock = if (!poisoned) config_man.toOwnedLock() else null;
5741 defer configuration_lock.release(io);5749 defer if (configuration_lock) |*l| l.release(io);
57425750
5743 const make_runner = make_runner_task.await(io) catch |err|5751 const make_runner = make_runner_task.await(io) catch |err| fatal("failed compiling maker: {t}", .{err});
5744 fatal("failed to compile maker: {t}", .{err});
57455752
5746 make_argv.items[0] = try make_runner.exe_path.toString(arena);5753 make_argv.items[0] = try make_runner.exe_path.toString(arena);
5747 make_argv.items[argv_index_configuration_file] = try configuration_path.toString(arena);5754 make_argv.items[argv_index_configuration_file] = try configuration_path.toString(arena);
...@@ -5749,33 +5756,24 @@ fn cmdBuild(...@@ -5749,33 +5756,24 @@ fn cmdBuild(
57495756
5750 if (!process.can_spawn) {5757 if (!process.can_spawn) {
5751 const cmd = try std.mem.join(arena, " ", make_argv.items);5758 const cmd = try std.mem.join(arena, " ", make_argv.items);
5752 fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{ native_os, cmd });5759 fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{
5760 native_os, cmd,
5761 });
5753 }5762 }
57545763
5755 switch (term: {5764 const term = term: {
5756 _ = try io.lockStderr(&.{}, .no_color);5765 _ = try io.lockStderr(&.{}, .no_color);
5757 defer io.unlockStderr();5766 defer io.unlockStderr();
5758 var child = std.process.spawn(io, .{5767 var child = std.process.spawn(io, .{
5759 .argv = make_argv.items,5768 .argv = make_argv.items,
5760 }) catch |err| fatal("failed to spawn maker {s}: {t}", .{ make_argv.items[0], err });5769 }) catch |err| fatal("failed spawning maker {s}: {t}", .{ make_argv.items[0], err });
5761 defer child.kill(io);5770 defer child.kill(io);
5762 break :term child.wait(io) catch |err|5771 break :term child.wait(io) catch |err|
5763 fatal("failed to wait maker {s}: {t}", .{ make_argv.items[0], err });5772 fatal("failed waiting on maker {s}: {t}", .{ make_argv.items[0], err });
5764 }) {5773 };
5765 .exited => |code| {5774 if (term.success()) return cleanExit(io);
5766 if (code == 0) return cleanExit(io);5775 const cmd = try std.mem.join(arena, " ", make_argv.items);
5767 const cmd = try std.mem.join(arena, " ", make_argv.items);5776 fatal("the following maker command {f}:\n{s}", .{ term, cmd });
5768 fatal("the following maker command failed with exit code {d}:\n{s}", .{ code, cmd });
5769 },
5770 .signal => |sig| {
5771 const cmd = try std.mem.join(arena, " ", make_argv.items);
5772 fatal("the following maker command terminated with signal {t}:\n{s}", .{ sig, cmd });
5773 },
5774 else => {
5775 const cmd = try std.mem.join(arena, " ", make_argv.items);
5776 fatal("the following maker command crashed:\n{s}", .{cmd});
5777 },
5778 }
5779}5777}
57805778
5781const MakeRunner = struct {5779const MakeRunner = struct {