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 {
425425 break :c Configuration.loadFile(arena, io, file) catch |err|
426426 fatal("failed to load configuration file {s}: {t}", .{ configure_path, err });
427427 };
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.
428432 const c = &configuration;
429433 var top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index) = .empty;
430434 for (configuration.steps, 0..) |*conf_step, step_index_usize| {
......@@ -445,6 +449,7 @@ pub fn main(init: process.Init.Minimal) !void {
445449 break :sc .{
446450 .configuration = configuration,
447451 .top_level_steps = top_level_steps,
452 .path = configure_path,
448453 };
449454 };
450455
......@@ -455,7 +460,7 @@ pub fn main(init: process.Init.Minimal) !void {
455460 else => |e| return e,
456461 };
457462 w.flush() catch return stdout_writer_allocation.err.?;
458 return;
463 return cleanExit(io, &scanned_config);
459464 } else if (steps_menu) {
460465 var w = initStdoutWriter(io);
461466 scanned_config.printSteps(&graph, w) catch |err| switch (err) {
......@@ -463,12 +468,12 @@ pub fn main(init: process.Init.Minimal) !void {
463468 else => |e| return e,
464469 };
465470 w.flush() catch return stdout_writer_allocation.err.?;
466 return;
471 return cleanExit(io, &scanned_config);
467472 } else if (print_configuration) {
468473 var w = initStdoutWriter(io);
469474 scanned_config.print(w) catch return stdout_writer_allocation.err.?;
470475 w.flush() catch return stdout_writer_allocation.err.?;
471 return;
476 return cleanExit(io, &scanned_config);
472477 }
473478
474479 if (webui_listen != null) {
......@@ -1000,6 +1005,8 @@ fn makeStepNames(
10001005 if (maker.error_style.verboseContext()) break :code 1; // failure; print build command
10011006 break :code 2; // failure; do not print build command
10021007 };
1008 if (code == 0) removePoisonedConfiguration(io, maker.scanned_config);
1009 cleanup_task.await(io); // There is a defer above but an exit below.
10031010 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
10041011 process.exit(code);
10051012}
......@@ -2000,3 +2007,17 @@ fn installSymLinksInner(
20002007 return step.fail(maker, "unable to symlink {f} -> {s}: {t}", .{ name_only_path, filename_major_only, err });
20012008 };
20022009}
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");
99
1010configuration: Configuration,
1111top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index),
12path: []const u8,
1213
1314pub fn print(sc: *const ScannedConfig, w: *Writer) Writer.Error!void {
1415 std.log.err("TODO also print paths", .{});
......@@ -343,6 +344,11 @@ pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {
343344 \\ --zig-lib-dir [arg] Override path to Zig lib directory
344345 \\ --build-runner [file] Override path to build runner
345346 \\ --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
346352 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries
347353 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)
348354 \\ 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 {
114114 graph.system_package_mode = true;
115115 } else if (mem.eql(u8, arg, "--verbose")) {
116116 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});
117120 } else {
118121 fatalWithHint("unrecognized argument: {s}", .{arg});
119122 }
......@@ -1222,6 +1225,10 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
12221225 try wc.write(writer, .{
12231226 .default_step = s.stepIndex(b.default_step),
12241227 .generated_files_len = @intCast(graph.generated_files.items.len),
1228 .poisoned = switch (graph.cache_poison) {
1229 .pure, .disallowed, .ignored => false,
1230 .poisoned => true,
1231 },
12251232 });
12261233}
12271234
lib/std/Build.zig+107-7
......@@ -98,6 +98,35 @@ pub const Graph = struct {
9898 generated_files: std.ArrayList(*Step),
9999 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
101130 pub fn addGeneratedFile(graph: *Graph, owner: *Step) Configuration.GeneratedFileIndex {
102131 graph.generated_files.append(graph.arena, owner) catch @panic("OOM");
103132 return @enumFromInt(graph.generated_files.items.len - 1);
......@@ -169,6 +198,19 @@ pub const Graph = struct {
169198 const wc = &graph.wip_configuration;
170199 return wc.addString(bytes) catch @panic("OOM");
171200 }
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 }
172214};
173215
174216const AvailableDeps = []const struct { []const u8, []const u8 };
......@@ -798,11 +840,23 @@ pub fn createModule(b: *Build, options: Module.CreateOptions) *Module {
798840 return Module.create(b, options);
799841}
800842
801/// Initializes a `Step.Run` with argv, which must at least have the path to the
802/// executable. More command line arguments can be added with `addArg`,
803/// `addArgs`, and `addArtifactArg`.
804/// Be careful using this function, as it introduces a system dependency.
805/// To run an executable built with zig build, see `Step.Compile.run`.
843/// Creates a step that executes a process on the host system.
844///
845/// `argv` is one or more command line arguments passed to the executed
846/// process. The first element is the name of the executable to run. More
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`
806860pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run {
807861 assert(argv.len >= 1);
808862 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 {
818872///
819873/// This is declarative; it constructs a build step that may or may not be run
820874/// depending on the options provided by the user to the build command.
875///
876/// See also:
877/// * `addSystemCommand`
878/// * `addRunFile`
821879pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
822
823880 // Avoid the common case of the step name looking like "run test test".
824881 const step_name = if (exe.kind.isTest() and mem.eql(u8, exe.name, "test"))
825882 b.fmt("run {t}", .{exe.kind})
......@@ -879,6 +936,19 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
879936 return run_step;
880937}
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
882952/// Using the `values` provided, produces a C header file, possibly based on a
883953/// template input file (e.g. config.h.in).
884954/// 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 {
16411711///
16421712/// Returns the `LazyPath` of the found executable. The search only takes place
16431713/// 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 {
16451744 const graph = b.graph;
16461745 const wc = &graph.wip_configuration;
16471746 const string_list = wc.addStringList(names) catch @panic("OOM");
16481747 _ = string_list;
1748 graph.poisonCache();
16491749 @panic("TODO");
16501750}
16511751
lib/std/Build/Configuration.zig+13
......@@ -17,6 +17,7 @@ search_prefixes: []String,
1717extra: []u32,
1818default_step: Step.Index,
1919generated_files_len: u32,
20poisoned: bool,
2021
2122/// The field order here matches `Configuration` which documents the order in
2223/// the serialized format.
......@@ -34,6 +35,12 @@ pub const Header = extern struct {
3435 /// There is not actually any data stored for this - it just provides a way
3536 /// for maker process to preallocate an array for these.
3637 generated_files_len: u32,
38 flags: Flags,
39
40 pub const Flags = packed struct(u32) {
41 poisoned: bool,
42 _: u31 = 0,
43 };
3744};
3845
3946pub const Wip = struct {
......@@ -52,6 +59,7 @@ pub const Wip = struct {
5259 search_prefixes: std.ArrayList(String) = .empty,
5360 extra: std.ArrayList(u32) = .empty,
5461 next_generated_file_index: u32 = 0,
62 cache_poison: bool = false,
5563
5664 const DedupeTable = std.HashMapUnmanaged(ExtraSlice, void, ExtraSlice.Context, std.hash_map.default_max_load_percentage);
5765 const TargetsTable = std.HashMapUnmanaged(TargetQuery.Index, void, TargetsTableContext, std.hash_map.default_max_load_percentage);
......@@ -137,6 +145,7 @@ pub const Wip = struct {
137145 pub const Static = struct {
138146 default_step: Step.Index,
139147 generated_files_len: u32,
148 poisoned: bool,
140149 };
141150
142151 pub fn write(wip: *Wip, w: *Io.Writer, static: Static) Io.Writer.Error!void {
......@@ -152,6 +161,9 @@ pub const Wip = struct {
152161
153162 .default_step = static.default_step,
154163 .generated_files_len = static.generated_files_len,
164 .flags = .{
165 .poisoned = static.poisoned,
166 },
155167 };
156168 var buffers = [_][]const u8{
157169 @ptrCast(&header),
......@@ -3325,6 +3337,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {
33253337 .extra = try arena.alloc(u32, header.extra_len),
33263338 .default_step = header.default_step,
33273339 .generated_files_len = header.generated_files_len,
3340 .poisoned = header.flags.poisoned,
33283341 };
33293342 var vecs = [_][]u8{
33303343 result.string_bytes,
src/main.zig+114-116
......@@ -4970,6 +4970,7 @@ fn cmdBuild(
49704970 var system_pkg_dir_path: ?[]const u8 = null;
49714971 var debug_target: ?[]const u8 = null;
49724972 var debug_libc_paths_file: ?[]const u8 = null;
4973 var cache_poison: std.Build.Graph.CachePoison = .pure;
49734974
49744975 const self_exe_path = try process.executablePathAlloc(io, arena);
49754976 const default_seed = try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)});
......@@ -5039,6 +5040,21 @@ fn cmdBuild(
50395040 try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
50405041 configure_argv.appendAssumeCapacity(arg);
50415042 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);
50425058 } else if (mem.eql(u8, arg, "--build-file")) {
50435059 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
50445060 i += 1;
......@@ -5069,10 +5085,6 @@ fn cmdBuild(
50695085 i += 1;
50705086 override_global_cache_dir = args[i];
50715087 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);
50765088 } else if (mem.eql(u8, arg, "-freference-trace")) {
50775089 reference_trace = 256;
50785090 } else if (mem.eql(u8, arg, "--fetch")) {
......@@ -5229,6 +5241,10 @@ fn cmdBuild(
52295241 for (cached_passthru_configure.items) |i|
52305242 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
52325248 // Normally the build runner is compiled for the host target but here is
52335249 // some code to help when debugging edits to the build runner so that you
52345250 // can make sure it compiles successfully on other targets.
......@@ -5338,7 +5354,7 @@ fn cmdBuild(
53385354
53395355 // This loop is re-evaluated when the build script exits with an indication that it
53405356 // 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) {
53425358 // We want to release all the locks before executing the child process, so we make a nice
53435359 // big block here to ensure the cleanup gets run when we extract out our argv.
53445360 {
......@@ -5609,12 +5625,18 @@ fn cmdBuild(
56095625 _ = try config_man.addFilePath(exe_path, null);
56105626 configure_argv.items[0] = try exe_path.toString(arena);
56115627
5612 if (try config_man.hit()) {
5613 const digest = config_man.final();
5614 break :cp .{
5615 .root_dir = dirs.local_cache,
5616 .sub_path = try std.fmt.allocPrint(arena, "o/{s}", .{&digest}),
5617 };
5628 switch (cache_poison) {
5629 .pure, .disallowed, .ignored => if (try config_man.hit()) {
5630 const digest = config_man.final();
5631 break :cp .{
5632 .{
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.
56185640 }
56195641 }
56205642
......@@ -5636,7 +5658,7 @@ fn cmdBuild(
56365658 );
56375659 defer config_tmp_file.close(io);
56385660
5639 switch (term: {
5661 const term = term: {
56405662 const child_node = root_prog_node.start("Run Configure Script", 0);
56415663 defer child_node.end();
56425664 var child = std.process.spawn(io, .{
......@@ -5647,101 +5669,86 @@ fn cmdBuild(
56475669 defer child.kill(io);
56485670 break :term child.wait(io) catch |err|
56495671 fatal("failed to wait configure script {s}: {t}", .{ configure_argv.items[0], err });
5650 }) {
5651 .exited => |code| {
5652 if (code != 0) {
5653 // Failure to produce the configuration file.
5654 const cmd = try std.mem.join(arena, " ", configure_argv.items);
5655 fatal("the following configure command failed with exit code {d}:\n{s}", .{ code, cmd });
5656 }
5657 // Even though the file is designed to be sent directly to make
5658 // runner, we must load it now because:
5659 // * If it contains additional file dependencies, we need to
5660 // add them to `config_man` before obtaining the final digest.
5661 // * If it contains a set of lazy packages that need to be
5662 // fetched, we need to fetch those now and re-run configure.
5663 var configuration = std.Build.Configuration.loadFile(arena, io, config_tmp_file) catch |err|
5664 fatal("failed to load configuration file {f}: {t}", .{ config_tmp_path, err });
5665
5666 if (configuration.unlazy_deps.len != 0) {
5667 if (!dev.env.supports(.fetch_command)) process.exit(1);
5668 var any_errors = false;
5669 for (configuration.unlazy_deps) |hash_string| {
5670 const hash = hash_string.slice(&configuration);
5671 assert(hash.len != 0);
5672 if (hash.len > Package.Hash.max_len) {
5673 std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{
5674 hash.len, hash,
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;
5672 };
5673 if (!term.success()) {
5674 // Failure to produce the configuration file.
5675 const cmd = try std.mem.join(arena, " ", configure_argv.items);
5676 fatal("the following configure command {f}:\n{s}", .{ term, cmd });
5677 }
5678 // Even though the file is designed to be sent directly to make
5679 // runner, we must load it now because:
5680 // * If it contains additional file dependencies, we need to
5681 // add them to `config_man` before obtaining the final digest.
5682 // * If it contains a set of lazy packages that need to be
5683 // 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|
5685 fatal("failed to load configuration file {f}: {t}", .{ config_tmp_path, err });
5686
5687 if (configuration.unlazy_deps.len != 0) {
5688 if (!dev.env.supports(.fetch_command)) process.exit(1);
5689 var any_errors = false;
5690 for (configuration.unlazy_deps) |hash_string| {
5691 const hash = hash_string.slice(&configuration);
5692 assert(hash.len != 0);
5693 if (hash.len > Package.Hash.max_len) {
5694 std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{ hash.len, hash });
5695 any_errors = true;
5696 continue;
56965697 }
5697
5698 for (configuration.path_deps_base, configuration.path_deps_sub) |base, sub| {
5699 const conf_path: std.Build.Configuration.Path = .{ .base = base, .sub = sub };
5700 try config_man.addPathPost(conf_path.toCachePath(&configuration, arena));
5698 try unlazy_set.put(arena, .fromSlice(hash), {});
5699 }
5700 if (any_errors) process.exit(1);
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() });
57015707 }
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();
5704 const final_path: Path = .{
5705 .root_dir = dirs.local_cache,
5706 .sub_path = try std.fmt.allocPrint(arena, "o/{s}", .{&digest}),
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});
5715 for (configuration.path_deps_base, configuration.path_deps_sub) |base, sub| {
5716 const conf_path: std.Build.Configuration.Path = .{ .base = base, .sub = sub };
5717 try config_man.addPathPost(conf_path.toCachePath(&configuration, arena));
5718 }
57205719
5721 break :cp final_path;
5722 },
5723 .signal => |sig| {
5724 const cmd = try std.mem.join(arena, " ", configure_argv.items);
5725 fatal("the following configure command terminated with signal {t}:\n{s}", .{ sig, cmd });
5726 },
5727 .stopped => |sig| {
5728 const cmd = try std.mem.join(arena, " ", configure_argv.items);
5729 fatal("the following build command stopped with signal {t}:\n{s}", .{ sig, cmd });
5730 },
5731 .unknown => {
5732 const cmd = try std.mem.join(arena, " ", configure_argv.items);
5733 fatal("the following build command crashed:\n{s}", .{cmd});
5734 },
5720 // If it is poisoned, there is no point in moving it to cached
5721 // location. Just leave it in the tmp directory.
5722 if (configuration.poisoned) {
5723 break :cp .{ config_tmp_path, true };
5724 } else {
5725 const digest = config_man.final();
5726 const final_path: Path = .{
5727 .root_dir = dirs.local_cache,
5728 .sub_path = try std.fmt.allocPrint(arena, "o/{s}", .{&digest}),
5729 };
5730 Io.Dir.rename(
5731 config_tmp_path.root_dir.handle,
5732 config_tmp_path.sub_path,
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 };
57355743 }
57365744 };
57375745
57385746 {
57395747 // Release all file system locks just before running the maker process.
5740 var configuration_lock = config_man.toOwnedLock();
5741 defer configuration_lock.release(io);
5748 var configuration_lock = if (!poisoned) config_man.toOwnedLock() else null;
5749 defer if (configuration_lock) |*l| l.release(io);
57425750
5743 const make_runner = make_runner_task.await(io) catch |err|
5744 fatal("failed to compile maker: {t}", .{err});
5751 const make_runner = make_runner_task.await(io) catch |err| fatal("failed compiling maker: {t}", .{err});
57455752
57465753 make_argv.items[0] = try make_runner.exe_path.toString(arena);
57475754 make_argv.items[argv_index_configuration_file] = try configuration_path.toString(arena);
......@@ -5749,33 +5756,24 @@ fn cmdBuild(
57495756
57505757 if (!process.can_spawn) {
57515758 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 });
57535762 }
57545763
5755 switch (term: {
5764 const term = term: {
57565765 _ = try io.lockStderr(&.{}, .no_color);
57575766 defer io.unlockStderr();
57585767 var child = std.process.spawn(io, .{
57595768 .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 });
57615770 defer child.kill(io);
57625771 break :term child.wait(io) catch |err|
5763 fatal("failed to wait maker {s}: {t}", .{ make_argv.items[0], err });
5764 }) {
5765 .exited => |code| {
5766 if (code == 0) return cleanExit(io);
5767 const cmd = try std.mem.join(arena, " ", make_argv.items);
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 }
5772 fatal("failed waiting on maker {s}: {t}", .{ make_argv.items[0], err });
5773 };
5774 if (term.success()) return cleanExit(io);
5775 const cmd = try std.mem.join(arena, " ", make_argv.items);
5776 fatal("the following maker command {f}:\n{s}", .{ term, cmd });
57795777}
57805778
57815779const MakeRunner = struct {