| author | |
| committer | |
| log | 54bb8d2dd9369f5e5b43b4773878edc32fd3851e |
| tree | 00fba145dc090cc126b0b98c7b74f73cab8183af |
| parent | df8aaad05852b18e5a47aa09d5acdfd312583d1c |
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 | 425 | break :c Configuration.loadFile(arena, io, file) catch |err| |
| 426 | 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 | 432 | const c = &configuration; |
| 429 | 433 | var top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index) = .empty; |
| 430 | 434 | for (configuration.steps, 0..) |*conf_step, step_index_usize| { |
| ... | ... | @@ -445,6 +449,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 445 | 449 | break :sc .{ |
| 446 | 450 | .configuration = configuration, |
| 447 | 451 | .top_level_steps = top_level_steps, |
| 452 | .path = configure_path, | |
| 448 | 453 | }; |
| 449 | 454 | }; |
| 450 | 455 | |
| ... | ... | @@ -455,7 +460,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 455 | 460 | else => |e| return e, |
| 456 | 461 | }; |
| 457 | 462 | w.flush() catch return stdout_writer_allocation.err.?; |
| 458 | return; | |
| 463 | return cleanExit(io, &scanned_config); | |
| 459 | 464 | } else if (steps_menu) { |
| 460 | 465 | var w = initStdoutWriter(io); |
| 461 | 466 | scanned_config.printSteps(&graph, w) catch |err| switch (err) { |
| ... | ... | @@ -463,12 +468,12 @@ pub fn main(init: process.Init.Minimal) !void { |
| 463 | 468 | else => |e| return e, |
| 464 | 469 | }; |
| 465 | 470 | w.flush() catch return stdout_writer_allocation.err.?; |
| 466 | return; | |
| 471 | return cleanExit(io, &scanned_config); | |
| 467 | 472 | } else if (print_configuration) { |
| 468 | 473 | var w = initStdoutWriter(io); |
| 469 | 474 | scanned_config.print(w) catch return stdout_writer_allocation.err.?; |
| 470 | 475 | w.flush() catch return stdout_writer_allocation.err.?; |
| 471 | return; | |
| 476 | return cleanExit(io, &scanned_config); | |
| 472 | 477 | } |
| 473 | 478 | |
| 474 | 479 | if (webui_listen != null) { |
| ... | ... | @@ -1000,6 +1005,8 @@ fn makeStepNames( |
| 1000 | 1005 | if (maker.error_style.verboseContext()) break :code 1; // failure; print build command |
| 1001 | 1006 | 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 | 1010 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; |
| 1004 | 1011 | process.exit(code); |
| 1005 | 1012 | } |
| ... | ... | @@ -2000,3 +2007,17 @@ fn installSymLinksInner( |
| 2000 | 2007 | return step.fail(maker, "unable to symlink {f} -> {s}: {t}", .{ name_only_path, filename_major_only, err }); |
| 2001 | 2008 | }; |
| 2002 | 2009 | } |
| 2010 | ||
| 2011 | fn cleanExit(io: Io, scanned_config: *const ScannedConfig) void { | |
| 2012 | removePoisonedConfiguration(io, scanned_config); | |
| 2013 | return process.cleanExit(io); | |
| 2014 | } | |
| 2015 | ||
| 2016 | fn 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 | 9 | |
| 10 | 10 | configuration: Configuration, |
| 11 | 11 | top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index), |
| 12 | path: []const u8, | |
| 12 | 13 | |
| 13 | 14 | pub fn print(sc: *const ScannedConfig, w: *Writer) Writer.Error!void { |
| 14 | 15 | std.log.err("TODO also print paths", .{}); |
| ... | ... | @@ -343,6 +344,11 @@ pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void { |
| 343 | 344 | \\ --zig-lib-dir [arg] Override path to Zig lib directory |
| 344 | 345 | \\ --build-runner [file] Override path to build runner |
| 345 | 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 | 352 | \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries |
| 347 | 353 | \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM) |
| 348 | 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 | 114 | graph.system_package_mode = true; |
| 115 | 115 | } else if (mem.eql(u8, arg, "--verbose")) { |
| 116 | 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 | 120 | } else { |
| 118 | 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 | 1225 | try wc.write(writer, .{ |
| 1223 | 1226 | .default_step = s.stepIndex(b.default_step), |
| 1224 | 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 | } |
| 1227 | 1234 |
lib/std/Build.zig+107-7| ... | ... | @@ -98,6 +98,35 @@ pub const Graph = struct { |
| 98 | 98 | generated_files: std.ArrayList(*Step), |
| 99 | 99 | wip_configuration: Configuration.Wip, |
| 100 | 100 | |
| 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 | 130 | pub fn addGeneratedFile(graph: *Graph, owner: *Step) Configuration.GeneratedFileIndex { |
| 102 | 131 | graph.generated_files.append(graph.arena, owner) catch @panic("OOM"); |
| 103 | 132 | return @enumFromInt(graph.generated_files.items.len - 1); |
| ... | ... | @@ -169,6 +198,19 @@ pub const Graph = struct { |
| 169 | 198 | const wc = &graph.wip_configuration; |
| 170 | 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 | }; |
| 173 | 215 | |
| 174 | 216 | const AvailableDeps = []const struct { []const u8, []const u8 }; |
| ... | ... | @@ -798,11 +840,23 @@ pub fn createModule(b: *Build, options: Module.CreateOptions) *Module { |
| 798 | 840 | return Module.create(b, options); |
| 799 | 841 | } |
| 800 | 842 | |
| 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` | |
| 806 | 860 | pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run { |
| 807 | 861 | assert(argv.len >= 1); |
| 808 | 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 | 872 | /// |
| 819 | 873 | /// This is declarative; it constructs a build step that may or may not be run |
| 820 | 874 | /// depending on the options provided by the user to the build command. |
| 875 | /// | |
| 876 | /// See also: | |
| 877 | /// * `addSystemCommand` | |
| 878 | /// * `addRunFile` | |
| 821 | 879 | pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run { |
| 822 | ||
| 823 | 880 | // Avoid the common case of the step name looking like "run test test". |
| 824 | 881 | const step_name = if (exe.kind.isTest() and mem.eql(u8, exe.name, "test")) |
| 825 | 882 | b.fmt("run {t}", .{exe.kind}) |
| ... | ... | @@ -879,6 +936,19 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run { |
| 879 | 936 | return run_step; |
| 880 | 937 | } |
| 881 | 938 | |
| 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` | |
| 946 | pub 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 | 952 | /// Using the `values` provided, produces a C header file, possibly based on a |
| 883 | 953 | /// template input file (e.g. config.h.in). |
| 884 | 954 | /// 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 | 1711 | /// |
| 1642 | 1712 | /// Returns the `LazyPath` of the found executable. The search only takes place |
| 1643 | 1713 | /// if the `LazyPath` will be used by a depending `Step`. |
| 1644 | pub 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` | |
| 1724 | pub 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` | |
| 1743 | pub fn findProgram(b: *Build, names: []const []const u8) ?[]const u8 { | |
| 1645 | 1744 | const graph = b.graph; |
| 1646 | 1745 | const wc = &graph.wip_configuration; |
| 1647 | 1746 | const string_list = wc.addStringList(names) catch @panic("OOM"); |
| 1648 | 1747 | _ = string_list; |
| 1748 | graph.poisonCache(); | |
| 1649 | 1749 | @panic("TODO"); |
| 1650 | 1750 | } |
| 1651 | 1751 |
lib/std/Build/Configuration.zig+13| ... | ... | @@ -17,6 +17,7 @@ search_prefixes: []String, |
| 17 | 17 | extra: []u32, |
| 18 | 18 | default_step: Step.Index, |
| 19 | 19 | generated_files_len: u32, |
| 20 | poisoned: bool, | |
| 20 | 21 | |
| 21 | 22 | /// The field order here matches `Configuration` which documents the order in |
| 22 | 23 | /// the serialized format. |
| ... | ... | @@ -34,6 +35,12 @@ pub const Header = extern struct { |
| 34 | 35 | /// There is not actually any data stored for this - it just provides a way |
| 35 | 36 | /// for maker process to preallocate an array for these. |
| 36 | 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 | }; |
| 38 | 45 | |
| 39 | 46 | pub const Wip = struct { |
| ... | ... | @@ -52,6 +59,7 @@ pub const Wip = struct { |
| 52 | 59 | search_prefixes: std.ArrayList(String) = .empty, |
| 53 | 60 | extra: std.ArrayList(u32) = .empty, |
| 54 | 61 | next_generated_file_index: u32 = 0, |
| 62 | cache_poison: bool = false, | |
| 55 | 63 | |
| 56 | 64 | const DedupeTable = std.HashMapUnmanaged(ExtraSlice, void, ExtraSlice.Context, std.hash_map.default_max_load_percentage); |
| 57 | 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 | 145 | pub const Static = struct { |
| 138 | 146 | default_step: Step.Index, |
| 139 | 147 | generated_files_len: u32, |
| 148 | poisoned: bool, | |
| 140 | 149 | }; |
| 141 | 150 | |
| 142 | 151 | pub fn write(wip: *Wip, w: *Io.Writer, static: Static) Io.Writer.Error!void { |
| ... | ... | @@ -152,6 +161,9 @@ pub const Wip = struct { |
| 152 | 161 | |
| 153 | 162 | .default_step = static.default_step, |
| 154 | 163 | .generated_files_len = static.generated_files_len, |
| 164 | .flags = .{ | |
| 165 | .poisoned = static.poisoned, | |
| 166 | }, | |
| 155 | 167 | }; |
| 156 | 168 | var buffers = [_][]const u8{ |
| 157 | 169 | @ptrCast(&header), |
| ... | ... | @@ -3325,6 +3337,7 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration { |
| 3325 | 3337 | .extra = try arena.alloc(u32, header.extra_len), |
| 3326 | 3338 | .default_step = header.default_step, |
| 3327 | 3339 | .generated_files_len = header.generated_files_len, |
| 3340 | .poisoned = header.flags.poisoned, | |
| 3328 | 3341 | }; |
| 3329 | 3342 | var vecs = [_][]u8{ |
| 3330 | 3343 | result.string_bytes, |
src/main.zig+114-116| ... | ... | @@ -4970,6 +4970,7 @@ fn cmdBuild( |
| 4970 | 4970 | var system_pkg_dir_path: ?[]const u8 = null; |
| 4971 | 4971 | var debug_target: ?[]const u8 = null; |
| 4972 | 4972 | var debug_libc_paths_file: ?[]const u8 = null; |
| 4973 | var cache_poison: std.Build.Graph.CachePoison = .pure; | |
| 4973 | 4974 | |
| 4974 | 4975 | const self_exe_path = try process.executablePathAlloc(io, arena); |
| 4975 | 4976 | const default_seed = try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}); |
| ... | ... | @@ -5039,6 +5040,21 @@ fn cmdBuild( |
| 5039 | 5040 | try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len)); |
| 5040 | 5041 | configure_argv.appendAssumeCapacity(arg); |
| 5041 | 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 | 5058 | } else if (mem.eql(u8, arg, "--build-file")) { |
| 5043 | 5059 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); |
| 5044 | 5060 | i += 1; |
| ... | ... | @@ -5069,10 +5085,6 @@ fn cmdBuild( |
| 5069 | 5085 | i += 1; |
| 5070 | 5086 | override_global_cache_dir = args[i]; |
| 5071 | 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 | 5088 | } else if (mem.eql(u8, arg, "-freference-trace")) { |
| 5077 | 5089 | reference_trace = 256; |
| 5078 | 5090 | } else if (mem.eql(u8, arg, "--fetch")) { |
| ... | ... | @@ -5229,6 +5241,10 @@ fn cmdBuild( |
| 5229 | 5241 | for (cached_passthru_configure.items) |i| |
| 5230 | 5242 | config_man.hash.addBytes(configure_argv.items[i]); |
| 5231 | 5243 | |
| 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 | 5248 | // Normally the build runner is compiled for the host target but here is |
| 5233 | 5249 | // some code to help when debugging edits to the build runner so that you |
| 5234 | 5250 | // can make sure it compiles successfully on other targets. |
| ... | ... | @@ -5338,7 +5354,7 @@ fn cmdBuild( |
| 5338 | 5354 | |
| 5339 | 5355 | // This loop is re-evaluated when the build script exits with an indication that it |
| 5340 | 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 | 5358 | // We want to release all the locks before executing the child process, so we make a nice |
| 5343 | 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 | 5625 | _ = try config_man.addFilePath(exe_path, null); |
| 5610 | 5626 | configure_argv.items[0] = try exe_path.toString(arena); |
| 5611 | 5627 | |
| 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. | |
| 5618 | 5640 | } |
| 5619 | 5641 | } |
| 5620 | 5642 | |
| ... | ... | @@ -5636,7 +5658,7 @@ fn cmdBuild( |
| 5636 | 5658 | ); |
| 5637 | 5659 | defer config_tmp_file.close(io); |
| 5638 | 5660 | |
| 5639 | switch (term: { | |
| 5661 | const term = term: { | |
| 5640 | 5662 | const child_node = root_prog_node.start("Run Configure Script", 0); |
| 5641 | 5663 | defer child_node.end(); |
| 5642 | 5664 | var child = std.process.spawn(io, .{ |
| ... | ... | @@ -5647,101 +5669,86 @@ fn cmdBuild( |
| 5647 | 5669 | defer child.kill(io); |
| 5648 | 5670 | break :term child.wait(io) catch |err| |
| 5649 | 5671 | 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; | |
| 5696 | 5697 | } |
| 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() }); | |
| 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 | } | |
| 5702 | 5714 | |
| 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 | } | |
| 5720 | 5719 | |
| 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 }; | |
| 5735 | 5743 | } |
| 5736 | 5744 | }; |
| 5737 | 5745 | |
| 5738 | 5746 | { |
| 5739 | 5747 | // 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); | |
| 5742 | 5750 | |
| 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}); | |
| 5745 | 5752 | |
| 5746 | 5753 | make_argv.items[0] = try make_runner.exe_path.toString(arena); |
| 5747 | 5754 | make_argv.items[argv_index_configuration_file] = try configuration_path.toString(arena); |
| ... | ... | @@ -5749,33 +5756,24 @@ fn cmdBuild( |
| 5749 | 5756 | |
| 5750 | 5757 | if (!process.can_spawn) { |
| 5751 | 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 | } |
| 5754 | 5763 | |
| 5755 | switch (term: { | |
| 5764 | const term = term: { | |
| 5756 | 5765 | _ = try io.lockStderr(&.{}, .no_color); |
| 5757 | 5766 | defer io.unlockStderr(); |
| 5758 | 5767 | var child = std.process.spawn(io, .{ |
| 5759 | 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 | 5770 | defer child.kill(io); |
| 5762 | 5771 | 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 }); | |
| 5779 | 5777 | } |
| 5780 | 5778 | |
| 5781 | 5779 | const MakeRunner = struct { |