1const Maker = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5
6const std = @import("std");
7const Allocator = std.mem.Allocator;
8const Cache = std.Build.Cache;
9const Configuration = std.Build.Configuration;
10const File = std.Io.File;
11const Io = std.Io;
12const Dir = std.Io.Dir;
13const Path = std.Build.Cache.Path;
14const Reader = std.Io.Reader;
15const Writer = std.Io.Writer;
16const assert = std.debug.assert;
17const fatal = std.process.fatal;
18const fmt = std.fmt;
19const log = std.log;
20const mem = std.mem;
21const process = std.process;
22const Color = std.zig.Color;
23const Client = std.zig.Client;
24const Server = std.zig.Server;
25const EnvVar = std.zig.EnvVar;
26const default_local_zig_cache_basename = std.zig.default_local_zig_cache_basename;
27const stringToEnum = std.meta.stringToEnum;
28
29const Fuzz = @import("Maker/Fuzz.zig");
30const Graph = @import("Maker/Graph.zig");
31const Step = @import("Maker/Step.zig");
32const Watch = @import("Maker/Watch.zig");
33const WebServer = @import("Maker/WebServer.zig");
34const ScannedConfig = @import("Maker/ScannedConfig.zig");
35const PkgConfig = @import("Maker/PkgConfig.zig");
36const Fetch = @import("Maker/Fetch.zig");
37const Package = @import("Maker/Package.zig");
38
39pub const std_options: std.Options = .{
40 .side_channels_mitigations = .none,
41};
42
43gpa: Allocator,
44graph: *Graph,
45install_paths: InstallPaths,
46scanned_config: *const ScannedConfig,
47/// Includes an extra auto-generated placeholder Step at the end that indicates
48/// configure must be rerun. It is done this way so that the hot path of file
49/// system watching does not need to make any special cases, and to avoid more
50/// OS-specific logic in file system watching implementation.
51steps: []Step,
52generated_files: []Path,
53run_args: ?[]const []const u8,
54
55available_rss: u64,
56max_rss_is_default: bool,
57max_rss_mutex: Io.Mutex,
58skip_oom_steps: bool,
59unit_test_timeout_ns: ?u64,
60watch: bool,
61protocol_server: ?*AvoidableServer,
62protocol_server_mutex: Io.Mutex,
63web_server: ?*AvoidableWebServer,
64/// Allocated into `gpa`.
65memory_blocked_steps: std.ArrayList(Configuration.Step.Index),
66/// Allocated into `gpa`.
67initial_steps: std.array_hash_map.Auto(Configuration.Step.Index, void),
68/// Allocated into `gpa`.
69step_stack: std.array_hash_map.Auto(Configuration.Step.Index, void),
70pkg_config: PkgConfig,
71
72error_style: ErrorStyle,
73multiline_errors: MultilineErrors,
74summary: Summary,
75
76var safe_allocator_instance: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{});
77var stdio_buffer_allocation: [256]u8 = undefined;
78var stdout_writer_allocation: Io.File.Writer = undefined;
79var debug_maker_leaks: bool = false;
80
81const AvoidableServer = if (builtin.single_threaded) void else Server;
82const AvoidableWebServer = if (builtin.single_threaded) void else WebServer;
83
84const is_debug_mode = builtin.mode == .debug;
85const use_safe_allocator = switch (builtin.mode) {
86 .debug, .safe => true,
87 .fast, .small => false,
88};
89
90const InstallPaths = struct {
91 prefix: Path,
92 lib: Path,
93 bin: Path,
94 include: Path,
95};
96
97const PrintNode = struct {
98 parent: ?*PrintNode,
99 last: bool = false,
100};
101
102const ErrorStyle = enum {
103 verbose,
104 minimal,
105 verbose_clear,
106 minimal_clear,
107 fn verboseContext(s: ErrorStyle) bool {
108 return switch (s) {
109 .verbose, .verbose_clear => true,
110 .minimal, .minimal_clear => false,
111 };
112 }
113 fn clearOnUpdate(s: ErrorStyle) bool {
114 return switch (s) {
115 .verbose, .minimal => false,
116 .verbose_clear, .minimal_clear => true,
117 };
118 }
119};
120const MultilineErrors = enum { indent, newline, none };
121const Summary = enum { all, new, failures, line, none };
122const PrintConfiguration = enum { none, zon, path };
123
124/// Used to build the -M flags to pass to build-exe.
125pub const CliModule = struct {
126 name: []const u8,
127 root_path: []const u8,
128 deps: Deps = .empty,
129
130 const Deps = std.array_hash_map.String(*CliModule);
131
132 fn lower(cm: *const CliModule, arena: Allocator, gpa: Allocator, argv: *std.ArrayList([]const u8)) !void {
133 try argv.ensureUnusedCapacity(gpa, 2 * cm.deps.count() + 1);
134 for (cm.deps.keys(), cm.deps.values()) |name, dep| {
135 argv.appendAssumeCapacity("--dep");
136 if (mem.eql(u8, name, dep.name)) {
137 argv.appendAssumeCapacity(dep.name);
138 } else {
139 argv.appendAssumeCapacity(try arena.print("{s}={s}", .{ name, dep.name }));
140 }
141 }
142 argv.appendAssumeCapacity(try arena.print("-M{s}={s}", .{ cm.name, cm.root_path }));
143 }
144};
145
146pub fn main(init: process.Init.Minimal) !void {
147 // The build runner is long-lived in the following use cases:
148 // * `--watch` mode
149 // * `--webui` mode
150 // * `--fuzz` mode
151 // * A project that has a large, complex build graph.
152 const gpa = if (use_safe_allocator) safe_allocator_instance.allocator() else std.heap.smp_allocator;
153 defer if (use_safe_allocator) {
154 _ = safe_allocator_instance.deinit();
155 };
156
157 var threaded: std.Io.Threaded = .init(gpa, .{
158 .environ = init.environ,
159 .argv0 = .init(init.args),
160 });
161 defer threaded.deinit();
162 const io = threaded.io();
163
164 var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
165 defer arena_instance.deinit();
166 defer if (debugMakerLeaks()) log.debug("used {Bi} of arena", .{arena_instance.queryCapacity()});
167 const arena = arena_instance.allocator();
168
169 const args = try init.args.toSlice(arena);
170 var arg_i: usize = 1;
171 const cmd_name = nextArgOrFatal(args, &arg_i);
172 const zig_lib_arg = prefixedArgOrFatal(args, &arg_i, "--zig-lib=");
173 const zig_exe_arg = prefixedArgOrFatal(args, &arg_i, "--zig=");
174 const global_cache_arg = prefixedArgOrFatal(args, &arg_i, "--global-cache=");
175 const seed_arg = prefixedArgOrFatal(args, &arg_i, "--seed=");
176
177 const cwd: Dir = .cwd();
178
179 const zig_lib_directory: Cache.Directory = if (std.mem.eql(u8, zig_lib_arg, ".")) .cwd() else .{
180 .path = zig_lib_arg,
181 .handle = try cwd.openDir(io, zig_lib_arg, .{}),
182 };
183
184 const global_cache_directory: Cache.Directory = if (std.mem.eql(u8, global_cache_arg, ".")) .cwd() else .{
185 .path = global_cache_arg,
186 .handle = try cwd.createDirPathOpen(io, global_cache_arg, .{}),
187 };
188
189 var graph: Graph = .{
190 .io = io,
191 .arena = arena,
192 .cache = undefined,
193 .zig_exe = zig_exe_arg,
194 .environ_map = try init.environ.createMap(arena),
195 .global_cache_root = global_cache_directory,
196 .local_cache_root = undefined,
197 .zig_lib_directory = zig_lib_directory,
198 .build_root_directory = undefined,
199 .random_seed = parseRandomSeed(seed_arg),
200 };
201
202 const cmd = stringToEnum(enum { libc, init, fetch, build }, cmd_name) orelse
203 fatal("bad command name: {q}", .{cmd_name});
204 switch (cmd) {
205 .libc => return cmdLibC(gpa, &graph, args[arg_i..]),
206 .init => return cmdInit(gpa, &graph, args[arg_i..]),
207 .fetch => return cmdFetch(gpa, &graph, args[arg_i..]),
208 .build => {},
209 }
210
211 var step_names: std.ArrayList([]const u8) = .empty;
212 var help_menu = false;
213 var steps_menu = false;
214 var print_configuration: PrintConfiguration = .none;
215 var override_install_prefix: ?[]const u8 = null;
216 var override_lib_dir: ?[]const u8 = null;
217 var override_bin_dir: ?[]const u8 = null;
218 var override_include_dir: ?[]const u8 = null;
219 var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(&graph.environ_map);
220 var override_pkg_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_PKG_DIR.get(&graph.environ_map);
221 var error_style: ErrorStyle = .verbose;
222 var multiline_errors: MultilineErrors = .indent;
223 var summary: ?Summary = null;
224 var max_rss: u64 = 0;
225 var skip_oom_steps = false;
226 var test_timeout_ns: ?u64 = null;
227 var color: Color = .settingFromEnvironment(&graph.environ_map);
228 var watch_flag = false;
229 var fuzz: ?Fuzz.Mode = null;
230 var debounce_interval_ms: u16 = 50;
231 var listen: bool = false;
232 var webui_listen: ?Io.net.IpAddress = null;
233 var debug_pkg_config = false;
234 var run_args: ?[]const []const u8 = null;
235 var build_file: ?[]const u8 = null;
236
237 var configure_argv: std.ArrayList([]const u8) = .empty;
238 var cached_passthru_configure: std.ArrayList(u32) = .empty;
239 var forks: std.ArrayList(Fork) = .empty;
240 var system_pkg_dir_path: ?[]const u8 = null;
241 var fetch_only = false;
242 var fetch_mode: Fetch.JobQueue.Mode = .needed;
243 var debug_target: ?[]const u8 = null;
244 var cache_poison: std.Build.Graph.CachePoison = .pure;
245
246 if (EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.environ_map)) |str| {
247 if (stringToEnum(ErrorStyle, str)) |style| {
248 error_style = style;
249 }
250 }
251
252 if (EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.environ_map)) |str| {
253 if (stringToEnum(MultilineErrors, str)) |style| {
254 multiline_errors = style;
255 }
256 }
257
258 if (EnvVar.ZIG_BUILD_SUMMARY.get(&graph.environ_map)) |str| {
259 if (stringToEnum(Summary, str)) |value| {
260 summary = value;
261 }
262 }
263
264 try configure_argv.ensureUnusedCapacity(arena, 16);
265 try cached_passthru_configure.ensureUnusedCapacity(arena, 16);
266
267 _ = configure_argv.addOneAssumeCapacity(); // configurer executable
268 configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--zig", graph.zig_exe };
269 configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--build-root", undefined };
270 const conf_argv_index_build_root = configure_argv.items.len - 1;
271
272 while (nextArg(args, &arg_i)) |arg| {
273 if (mem.startsWith(u8, arg, "-")) {
274 try configure_argv.ensureUnusedCapacity(arena, 2);
275 if (mem.startsWith(u8, arg, "-D") or
276 mem.startsWith(u8, arg, "-fsys=") or
277 mem.startsWith(u8, arg, "-fno-sys=") or
278 mem.startsWith(u8, arg, "--release=") or
279 mem.eql(u8, arg, "--release"))
280 {
281 try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
282 configure_argv.appendAssumeCapacity(arg);
283 } else if (mem.eql(u8, arg, "--system")) {
284 system_pkg_dir_path = nextArgOrFatal(args, &arg_i);
285
286 try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
287 configure_argv.appendAssumeCapacity(arg); // Intentionally "--system" only; not the path.
288 } else if (mem.cutPrefix(u8, arg, "--color=")) |rest| {
289 color = stringToEnum(Color, rest) orelse
290 fatalWithHint("expected --color=[auto|on|off]; found {q}", .{arg});
291
292 try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
293 configure_argv.appendAssumeCapacity(arg);
294 } else if (mem.eql(u8, arg, "--color")) {
295 color = nextEnumArg(args, &arg_i, Color);
296
297 try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
298 configure_argv.appendAssumeCapacity(try arena.print("--color={t}", .{color}));
299 } else if (mem.eql(u8, arg, "--cache-poison")) {
300 cache_poison = .poisoned;
301 configure_argv.appendAssumeCapacity("--cache-poison=poisoned");
302 } else if (mem.cutPrefix(u8, arg, "--cache-poison=")) |rest| {
303 // We have to report parse failure here otherwise we would
304 // potentially get false positive cache hits for misspellings.
305 cache_poison = stringToEnum(std.Build.Graph.CachePoison, rest) orelse
306 fatalWithHint("expected --cache-poison=[pure|poisoned|disallowed|ignored]; found: {s}", .{arg});
307 if (cache_poison != .pure) configure_argv.appendAssumeCapacity(arg);
308 } else if (mem.eql(u8, arg, "--verbose")) {
309 // Intentionally is added both to make and configure but
310 // does not go into the cache hash.
311 configure_argv.appendAssumeCapacity(arg);
312 graph.verbose = true;
313 } else if (mem.eql(u8, arg, "--search-prefix")) {
314 const prefix = nextArgOrFatal(args, &arg_i);
315
316 // This argument is cache poisonous: it does not go into
317 // the cache and configurer must set the poison bit when
318 // choosing to observe it.
319 configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ arg, prefix };
320
321 try graph.search_prefixes.append(arena, prefix);
322 } else if (mem.eql(u8, arg, "--cache-dir")) {
323 override_local_cache_dir = nextArgOrFatal(args, &arg_i);
324 } else if (mem.eql(u8, arg, "--pkg-dir")) {
325 override_pkg_dir = nextArgOrFatal(args, &arg_i);
326 } else if (mem.eql(u8, arg, "--fetch")) {
327 fetch_only = true;
328 } else if (mem.cutPrefix(u8, arg, "--fetch=")) |rest| {
329 fetch_only = true;
330 fetch_mode = stringToEnum(Fetch.JobQueue.Mode, rest) orelse
331 fatal("expected [needed|all] after \"--fetch=\", found {q}", .{rest});
332 } else if (mem.cutPrefix(u8, arg, "--fork=")) |rest| {
333 try forks.append(arena, .init(rest));
334 } else if (mem.eql(u8, arg, "--fork")) {
335 try forks.append(arena, .init(nextArgOrFatal(args, &arg_i)));
336 } else if (mem.startsWith(u8, arg, "--zig-lib=")) {
337 fatal("--zig-lib= argument is special and must be first", .{});
338 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
339 help_menu = true;
340 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
341 steps_menu = true;
342 } else if (mem.eql(u8, arg, "--print-configuration")) {
343 print_configuration = .zon;
344 } else if (mem.eql(u8, arg, "--print-configuration-path")) {
345 print_configuration = .path;
346 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
347 override_install_prefix = nextArgOrFatal(args, &arg_i);
348 } else if (mem.eql(u8, arg, "--build-file")) {
349 build_file = nextArgOrFatal(args, &arg_i);
350 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
351 override_lib_dir = nextArgOrFatal(args, &arg_i);
352 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
353 override_bin_dir = nextArgOrFatal(args, &arg_i);
354 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
355 override_include_dir = nextArgOrFatal(args, &arg_i);
356 } else if (mem.eql(u8, arg, "--sysroot")) {
357 graph.sysroot = nextArgOrFatal(args, &arg_i);
358 } else if (mem.eql(u8, arg, "--maxrss")) {
359 const max_rss_text = nextArgOrFatal(args, &arg_i);
360 max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err|
361 fatal("invalid byte size {q}: {t}", .{ max_rss_text, err });
362 } else if (mem.eql(u8, arg, "--skip-oom-steps")) {
363 skip_oom_steps = true;
364 } else if (mem.eql(u8, arg, "--test-timeout")) {
365 const units: []const struct { []const u8, u64 } = &.{
366 .{ "ns", 1 },
367 .{ "nanosecond", 1 },
368 .{ "us", std.time.ns_per_us },
369 .{ "microsecond", std.time.ns_per_us },
370 .{ "ms", std.time.ns_per_ms },
371 .{ "millisecond", std.time.ns_per_ms },
372 .{ "s", std.time.ns_per_s },
373 .{ "second", std.time.ns_per_s },
374 .{ "m", std.time.ns_per_min },
375 .{ "minute", std.time.ns_per_min },
376 .{ "h", std.time.ns_per_hour },
377 .{ "hour", std.time.ns_per_hour },
378 };
379 const timeout_str = nextArgOrFatal(args, &arg_i);
380 const num_end_idx = std.mem.findLastNone(u8, timeout_str, "abcdefghijklmnopqrstuvwxyz") orelse fatal(
381 "invalid timeout {q}: expected unit (ns, us, ms, s, m, h)",
382 .{timeout_str},
383 );
384 const num_str = timeout_str[0 .. num_end_idx + 1];
385 const unit_str = timeout_str[num_end_idx + 1 ..];
386 const unit_factor: f64 = for (units) |unit_and_factor| {
387 if (std.mem.eql(u8, unit_str, unit_and_factor[0])) {
388 break @floatFromInt(unit_and_factor[1]);
389 }
390 } else fatal(
391 "invalid timeout {q}: invalid unit {q} (expected ns, us, ms, s, m, h)",
392 .{ timeout_str, unit_str },
393 );
394 const num_parsed = std.fmt.parseFloat(f64, num_str) catch |err| fatal(
395 "invalid timeout {q}: invalid number {q} ({t})",
396 .{ timeout_str, num_str, err },
397 );
398 test_timeout_ns = std.math.lossyCast(u64, unit_factor * num_parsed);
399 } else if (mem.eql(u8, arg, "--libc")) {
400 graph.libc_file = nextArgOrFatal(args, &arg_i);
401 } else if (mem.eql(u8, arg, "--error-style")) {
402 error_style = nextEnumArg(args, &arg_i, ErrorStyle);
403 } else if (mem.eql(u8, arg, "--multiline-errors")) {
404 multiline_errors = nextEnumArg(args, &arg_i, MultilineErrors);
405 } else if (mem.eql(u8, arg, "--summary")) {
406 summary = nextEnumArg(args, &arg_i, Summary);
407 } else if (mem.cutPrefix(u8, arg, "--seed=")) |rest| {
408 graph.random_seed = parseRandomSeed(rest);
409 } else if (mem.eql(u8, arg, "--build-id")) {
410 graph.build_id = .fast;
411 } else if (mem.cutPrefix(u8, arg, "--build-id=")) |style| {
412 graph.build_id = std.zig.BuildId.parse(style) catch |err|
413 fatal("unable to parse --build-id style {q}: {t}", .{ style, err });
414 } else if (mem.eql(u8, arg, "--debounce")) {
415 const next_arg = nextArg(args, &arg_i) orelse
416 fatalWithHint("expected u16 after {q}", .{arg});
417 debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| {
418 fatal("unable to parse debounce interval {q} as unsigned 16-bit integer: {t}", .{
419 next_arg, err,
420 });
421 };
422 } else if (mem.eql(u8, arg, "--listen=-")) {
423 listen = true;
424 } else if (mem.eql(u8, arg, "--webui")) {
425 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
426 } else if (mem.startsWith(u8, arg, "--webui=")) {
427 const addr_str = arg["--webui=".len..];
428 if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{});
429 webui_listen = Io.net.IpAddress.parseLiteral(addr_str) catch |err| {
430 fatal("invalid web UI address {q}: {t}", .{ addr_str, err });
431 };
432 } else if (mem.eql(u8, arg, "--debug-target")) {
433 debug_target = nextArgOrFatal(args, &arg_i);
434 } else if (mem.eql(u8, arg, "--debug-log")) {
435 try graph.debug_log_scopes.append(arena, nextArgOrFatal(args, &arg_i));
436 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
437 graph.debug_compile_errors = true;
438 } else if (mem.eql(u8, arg, "--debug-incremental")) {
439 graph.debug_incremental = true;
440 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
441 debug_pkg_config = true;
442 } else if (mem.eql(u8, arg, "--debug-rt")) {
443 graph.debug_compiler_runtime_libs = .debug;
444 } else if (mem.cutPrefix(u8, arg, "--debug-rt=")) |rest| {
445 graph.debug_compiler_runtime_libs = stringToEnum(std.lang.Optimize, rest) orelse
446 fatal("unrecognized optimization mode: {s}", .{rest});
447 } else if (is_debug_mode and mem.eql(u8, arg, "--debug-maker-leaks")) {
448 debug_maker_leaks = true;
449 } else if (mem.eql(u8, arg, "--libc-runtimes") or mem.eql(u8, arg, "--glibc-runtimes")) {
450 // --glibc-runtimes was the old name of the flag; kept for compatibility for now.
451 graph.libc_runtimes_dir = nextArgOrFatal(args, &arg_i);
452 } else if (mem.eql(u8, arg, "--verbose-air")) {
453 graph.verbose_air = true;
454 } else if (mem.eql(u8, arg, "--verbose-cc")) {
455 graph.verbose_cc = true;
456 } else if (mem.eql(u8, arg, "--verbose-link")) {
457 graph.verbose_link = true;
458 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
459 graph.verbose_llvm_ir = true;
460 } else if (mem.eql(u8, arg, "--watch")) {
461 watch_flag = true;
462 } else if (mem.eql(u8, arg, "--time-report")) {
463 graph.time_report = true;
464 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
465 } else if (mem.eql(u8, arg, "--fuzz")) {
466 fuzz = .{ .forever = undefined };
467 graph.fuzzing = true;
468 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
469 } else if (mem.startsWith(u8, arg, "--fuzz=")) {
470 const value = arg["--fuzz=".len..];
471 if (value.len == 0) fatal("missing argument to --fuzz", .{});
472
473 const unit: u8 = value[value.len - 1];
474 const digits = switch (unit) {
475 '0'...'9' => value,
476 'K', 'M', 'G' => value[0 .. value.len - 1],
477 else => fatal(
478 "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
479 .{},
480 ),
481 };
482
483 const amount = std.fmt.parseInt(u64, digits, 10) catch {
484 fatal(
485 "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
486 .{},
487 );
488 };
489
490 const normalized_amount = std.math.mul(u64, amount, switch (unit) {
491 else => unreachable,
492 '0'...'9' => 1,
493 'K' => 1000,
494 'M' => 1_000_000,
495 'G' => 1_000_000_000,
496 }) catch fatal("fuzzing limit amount overflows u64", .{});
497
498 fuzz = .{
499 .limit = .{
500 .amount = normalized_amount,
501 },
502 };
503 graph.fuzzing = true;
504 } else if (mem.eql(u8, arg, "-fincremental")) {
505 graph.incremental = true;
506 } else if (mem.eql(u8, arg, "-fno-incremental")) {
507 graph.incremental = false;
508 } else if (mem.eql(u8, arg, "-fwine")) {
509 graph.enable_wine = true;
510 } else if (mem.eql(u8, arg, "-fno-wine")) {
511 graph.enable_wine = false;
512 } else if (mem.eql(u8, arg, "-fqemu")) {
513 graph.enable_qemu = true;
514 } else if (mem.eql(u8, arg, "-fno-qemu")) {
515 graph.enable_qemu = false;
516 } else if (mem.eql(u8, arg, "-fwasmtime")) {
517 graph.enable_wasmtime = true;
518 } else if (mem.eql(u8, arg, "-fno-wasmtime")) {
519 graph.enable_wasmtime = false;
520 } else if (mem.eql(u8, arg, "-frosetta")) {
521 graph.enable_rosetta = true;
522 } else if (mem.eql(u8, arg, "-fno-rosetta")) {
523 graph.enable_rosetta = false;
524 } else if (mem.eql(u8, arg, "-fdarling")) {
525 graph.enable_darling = true;
526 } else if (mem.eql(u8, arg, "-fno-darling")) {
527 graph.enable_darling = false;
528 } else if (mem.eql(u8, arg, "-fallow-so-scripts")) {
529 graph.allow_so_scripts = true;
530 } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) {
531 graph.allow_so_scripts = false;
532 } else if (mem.eql(u8, arg, "-freference-trace")) {
533 graph.reference_trace = 256;
534 } else if (mem.cutPrefix(u8, arg, "-freference-trace=")) |num| {
535 graph.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err|
536 fatal("unable to parse reference_trace count {q}: {t}", .{ num, err });
537 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
538 graph.reference_trace = null;
539 } else if (mem.eql(u8, arg, "--error-limit")) {
540 const next_arg = nextArgOrFatal(args, &arg_i);
541 graph.error_limit = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err|
542 fatal("unable to parse error limit {q}: {t}", .{ next_arg, err });
543 } else if (mem.cutPrefix(u8, arg, "-j")) |text| {
544 const n = std.fmt.parseUnsigned(u32, text, 10) catch |err|
545 fatal("unable to parse jobs count {q}: {t}", .{ text, err });
546 if (n < 1) fatal("number of jobs must be at least 1", .{});
547 threaded.setAsyncLimit(.limited(n));
548 graph.max_jobs = n;
549 } else if (mem.eql(u8, arg, "--")) {
550 run_args = argsRest(args, arg_i);
551 break;
552 } else {
553 fatalWithHint("unrecognized argument: {s}", .{arg});
554 }
555 } else {
556 try step_names.append(arena, arg);
557 }
558 }
559
560 const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none;
561 const server_mode = !early_exit_mode and (watch_flag or webui_listen != null or fuzz != null or listen);
562
563 process.raiseFileDescriptorLimit();
564
565 const cwd_path = std.zig.getResolvedCwd(io, arena) catch |err|
566 fatal("resolving current directory path failed: {t}", .{err});
567
568 var build_root = try findBuildRoot(arena, io, .{
569 .cwd_path = cwd_path,
570 .build_file = build_file,
571 });
572 defer build_root.deinit(io);
573
574 graph.build_root_directory = build_root.directory;
575 graph.local_cache_root = if (override_local_cache_dir) |unresolved_path| std.zig.Directories.openUnresolved(
576 arena,
577 io,
578 cwd_path,
579 unresolved_path,
580 .@"local cache",
581 ) else .{
582 .path = try build_root.directory.join(arena, &.{default_local_zig_cache_basename}),
583 .handle = try build_root.directory.handle.createDirPathOpen(io, default_local_zig_cache_basename, .{}),
584 };
585 graph.cache = .{
586 .io = io,
587 .gpa = gpa,
588 .manifest_dir = try graph.local_cache_root.handle.createDirPathOpen(io, "h", .{}),
589 .cwd = cwd_path,
590 };
591
592 graph.cache.addPrefix(.{ .path = null, .handle = cwd });
593 graph.cache.addPrefix(zig_lib_directory);
594 graph.cache.addPrefix(graph.local_cache_root);
595 graph.cache.addPrefix(global_cache_directory);
596 graph.cache.addPrefix(graph.build_root_directory);
597 comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd));
598 comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib));
599 comptime assert(2 == @backingInt(std.zig.Server.Message.PathPrefix.local_cache));
600 comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache));
601 comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root));
602 comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5);
603
604 graph.cache.hash.addBytes(builtin.zig_version_string);
605
606 const NO_COLOR = EnvVar.NO_COLOR.isSet(&graph.environ_map);
607 const CLICOLOR_FORCE = EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map);
608
609 graph.stderr_mode = switch (color) {
610 .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),
611 .on => .escape_codes,
612 .off => .no_color,
613 };
614
615 const pkg_root: Path = if (override_pkg_dir) |p|
616 .initCwd(p)
617 else if (system_pkg_dir_path) |p|
618 .initCwd(p)
619 else
620 .{
621 .root_dir = build_root.directory,
622 .sub_path = "zig-pkg",
623 };
624
625 const main_progress_node = std.Progress.start(io, .{
626 .disable_printing = (graph.stderr_mode.? == .no_color),
627 });
628 defer main_progress_node.end();
629
630 const install_prefix_path: Path = if (graph.environ_map.get("DESTDIR")) |dest_dir| .{
631 .root_dir = .cwd(),
632 .sub_path = try Dir.path.join(arena, &.{ dest_dir, override_install_prefix orelse "/usr" }),
633 } else if (override_install_prefix) |cwd_relative| .{
634 .root_dir = .cwd(),
635 .sub_path = cwd_relative,
636 } else .{
637 .root_dir = graph.build_root_directory,
638 .sub_path = "zig-out",
639 };
640
641 // These three overrides are meant to be relative to the install prefix,
642 // not current working directory, unless absolute paths are used.
643 const install_lib_path: Path = if (override_lib_dir) |lib_dir|
644 if (Dir.path.isAbsolute(lib_dir)) .{
645 .root_dir = .cwd(),
646 .sub_path = lib_dir,
647 } else try install_prefix_path.join(arena, lib_dir)
648 else
649 try install_prefix_path.join(arena, "lib");
650
651 const install_bin_path: Path = if (override_bin_dir) |bin_dir|
652 if (Dir.path.isAbsolute(bin_dir)) .{
653 .root_dir = .cwd(),
654 .sub_path = bin_dir,
655 } else try install_prefix_path.join(arena, bin_dir)
656 else
657 try install_prefix_path.join(arena, "bin");
658
659 const install_include_path: Path = if (override_include_dir) |include_dir|
660 if (Dir.path.isAbsolute(include_dir)) .{
661 .root_dir = .cwd(),
662 .sub_path = include_dir,
663 } else try install_prefix_path.join(arena, include_dir)
664 else
665 try install_prefix_path.join(arena, "include");
666
667 const now = Io.Clock.Timestamp.now(io, .awake);
668
669 var web_server_allocation: AvoidableWebServer = undefined;
670 const web_server: ?*AvoidableWebServer = if (webui_listen) |listen_address| ws: {
671 if (watch_flag) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{});
672 if (builtin.single_threaded) fatal("--webui is not yet supported on single-threaded hosts", .{});
673 web_server_allocation = .init(.{
674 .graph = &graph,
675 .root_prog_node = main_progress_node,
676 .listen_address = listen_address,
677 .base_timestamp = now,
678 });
679 web_server_allocation.start() catch |err| fatal("failed to start web server: {t}", .{err});
680 break :ws &web_server_allocation;
681 } else null;
682
683 var stdin_buffer: [256]u8 = undefined;
684 var stdout_buffer: [256]u8 = undefined;
685 var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer);
686 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
687
688 var protocol_server_allocation: AvoidableServer = undefined;
689 const protocol_server: ?*AvoidableServer = if (listen) s: {
690 if (builtin.single_threaded) fatal("--listen is not yet supported on single-threaded hosts", .{});
691 if (watch_flag) fatal("using '--watch' and '--listen' together is not supported", .{});
692 if (fuzz != null) fatal("using '--fuzz' and '--listen' together is not supported", .{});
693 if (step_names.items.len > 0) fatal("build steps must be provided over the protocol instead of using CLI arguments", .{});
694 protocol_server_allocation = .{
695 .in = &stdin_reader.interface,
696 .out = &stdout_writer.interface,
697 };
698 try serveBSPHandshake(&protocol_server_allocation);
699 break :s &protocol_server_allocation;
700 } else null;
701
702 configure: while (true) {
703 // Set of files that, if modified, imply that recompiling and rerunning
704 // configurer is needed.
705 var configure_source_files: Cache.Manifest.Files = .empty;
706 defer Cache.Manifest.freeFiles(gpa, &configure_source_files);
707
708 // If this fails, we can still start the server and wait for user
709 // to request a rebuild. If it returns error.FailedButCacheIntact
710 // we can even still do file system watching and automatically
711 // rebuild on source changes.
712 if (configure(&graph, .{
713 .configure_argv = configure_argv.items,
714 .conf_argv_index_build_root = conf_argv_index_build_root,
715 .cached_passthru_configure = cached_passthru_configure.items,
716
717 .cache_poison = cache_poison,
718 .pkg_root = pkg_root,
719 .build_root = build_root,
720 .cwd_path = cwd_path,
721 .color = color,
722 .debug_target = debug_target,
723 .parent_progress_node = main_progress_node,
724 .fetch_mode = fetch_mode,
725 .system_pkg_dir_path = system_pkg_dir_path,
726 .fetch_only = fetch_only,
727 .print_configuration = print_configuration,
728 .forks = forks.items,
729 .src_files = &configure_source_files,
730 })) |scanned_config| {
731 if (help_menu) {
732 scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {
733 error.WriteFailed => return stdout_writer_allocation.err.?,
734 else => |e| return e,
735 };
736 try stdout_writer_allocation.flush();
737 return cleanExit(io, &scanned_config);
738 } else if (steps_menu) {
739 scanned_config.printSteps(&graph, initStdoutWriter(io)) catch |err| switch (err) {
740 error.WriteFailed => return stdout_writer_allocation.err.?,
741 else => |e| return e,
742 };
743 try stdout_writer_allocation.flush();
744 return cleanExit(io, &scanned_config);
745 } else switch (print_configuration) {
746 .none => {},
747 .zon => {
748 scanned_config.print(initStdoutWriter(io)) catch return stdout_writer_allocation.err.?;
749 try stdout_writer_allocation.flush();
750 return cleanExit(io, &scanned_config);
751 },
752 .path => unreachable,
753 }
754
755 var maker: Maker = .{
756 .gpa = gpa,
757 .graph = &graph,
758 .scanned_config = &scanned_config,
759 .install_paths = .{
760 .prefix = install_prefix_path,
761 .lib = install_lib_path,
762 .bin = install_bin_path,
763 .include = install_include_path,
764 },
765
766 // Extra step at the end which is the autogenerated placeholder
767 // step which indicates that we need to reconfigure.
768 .steps = try arena.alloc(Step, scanned_config.configuration.steps.len + 1),
769 .generated_files = try arena.alloc(Path, scanned_config.configuration.generated_files_len),
770 .run_args = run_args,
771
772 .available_rss = max_rss,
773 .max_rss_is_default = false,
774 .max_rss_mutex = .init,
775 .skip_oom_steps = skip_oom_steps,
776 .unit_test_timeout_ns = test_timeout_ns,
777
778 .watch = watch_flag,
779 .web_server = web_server,
780 .protocol_server = protocol_server,
781 .protocol_server_mutex = .init,
782 .memory_blocked_steps = .empty,
783 .initial_steps = .empty,
784 .step_stack = .empty,
785 .pkg_config = .{ .debug = debug_pkg_config },
786
787 .error_style = error_style,
788 .multiline_errors = multiline_errors,
789 .summary = summary orelse if (listen)
790 .none
791 else if (watch_flag or webui_listen != null)
792 .new
793 else
794 .failures,
795 };
796 defer {
797 maker.memory_blocked_steps.deinit(gpa);
798 maker.initial_steps.deinit(gpa);
799 maker.step_stack.deinit(gpa);
800 }
801
802 if (maker.available_rss == 0) {
803 maker.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
804 maker.max_rss_is_default = true;
805 }
806
807 if (protocol_server) |s| {
808 try s.serveStringMessage(.bsp_configuration, try arena.print("{f}", .{scanned_config.path}));
809
810 var watch: ?Watch = null;
811 defer if (watch) |*w| w.deinit();
812
813 const Event = union(enum) {
814 message: Reader.Error!Client.Message.Header,
815 fs_event: if (Watch.have_impl) @typeInfo(@TypeOf(Watch.wait)).@"fn".return_type.? else noreturn,
816 };
817
818 var select_buffer: [2]Event = undefined;
819 var select: Io.Select(Event) = .init(io, &select_buffer);
820 defer select.cancelDiscard();
821
822 try select.concurrent(.message, Server.receiveMessage, .{s});
823
824 var in_debounce = false;
825 loop: switch (try select.await()) {
826 .message => |payload| {
827 const header: Client.Message.Header = try payload;
828 switch (header.tag) {
829 .exit => {
830 cleanExit(io, &scanned_config);
831 process.exit(0);
832 },
833 .bsp_build_steps => {
834 // Cancel existing file watching
835 select.cancelDiscard();
836 in_debounce = false;
837
838 const body = try s.in.takeStruct(Client.Message.BuildSteps, .little);
839 const steps = try s.in.readSliceEndianAlloc(gpa, Configuration.Step.Index, body.step_count, .little);
840 defer gpa.free(steps);
841 if (body.flags.watch and !Watch.have_impl) fatal("file watching is unavailable", .{});
842
843 try select.concurrent(.message, Server.receiveMessage, .{s});
844
845 maker.watch = body.flags.watch;
846 maker.prepare(steps, &configure_source_files) catch |err| switch (err) {
847 error.DependencyLoopDetected, error.InsufficientMemory => {
848 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact
849 // and handle InsufficientMemory as error.AlreadyReported
850 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
851 process.exit(1);
852 },
853 else => |e| return e,
854 };
855
856 try maker.makeSteps(main_progress_node, null);
857
858 if (body.flags.watch) {
859 if (!Watch.have_impl) unreachable;
860 if (watch == null) watch = try .init(&maker);
861
862 try updateWatch(&maker, &watch.?);
863 try select.concurrent(.fs_event, Watch.wait, .{
864 &watch.?,
865 if (in_debounce) .{ .ms = debounce_interval_ms } else .none,
866 });
867 }
868
869 continue :loop try select.await();
870 },
871 else => fatal("unsupported message: {t}", .{header.tag}),
872 }
873 },
874 .fs_event => |payload| {
875 if (!Watch.have_impl) unreachable;
876 switch (payload catch |err| switch (err) {
877 error.MustReconfigure => {
878 try io.sleep(.fromMilliseconds(debounce_interval_ms), .awake);
879 continue :configure;
880 },
881 else => |e| fatal("file watching failed: {t}", .{e}),
882 }) {
883 .timeout => {
884 assert(in_debounce);
885 markFailedStepsDirty(&maker);
886 try maker.makeSteps(main_progress_node, null);
887 in_debounce = false;
888 },
889 .dirty => in_debounce = true,
890 .clean => {},
891 }
892 try select.concurrent(.fs_event, Watch.wait, .{
893 &watch.?,
894 if (in_debounce) .{ .ms = debounce_interval_ms } else .none,
895 });
896 continue :loop try select.await();
897 },
898 }
899 }
900
901 const initial_steps = try maker.resolveTopLevelSteps(step_names.items);
902 defer gpa.free(initial_steps);
903
904 maker.prepare(initial_steps, &configure_source_files) catch |err| switch (err) {
905 error.DependencyLoopDetected, error.InsufficientMemory => {
906 // TODO handle DependencyLoopDetected as error.FailedButCacheIntact
907 // and handle InsufficientMemory as error.AlreadyReported
908 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
909 process.exit(1);
910 },
911 else => |e| return e,
912 };
913
914 var w: Watch = w: {
915 if (!watch_flag) break :w undefined;
916 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{native_os});
917 break :w try .init(&maker);
918 };
919 defer w.deinit();
920
921 if (web_server) |ws| try ws.updateConfiguration(&maker);
922
923 rebuild: while (true) : (if (maker.error_style.clearOnUpdate()) {
924 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
925 defer io.unlockStderr();
926 stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H") catch |err| switch (err) {
927 error.WriteFailed => return stderr.file_writer.err.?,
928 };
929 }) {
930 try maker.makeSteps(main_progress_node, fuzz);
931
932 if (web_server) |ws| {
933 const c = &scanned_config.configuration;
934 assert(!watch_flag); // fatal error after CLI parsing
935 while (true) switch (try ws.wait()) {
936 .rebuild => {
937 for (maker.step_stack.keys()) |step_index| {
938 const step = maker.stepByIndex(step_index);
939 step.state = .precheck_done;
940 const deps = step_index.ptr(c).deps.slice(c);
941 step.pending_deps = @intCast(deps.len);
942 step.reset(&maker);
943 }
944 continue :rebuild;
945 },
946 };
947 }
948
949 if (!maker.watch) return;
950
951 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
952 if (!Watch.have_impl) unreachable;
953
954 try updateWatch(&maker, &w);
955
956 // Wait until a file system notification arrives. Read all such events
957 // until the buffer is empty. Then wait for a debounce interval, resetting
958 // if any more events come in. After the debounce interval has passed,
959 // trigger a rebuild on all steps with modified inputs, as well as their
960 // recursive dependants.
961 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
962 const caption = std.mem.print(&caption_buf, "watching {d} directories, {d} processes", .{
963 w.dir_count, countSubProcesses(&maker),
964 }) catch &caption_buf;
965 var debouncing_node = main_progress_node.start(caption, 0);
966 defer debouncing_node.end();
967 var in_debounce = false;
968 while (true) {
969 const timeout: Watch.Timeout = if (in_debounce) .{ .ms = debounce_interval_ms } else .none;
970 switch (w.wait(timeout) catch |err| switch (err) {
971 error.MustReconfigure => {
972 debouncing_node.end();
973 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
974 try io.sleep(.fromMilliseconds(debounce_interval_ms), .awake);
975 continue :configure;
976 },
977 else => |e| fatal("file watching failed: {t}", .{e}),
978 }) {
979 .timeout => {
980 assert(in_debounce);
981 debouncing_node.end();
982 debouncing_node = .none;
983 markFailedStepsDirty(&maker);
984 continue :rebuild;
985 },
986 .dirty => if (!in_debounce) {
987 in_debounce = true;
988 debouncing_node.end();
989 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
990 },
991 .clean => {},
992 }
993 }
994 }
995 } else |err| {
996 const can_fs_watch = switch (err) {
997 error.AlreadyReported => false,
998 error.FailedButCacheIntact => true,
999 else => |e| w: {
1000 log.err("configuration failed: {t}", .{e});
1001 break :w false;
1002 },
1003 };
1004 if (!server_mode) {
1005 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
1006 process.exit(1);
1007 }
1008 if (protocol_server != null) {
1009 fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{});
1010 }
1011 if (watch_flag and can_fs_watch) {
1012 fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{});
1013 } else {
1014 fatal("(zig build system) TODO stay running and wait for user to request rebuild even when build.zig compilation fails", .{});
1015 }
1016 }
1017 }
1018}
1019
1020/// Temporarily adds the reconfigure pseudostep to step_stack, calls
1021/// `Watch.update`, and then pops it again.
1022fn updateWatch(maker: *Maker, watch: *Watch) !void {
1023 const step_stack = &maker.step_stack;
1024 try step_stack.putNoClobber(maker.gpa, @fromBackingInt(@intCast(maker.steps.len - 1)), {});
1025 defer _ = step_stack.pop().?;
1026 try watch.update(step_stack.keys());
1027}
1028
1029const ConfigureOptions = struct {
1030 configure_argv: [][]const u8,
1031 conf_argv_index_build_root: usize,
1032 cached_passthru_configure: []const u32,
1033
1034 cache_poison: std.Build.Graph.CachePoison,
1035 pkg_root: Path,
1036 build_root: BuildRoot,
1037 cwd_path: []const u8,
1038 color: Color,
1039 debug_target: ?[]const u8,
1040 parent_progress_node: std.Progress.Node,
1041 fetch_mode: Fetch.JobQueue.Mode,
1042 system_pkg_dir_path: ?[]const u8,
1043 fetch_only: bool,
1044 print_configuration: PrintConfiguration,
1045 forks: []Fork,
1046 src_files: *Cache.Manifest.Files,
1047};
1048
1049fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1050 const configure_argv = options.configure_argv;
1051 const gpa = graph.cache.gpa;
1052 const io = graph.io;
1053 const arena = graph.arena;
1054
1055 configure_argv[options.conf_argv_index_build_root] = options.build_root.directory.path orelse options.cwd_path;
1056
1057 var http_client: std.http.Client = .{ .allocator = gpa, .io = io };
1058 defer http_client.deinit();
1059
1060 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};
1061 var fork_set: Package.Fetch.JobQueue.ForkSet = .{};
1062
1063 {
1064 // Populate fork_set.
1065 var group: Io.Group = .init;
1066 defer group.cancel(io);
1067
1068 for (options.forks) |*fork|
1069 group.async(io, Fork.load, .{ io, gpa, fork, options.color });
1070
1071 try group.await(io);
1072
1073 for (options.forks) |*fork| {
1074 if (fork.failed) return error.AlreadyReported;
1075 try fork_set.put(arena, .{
1076 .path = fork.path,
1077 .manifest_ast = fork.manifest_ast,
1078 .manifest = fork.manifest,
1079 .uses = 0,
1080 }, {});
1081 }
1082 }
1083 defer Fork.deinitList(options.forks);
1084
1085 var build_configurer_argv: std.ArrayList([]const u8) = .empty;
1086 defer build_configurer_argv.deinit(gpa);
1087
1088 var dependencies_source: std.ArrayList(u8) = .empty;
1089 defer dependencies_source.deinit(gpa);
1090
1091 const configurer_root_src_path: Cache.Path = .{
1092 .root_dir = graph.zig_lib_directory,
1093 .sub_path = "compiler/configurer.zig",
1094 };
1095
1096 const root_build_src_path: Cache.Path = .{
1097 .root_dir = options.build_root.directory,
1098 .sub_path = options.build_root.build_zig_basename,
1099 };
1100
1101 const configurer_exe_name = "configurer";
1102
1103 try build_configurer_argv.appendSlice(gpa, &.{
1104 graph.zig_exe, "build-exe", //
1105 "--cache-dir", graph.local_cache_root.path orelse ".", //
1106 "--global-cache-dir", graph.global_cache_root.path orelse ".", //
1107 "--build-root", graph.build_root_directory.path orelse ".", //
1108 "--zig-lib-dir", graph.zig_lib_directory.path orelse ".", //
1109 "--name", configurer_exe_name, //
1110 "-fsingle-threaded", //
1111 });
1112
1113 // Normally the build runner is compiled for the host target but here is
1114 // some code to help when debugging edits to the build runner so that you
1115 // can make sure it compiles successfully on other targets.
1116 const target_arch_os_abi: ?[]const u8 = if (options.debug_target) |triple| t: {
1117 try build_configurer_argv.appendSlice(gpa, &.{ "-target", triple });
1118 break :t triple;
1119 } else null;
1120
1121 if (graph.libc_file) |libc_file| {
1122 try build_configurer_argv.appendSlice(gpa, &.{ "--libc", libc_file });
1123 }
1124 if (graph.reference_trace) |n| {
1125 try build_configurer_argv.append(gpa, try arena.print("-freference-trace={d}", .{n}));
1126 }
1127 if (graph.debug_compile_errors) {
1128 try build_configurer_argv.append(gpa, "--debug-compile-errors");
1129 }
1130 try build_configurer_argv.appendSlice(gpa, &.{
1131 "--dep", "@build", //
1132 "--dep", "@dependencies", //
1133 try arena.print("-Mroot={f}", .{configurer_root_src_path}), //
1134 });
1135
1136 // In the loop below, after doing the fetch operation, the argv will be
1137 // truncated at this point, dependencies added, and then the
1138 // "--listen=-" arg appended at the end.
1139 const argv_deps_index = build_configurer_argv.items.len;
1140
1141 const build_mod = try arena.create(CliModule);
1142 build_mod.* = .{
1143 .name = "@build",
1144 .root_path = try root_build_src_path.toString(arena),
1145 };
1146
1147 const deps_mod = try arena.create(CliModule);
1148 deps_mod.* = .{
1149 .name = "@dependencies",
1150 .root_path = undefined,
1151 };
1152
1153 // This loop is re-evaluated when the build script exits with an indication that it
1154 // could not continue due to missing lazy dependencies.
1155 const configuration_path: Path, var configuration_lock: ?Cache.Lock = cp: while (true) {
1156 build_mod.deps.clearRetainingCapacity();
1157 deps_mod.deps.clearRetainingCapacity();
1158
1159 // Cache lookup for configure options. If we get a match, we can skip
1160 // execution of the configure script. If not, we get the file path to pass
1161 // to the configure process.
1162 //
1163 // In the hot path, we only check this cache, which means that also
1164 // configure source files need to go in here.
1165 var config_man_allocation: Cache.Manifest = undefined;
1166 const config_man: ?*Cache.Manifest = switch (options.cache_poison) {
1167 .pure, .disallowed, .ignored => m: {
1168 config_man_allocation = graph.cache.obtain();
1169
1170 for (options.cached_passthru_configure) |i|
1171 config_man_allocation.hash.addBytes(configure_argv[i]);
1172
1173 if (target_arch_os_abi) |triple|
1174 config_man_allocation.hash.addBytes(triple);
1175
1176 // Prevents a `zig build` from getting a false positive cache hit following
1177 // a `zig build --cache-poison=ignored`.
1178 config_man_allocation.hash.add(options.cache_poison == .ignored);
1179
1180 break :m &config_man_allocation;
1181 },
1182 .poisoned => null,
1183 };
1184 defer if (config_man) |man| man.deinit();
1185
1186 // We want to release all the locks before executing the child process, so we make a nice
1187 // big block here to ensure the cleanup gets run when we extract out our argv.
1188 {
1189 {
1190 const fetch_prog_node = options.parent_progress_node.start("Fetch Packages", 0);
1191 defer fetch_prog_node.end();
1192
1193 // Reset fork match counts.
1194 for (fork_set.keys()) |*fork| fork.uses = 0;
1195
1196 var job_queue: Package.Fetch.JobQueue = .{
1197 .io = io,
1198 .http_client = &http_client,
1199 .global_cache = graph.global_cache_root,
1200 .local_storage = &.{
1201 .cache_root = .{ .root_dir = graph.local_cache_root },
1202 .pkg_root = options.pkg_root,
1203 },
1204 .recursive = true,
1205 .debug_hash = false,
1206 .unlazy_set = unlazy_set,
1207 .fork_set = fork_set,
1208 .mode = options.fetch_mode,
1209 .prog_node = fetch_prog_node,
1210 .read_only = options.system_pkg_dir_path != null,
1211 };
1212 defer job_queue.deinit();
1213
1214 if (options.system_pkg_dir_path == null) {
1215 try http_client.initDefaultProxies(arena, &graph.environ_map);
1216 }
1217
1218 try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1);
1219 try job_queue.table.ensureUnusedCapacity(gpa, 1);
1220
1221 const phantom_package_root: Cache.Path = .{ .root_dir = options.build_root.directory };
1222
1223 var fetch: Package.Fetch = .{
1224 .arena = std.heap.ArenaAllocator.init(gpa),
1225 .location = .{ .relative_path = phantom_package_root },
1226 .location_tok = 0,
1227 .hash_tok = .none,
1228 .name_tok = 0,
1229 .lazy_status = .eager,
1230 .remote_package_root = phantom_package_root,
1231 .parent_package_root = phantom_package_root,
1232 .parent_manifest_ast = null,
1233 .prog_node = fetch_prog_node,
1234 .job_queue = &job_queue,
1235 .omit_missing_hash_error = true,
1236 .allow_missing_paths_field = false,
1237 .use_latest_commit = false,
1238
1239 .package_root = undefined,
1240 .error_bundle = undefined,
1241 .manifest = undefined,
1242 .manifest_ast = undefined,
1243 .have_manifest = false,
1244 .computed_hash = undefined,
1245 .has_build_zig = true,
1246 .oom_flag = false,
1247 .latest_commit = null,
1248
1249 .cli_module = build_mod,
1250 };
1251
1252 job_queue.all_fetches.appendAssumeCapacity(&fetch);
1253
1254 job_queue.table.putAssumeCapacityNoClobber(
1255 Package.Fetch.relativePathDigest(phantom_package_root, graph.global_cache_root),
1256 &fetch,
1257 );
1258
1259 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });
1260 try job_queue.group.await(io);
1261
1262 {
1263 // Ensure that forks were actually used. This is done
1264 // before printing manifest errors because using a fork can
1265 // prevent them.
1266 var any_unused = false;
1267 for (fork_set.keys()) |*fork| {
1268 if (fork.uses == 0) {
1269 log.err("fork {f} matched no {s} packages", .{
1270 fork.path, fork.manifest.name,
1271 });
1272 any_unused = true;
1273 } else {
1274 log.info("fork {f} matched {d} {s} packages", .{
1275 fork.path, fork.uses, fork.manifest.name,
1276 });
1277 }
1278 }
1279 if (any_unused) return error.FailedButCacheIntact;
1280 }
1281
1282 try job_queue.consolidateErrors();
1283
1284 if (fetch.error_bundle.root_list.items.len > 0) {
1285 var errors = try fetch.error_bundle.toOwnedBundle("");
1286 errors.renderToStderr(io, .{}, options.color) catch process.exit(1);
1287 return error.FailedButCacheIntact;
1288 }
1289
1290 if (options.fetch_only) {
1291 _ = io.lockStderr(&.{}, .no_color) catch {};
1292 process.exit(0);
1293 }
1294
1295 // Create the dependencies.zig file for configurer to
1296 // obtain via `@import("@dependencies")`.
1297 {
1298 {
1299 dependencies_source.clearRetainingCapacity();
1300 var source_writer: Io.Writer.Allocating = .fromArrayList(gpa, &dependencies_source);
1301 defer dependencies_source = source_writer.toArrayList();
1302 job_queue.createDependenciesSource(&source_writer.writer) catch |err| switch (err) {
1303 error.WriteFailed => return error.OutOfMemory,
1304 };
1305 }
1306 // Atomically create the file in a directory named after the hash of its contents.
1307 var hh: Cache.HashHelper = .{};
1308 hh.addBytes(builtin.zig_version_string);
1309 hh.addBytes(dependencies_source.items);
1310 const hex_digest = hh.final();
1311 const dependencies_zig_path: Path = .{
1312 .root_dir = graph.local_cache_root,
1313 .sub_path = try arena.print("o/{s}/dependencies.zig", .{&hex_digest}),
1314 };
1315 var atomic_file = try dependencies_zig_path.root_dir.handle.createFileAtomic(
1316 io,
1317 dependencies_zig_path.sub_path,
1318 .{ .make_path = true, .replace = true },
1319 );
1320 defer atomic_file.deinit(io);
1321 atomic_file.file.writeStreamingAll(io, dependencies_source.items) catch |err|
1322 fatal("writing dependencies.zig contents: {t}", .{err});
1323 atomic_file.replace(io) catch |err|
1324 fatal("replacing {f}: {t}", .{ dependencies_zig_path, err });
1325
1326 deps_mod.root_path = try dependencies_zig_path.toString(arena);
1327 }
1328
1329 {
1330 // Add a CliModule for each package's build.zig.
1331 const hashes = job_queue.table.keys();
1332 const fetches = job_queue.table.values();
1333 try deps_mod.deps.ensureUnusedCapacity(arena, @intCast(hashes.len));
1334 for (hashes, fetches) |*hash, f| {
1335 if (f == &fetch) {
1336 // The first one is a dummy package for the current project.
1337 continue;
1338 }
1339 if (!f.has_build_zig)
1340 continue;
1341 const hash_slice = try arena.dupe(u8, hash.toSlice());
1342
1343 const m = try arena.create(CliModule);
1344 m.* = .{
1345 .root_path = try f.package_root.toString(arena),
1346 .name = hash_slice,
1347 };
1348 deps_mod.deps.putAssumeCapacityNoClobber(hash_slice, m);
1349 f.cli_module = m;
1350 }
1351
1352 // Each build.zig module needs access to each of its
1353 // dependencies' build.zig modules by name.
1354 for (fetches) |f| {
1355 const mod = f.cli_module orelse continue;
1356 if (!f.have_manifest) continue;
1357 const man = &f.manifest;
1358 const dep_names = man.dependencies.keys();
1359 try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len));
1360 for (dep_names, man.dependencies.values()) |name, dep| {
1361 const dep_digest = Package.Fetch.depDigest(
1362 f.package_root,
1363 graph.global_cache_root,
1364 dep,
1365 ) orelse continue;
1366 const dep_mod = job_queue.table.get(dep_digest).?.cli_module orelse continue;
1367 const name_cloned = try arena.dupe(u8, name);
1368 mod.deps.putAssumeCapacityNoClobber(name_cloned, dep_mod);
1369 }
1370 }
1371 }
1372
1373 // Lower module dependencies to CLI argv.
1374 build_configurer_argv.shrinkRetainingCapacity(argv_deps_index);
1375 for (deps_mod.deps.values()) |dep| {
1376 try build_configurer_argv.ensureUnusedCapacity(gpa, 2 * dep.deps.count() + 1);
1377 for (dep.deps.keys(), dep.deps.values()) |name, sub| {
1378 build_configurer_argv.appendAssumeCapacity("--dep");
1379 if (mem.eql(u8, name, sub.name)) {
1380 build_configurer_argv.appendAssumeCapacity(sub.name);
1381 } else {
1382 build_configurer_argv.appendAssumeCapacity(try arena.print("{s}={s}", .{
1383 name, sub.name,
1384 }));
1385 }
1386 }
1387 build_configurer_argv.appendAssumeCapacity(try arena.print("-M{s}={s}/{s}", .{
1388 dep.name, dep.root_path, std.zig.build_zig_basename,
1389 }));
1390 }
1391 try deps_mod.lower(arena, gpa, &build_configurer_argv);
1392 try build_mod.lower(arena, gpa, &build_configurer_argv);
1393
1394 try build_configurer_argv.append(gpa, "--listen=-");
1395 }
1396
1397 const compile_prog_node = options.parent_progress_node.start("Compile Configure Script", 0);
1398 defer compile_prog_node.end();
1399
1400 if (config_man) |man| {
1401 if (try man.hit(compile_prog_node)) {
1402 const digest = man.final();
1403 const path: Path = .{
1404 .root_dir = graph.local_cache_root,
1405 .sub_path = try arena.print("c/{s}", .{&digest}),
1406 };
1407 options.src_files.* = man.takeFiles();
1408 break :cp .{ path, man.toOwnedLock() };
1409 }
1410 }
1411 try graph.handleVerbose(null, null, build_configurer_argv.items);
1412 const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{
1413 .argv = build_configurer_argv.items,
1414 .cache_root = graph.local_cache_root,
1415 .root_name = configurer_exe_name,
1416 .environ_map = &graph.environ_map,
1417 .cache_manifest = config_man,
1418 .arch_os_abi = target_arch_os_abi,
1419 .progress_node = compile_prog_node,
1420 .skip_log_cmdline_on_compile_errors = !graph.verbose,
1421 })) |r| r.path else |err| return err;
1422 defer gpa.free(configure_exe_path.sub_path);
1423
1424 configure_argv[0] = try configure_exe_path.toString(arena);
1425 }
1426
1427 if (!process.can_spawn) {
1428 fatal("cannot spawn command on {t}: {f}", .{ native_os, @as(std.zig.SubprocessCommand, .{
1429 .argv = configure_argv,
1430 }) });
1431 }
1432
1433 const config_tmp_path: Path = .{
1434 .root_dir = graph.local_cache_root,
1435 .sub_path = try arena.print("tmp" ++ Dir.path.sep_str ++ "{x}", .{randInt(io, u64)}),
1436 };
1437 const config_tmp_file: Io.File = try config_tmp_path.root_dir.handle.createFile(
1438 io,
1439 config_tmp_path.sub_path,
1440 .{ .read = true, .exclusive = true },
1441 );
1442 defer config_tmp_file.close(io);
1443
1444 const term = term: {
1445 const child_node = options.parent_progress_node.start("Run Configure Script", 0);
1446 defer child_node.end();
1447 var child = process.spawn(io, .{
1448 .argv = configure_argv,
1449 .stdout = .{ .file = config_tmp_file },
1450 .progress_node = child_node,
1451 }) catch |err| fatal("failed to spawn configure script {q}: {t}", .{ configure_argv[0], err });
1452 defer child.kill(io);
1453 break :term child.wait(io) catch |err|
1454 fatal("failed to wait configure script {q}: {t}", .{ configure_argv[0], err });
1455 };
1456 if (!term.success()) {
1457 // Failure to produce the configuration file.
1458 fatal("configure command {f}: {f}", .{ term, @as(std.zig.SubprocessCommand, .{
1459 .argv = configure_argv,
1460 }) });
1461 }
1462 // Even though the file is designed to be sent directly to make
1463 // runner, we must load it now because:
1464 // * If it contains additional file dependencies, we need to
1465 // add them to `config_man` before obtaining the final digest.
1466 // * If it contains a set of lazy packages that need to be
1467 // fetched, we need to fetch those now and re-run configure.
1468 var configuration = Configuration.loadFile(arena, io, config_tmp_file) catch |err|
1469 fatal("failed to load configuration file {f}: {t}", .{ config_tmp_path, err });
1470
1471 if (configuration.unlazy_deps.len != 0) {
1472 var any_errors = false;
1473 for (configuration.unlazy_deps) |hash_string| {
1474 const hash = hash_string.slice(&configuration);
1475 assert(hash.len != 0);
1476 if (hash.len > Package.Hash.max_len) {
1477 log.err("invalid digest (length {d} exceeds maximum): {q}", .{ hash.len, hash });
1478 any_errors = true;
1479 continue;
1480 }
1481 log.info("fetching lazy dependency {s}", .{hash});
1482 try unlazy_set.put(arena, .fromSlice(hash), {});
1483 }
1484 if (any_errors) return error.FailedButCacheIntact;
1485 if (options.system_pkg_dir_path) |p| {
1486 // In this mode, the system needs to provide these packages; they
1487 // cannot be fetched by Zig.
1488 const s = Dir.path.sep_str;
1489 for (unlazy_set.keys()) |*hash| {
1490 log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ p, hash.toSlice() });
1491 }
1492 log.info("remote package fetching disabled due to --system mode", .{});
1493 log.info("dependencies might be avoidable depending on build configuration", .{});
1494 return error.FailedButCacheIntact;
1495 }
1496 continue :cp;
1497 }
1498
1499 if (config_man) |man| for (configuration.path_deps) |path_dep| {
1500 switch (path_dep.flags.mode) {
1501 .directory => {}, // TODO
1502 .contents => try man.addPathPost(try confPathDepToCachePath(arena, graph, &configuration, path_dep)),
1503 .metadata => {}, // TODO
1504 }
1505 };
1506
1507 // If it is poisoned, there is no point in moving it to cached
1508 // location. Just leave it in the tmp directory.
1509 if (configuration.poisoned) {
1510 break :cp .{ config_tmp_path, null };
1511 } else {
1512 const man = config_man.?;
1513 const digest = man.final();
1514 const final_path: Path = .{
1515 .root_dir = graph.local_cache_root,
1516 .sub_path = try arena.print("c/{s}", .{&digest}),
1517 };
1518 Io.Dir.rename(
1519 config_tmp_path.root_dir.handle,
1520 config_tmp_path.sub_path,
1521 final_path.root_dir.handle,
1522 final_path.sub_path,
1523 io,
1524 ) catch |err| retry: {
1525 const e = switch (err) {
1526 error.FileNotFound => e: {
1527 const dir_path = final_path.dirname().?;
1528 dir_path.root_dir.handle.createDirPath(io, dir_path.sub_path) catch |e|
1529 fatal("failed to create directory {f}: {t}", .{ dir_path, e });
1530 if (Io.Dir.rename(
1531 config_tmp_path.root_dir.handle,
1532 config_tmp_path.sub_path,
1533 final_path.root_dir.handle,
1534 final_path.sub_path,
1535 io,
1536 )) |_| break :retry else |e| break :e e;
1537 },
1538 else => |e| e,
1539 };
1540 fatal("failed to rename configuration file from {f} into {f}: {t}", .{
1541 config_tmp_path, final_path, e,
1542 });
1543 };
1544 man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err});
1545 options.src_files.* = man.takeFiles();
1546 break :cp .{ final_path, man.toOwnedLock() };
1547 }
1548 };
1549 // Hang on to the configuration file lock until we finish loading the configuration file.
1550 defer if (configuration_lock) |*l| l.release(io);
1551
1552 switch (options.print_configuration) {
1553 .path => {
1554 initStdoutWriter(io).print("{f}\n", .{configuration_path}) catch
1555 fatal("failed printing cache file path: {t}", .{stdout_writer_allocation.err.?});
1556 stdout_writer_allocation.flush() catch |err|
1557 fatal("failed printing cache file path: {t}", .{err});
1558 _ = io.lockStderr(&.{}, .no_color) catch {};
1559 process.exit(0);
1560 },
1561 .none, .zon => {},
1562 }
1563
1564 const configuration = c: {
1565 var file = configuration_path.root_dir.handle.openFile(io, configuration_path.sub_path, .{}) catch |err|
1566 fatal("failed to open configuration file {f}: {t}", .{ configuration_path, err });
1567 defer file.close(io);
1568 break :c Configuration.loadFile(arena, io, file) catch |err|
1569 fatal("failed to load configuration file {f}: {t}", .{ configuration_path, err });
1570 };
1571 // Technically if the configuration is marked as poisoned, we could
1572 // already delete the file now, but we leave it around in case the
1573 // maker process fails or crashes and it's helpful to be able to repeat
1574 // execution of the command line or otherwise inspect the configuration file.
1575 const c = &configuration;
1576 var top_level_steps: std.array_hash_map.String(Configuration.Step.Index) = .empty;
1577 for (configuration.steps, 0..) |*conf_step, step_index_usize| {
1578 if (conf_step.owner != .root) continue;
1579 const step_index: Configuration.Step.Index = @fromBackingInt(@intCast(step_index_usize));
1580 const flags = conf_step.flags(c);
1581 switch (flags.tag) {
1582 .top_level => {
1583 const name = step_index.ptr(c).name.slice(c);
1584 try top_level_steps.put(arena, name, step_index);
1585 },
1586 else => {},
1587 }
1588 }
1589 for (c.search_prefixes) |search_prefix| {
1590 try graph.search_prefixes.append(arena, search_prefix.slice(c));
1591 }
1592 return .{
1593 .configuration = configuration,
1594 .top_level_steps = top_level_steps,
1595 .path = configuration_path,
1596 };
1597}
1598
1599fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
1600 const environ_map = &graph.environ_map;
1601 const io = graph.io;
1602 const arena = graph.arena;
1603
1604 const color: Color = Color.settingFromEnvironment(environ_map);
1605 var opt_path_or_url: ?[]const u8 = null;
1606 var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(environ_map);
1607 var override_pkg_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_PKG_DIR.get(environ_map);
1608 var debug_hash: bool = false;
1609 var save: union(enum) {
1610 no,
1611 yes: ?[]const u8,
1612 exact: ?[]const u8,
1613 } = .no;
1614
1615 var arg_i: usize = 0;
1616 while (nextArg(args, &arg_i)) |arg| {
1617 if (mem.startsWith(u8, arg, "-")) {
1618 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
1619 try Io.File.stdout().writeStreamingAll(io, usage_fetch);
1620 return process.cleanExit(io);
1621 } else if (mem.eql(u8, arg, "--cache-dir")) {
1622 override_local_cache_dir = nextArgOrFatal(args, &arg_i);
1623 } else if (mem.eql(u8, arg, "--pkg-dir")) {
1624 override_pkg_dir = nextArgOrFatal(args, &arg_i);
1625 } else if (mem.eql(u8, arg, "--debug-hash")) {
1626 debug_hash = true;
1627 } else if (mem.eql(u8, arg, "--debug-log")) {
1628 try graph.debug_log_scopes.append(arena, nextArgOrFatal(args, &arg_i));
1629 } else if (mem.eql(u8, arg, "--save")) {
1630 save = .{ .yes = null };
1631 } else if (mem.cutPrefix(u8, arg, "--save=")) |rest| {
1632 save = .{ .yes = rest };
1633 } else if (mem.eql(u8, arg, "--save-exact")) {
1634 save = .{ .exact = null };
1635 } else if (mem.cutPrefix(u8, arg, "--save-exact=")) |rest| {
1636 save = .{ .exact = rest };
1637 } else {
1638 fatal("unrecognized parameter: {q}", .{arg});
1639 }
1640 } else if (opt_path_or_url != null) {
1641 fatal("unexpected extra parameter: {q}", .{arg});
1642 } else {
1643 opt_path_or_url = arg;
1644 }
1645 }
1646
1647 const path_or_url = opt_path_or_url orelse fatal("missing url or path parameter", .{});
1648
1649 var http_client: std.http.Client = .{ .allocator = gpa, .io = io };
1650 defer http_client.deinit();
1651
1652 try http_client.initDefaultProxies(arena, environ_map);
1653
1654 var root_prog_node = std.Progress.start(io, .{
1655 .root_name = "Fetch",
1656 });
1657 defer root_prog_node.end();
1658
1659 var local_storage: Fetch.LocalStorage = undefined;
1660 var build_root: BuildRoot = undefined;
1661 var build_root_initialized = false;
1662 defer if (build_root_initialized) build_root.deinit(io);
1663
1664 const cwd_path = try std.zig.getResolvedCwd(io, arena);
1665
1666 const local_storage_ptr = switch (save) {
1667 .no => null,
1668 .yes, .exact => ls: {
1669 build_root = try findBuildRoot(arena, io, .{ .cwd_path = cwd_path });
1670 build_root_initialized = true;
1671
1672 local_storage = .{
1673 .cache_root = if (override_local_cache_dir) |p| .initCwd(p) else .{
1674 .root_dir = build_root.directory,
1675 .sub_path = ".zig-cache",
1676 },
1677 .pkg_root = if (override_pkg_dir) |p| .initCwd(p) else .{
1678 .root_dir = build_root.directory,
1679 .sub_path = "zig-pkg",
1680 },
1681 };
1682
1683 break :ls &local_storage;
1684 },
1685 };
1686
1687 var job_queue: Fetch.JobQueue = .{
1688 .io = io,
1689 .http_client = &http_client,
1690 .global_cache = graph.global_cache_root,
1691 .local_storage = local_storage_ptr,
1692 .recursive = false,
1693 .read_only = false,
1694 .debug_hash = debug_hash,
1695 .mode = .all,
1696 .prog_node = root_prog_node,
1697 };
1698 defer job_queue.deinit();
1699
1700 var fetch: Fetch = .{
1701 .arena = std.heap.ArenaAllocator.init(gpa),
1702 .location = .{ .path_or_url = path_or_url },
1703 .location_tok = 0,
1704 .hash_tok = .none,
1705 .name_tok = 0,
1706 .lazy_status = .eager,
1707 .remote_package_root = undefined,
1708 .parent_package_root = undefined,
1709 .parent_manifest_ast = null,
1710 .prog_node = root_prog_node,
1711 .job_queue = &job_queue,
1712 .omit_missing_hash_error = true,
1713 .allow_missing_paths_field = false,
1714 .use_latest_commit = true,
1715
1716 .package_root = undefined,
1717 .error_bundle = undefined,
1718 .manifest = undefined,
1719 .manifest_ast = undefined,
1720 .have_manifest = false,
1721 .computed_hash = undefined,
1722 .has_build_zig = false,
1723 .oom_flag = false,
1724 .latest_commit = null,
1725
1726 .cli_module = null,
1727 };
1728 defer fetch.deinit();
1729
1730 fetch.run() catch |err| switch (err) {
1731 error.OutOfMemory, error.Canceled => |e| return e,
1732 error.FetchFailed => {}, // error bundle checked below
1733 };
1734
1735 try job_queue.group.await(io);
1736
1737 if (fetch.error_bundle.root_list.items.len > 0) {
1738 var errors = try fetch.error_bundle.toOwnedBundle("");
1739 errors.renderToStderr(io, .{}, color) catch {};
1740 process.exit(1);
1741 }
1742
1743 const package_hash = fetch.computedPackageHash();
1744 const package_hash_slice = package_hash.toSlice();
1745
1746 root_prog_node.end();
1747 root_prog_node = .{ .index = .none };
1748
1749 const name = switch (save) {
1750 .no => {
1751 var data: [2][]const u8 = .{ package_hash_slice, "\n" };
1752 const w = initStdoutWriter(io);
1753 w.writeVecAll(&data) catch return stdout_writer_allocation.err.?;
1754 try stdout_writer_allocation.flush();
1755 return process.cleanExit(io);
1756 },
1757 .yes, .exact => |name| name: {
1758 if (name) |n| break :name n;
1759 if (!fetch.have_manifest)
1760 fatal("unable to determine name; fetched package has no build.zig.zon file", .{});
1761 break :name fetch.manifest.name;
1762 },
1763 };
1764
1765 // The name to use in case the manifest file needs to be created now.
1766 const init_root_name = Dir.path.basename(build_root.directory.path orelse cwd_path);
1767 var manifest, var ast = try loadManifest(gpa, arena, io, .{
1768 .root_name = try sanitizeExampleName(arena, init_root_name),
1769 .dir = build_root.directory.handle,
1770 .color = color,
1771 });
1772 defer {
1773 manifest.deinit(gpa);
1774 ast.deinit(gpa);
1775 }
1776
1777 var fixups: std.zig.Ast.Render.Fixups = .{};
1778 defer fixups.deinit(gpa);
1779
1780 var saved_path_or_url = path_or_url;
1781
1782 if (fetch.latest_commit) |latest_commit| resolved: {
1783 const latest_commit_hex = try arena.print("{f}", .{latest_commit});
1784
1785 var uri = try std.Uri.parse(path_or_url);
1786
1787 if (uri.fragment) |fragment| {
1788 const target_ref = try fragment.toRawMaybeAlloc(arena);
1789
1790 // the refspec may already be fully resolved
1791 if (std.mem.eql(u8, target_ref, latest_commit_hex)) break :resolved;
1792
1793 log.info("resolved ref {q} to commit {s}", .{ target_ref, latest_commit_hex });
1794
1795 // include the original refspec in a query parameter, could be used to check for updates
1796 uri.query = .{ .percent_encoded = try arena.print("ref={f}", .{
1797 std.fmt.alt(fragment, .formatEscaped),
1798 }) };
1799 } else {
1800 log.info("resolved to commit {s}", .{latest_commit_hex});
1801 }
1802
1803 // replace the refspec with the resolved commit SHA
1804 uri.fragment = .{ .raw = latest_commit_hex };
1805
1806 switch (save) {
1807 .yes => saved_path_or_url = try arena.print("{f}", .{uri}),
1808 .no, .exact => {}, // keep the original URL
1809 }
1810 }
1811
1812 const new_node_init = try arena.print(
1813 \\.{{
1814 \\ .url = "{f}",
1815 \\ .hash = "{f}",
1816 \\ }}
1817 , .{
1818 std.zig.fmtString(saved_path_or_url),
1819 std.zig.fmtString(package_hash_slice),
1820 });
1821
1822 const new_node_text = try arena.print(".{f} = {s},\n", .{
1823 std.zig.fmtIdPU(name), new_node_init,
1824 });
1825
1826 const dependencies_init = try arena.print(".{{\n {s} }}", .{
1827 new_node_text,
1828 });
1829
1830 const dependencies_text = try arena.print(".dependencies = {s},\n", .{
1831 dependencies_init,
1832 });
1833
1834 if (manifest.dependencies.get(name)) |dep| {
1835 if (dep.hash) |h| {
1836 switch (dep.location) {
1837 .url => |u| {
1838 if (mem.eql(u8, h, package_hash_slice) and mem.eql(u8, u, saved_path_or_url)) {
1839 log.info("existing dependency named {q} is up-to-date", .{name});
1840 process.exit(0);
1841 }
1842 },
1843 .path => {},
1844 }
1845 }
1846
1847 const location_replace = try arena.print("{q}", .{saved_path_or_url});
1848 const hash_replace = try arena.print("{q}", .{package_hash_slice});
1849
1850 log.warn("overwriting existing dependency named {q}", .{name});
1851 try fixups.replace_nodes_with_string.put(gpa, dep.location_node, location_replace);
1852 if (dep.hash_node.unwrap()) |hash_node| {
1853 try fixups.replace_nodes_with_string.put(gpa, hash_node, hash_replace);
1854 } else {
1855 // https://github.com/ziglang/zig/issues/21690
1856 }
1857 } else if (manifest.dependencies.count() > 0) {
1858 // Add fixup for adding another dependency.
1859 const deps = manifest.dependencies.values();
1860 const last_dep_node = deps[deps.len - 1].node;
1861 try fixups.append_string_after_node.put(gpa, last_dep_node, new_node_text);
1862 } else if (manifest.dependencies_node.unwrap()) |dependencies_node| {
1863 // Add fixup for replacing the entire dependencies struct.
1864 try fixups.replace_nodes_with_string.put(gpa, dependencies_node, dependencies_init);
1865 } else {
1866 // Add fixup for adding dependencies struct.
1867 try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text);
1868 }
1869
1870 var aw: Io.Writer.Allocating = .init(gpa);
1871 defer aw.deinit();
1872 try ast.render(gpa, &aw.writer, fixups);
1873 const rendered = aw.written();
1874
1875 build_root.directory.handle.writeFile(io, .{ .sub_path = Package.Manifest.basename, .data = rendered }) catch |err| {
1876 fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err });
1877 };
1878
1879 return process.cleanExit(io);
1880}
1881
1882const usage_fetch =
1883 \\Usage: zig fetch [options] <url>
1884 \\Usage: zig fetch [options] <path>
1885 \\
1886 \\ Copy a package into the global cache and print its hash.
1887 \\ <url> must point to one of the following:
1888 \\ - A git+http / git+https server for the package
1889 \\ - A tarball file (with or without compression) containing
1890 \\ package source
1891 \\ - A git bundle file containing package source
1892 \\
1893 \\Examples:
1894 \\
1895 \\ zig fetch --save git+https://example.com/andrewrk/fun-example-tool.git
1896 \\ zig fetch --save https://example.com/andrewrk/fun-example-tool/archive/refs/heads/master.tar.gz
1897 \\
1898 \\Options:
1899 \\ -h, --help Print this help and exit
1900 \\ --cache-dir [path] Override path to local cache directory
1901 \\ --pkg-dir [path] Override path to local package directory
1902 \\ --debug-hash Print verbose hash information to stdout
1903 \\ --debug-log [scope] Enable printing debug/info log messages for scope
1904 \\ --save Add the fetched package to build.zig.zon
1905 \\ --save=[name] Add the fetched package to build.zig.zon as name
1906 \\ --save-exact Add the fetched package to build.zig.zon, storing the URL verbatim
1907 \\ --save-exact=[name] Add the fetched package to build.zig.zon as name, storing the URL verbatim
1908 \\
1909;
1910
1911const usage_init =
1912 \\Usage: zig init
1913 \\
1914 \\ Initializes a `zig build` project in the current working
1915 \\ directory.
1916 \\
1917 \\Options:
1918 \\ -m, --minimal Use minimal init template
1919 \\ -h, --help Print this help and exit
1920 \\
1921 \\
1922;
1923
1924const usage_libc =
1925 \\Usage: zig libc
1926 \\
1927 \\ Detect the native libc installation and print the resulting
1928 \\ paths to stdout. You can save this into a file and then edit
1929 \\ the paths to create a cross compilation libc kit. Then you
1930 \\ can pass `--libc [file]` for Zig to use it.
1931 \\
1932 \\Usage: zig libc [paths_file]
1933 \\
1934 \\ Parse a libc installation text file and validate it.
1935 \\
1936 \\Options:
1937 \\ -h, --help Print this help and exit
1938 \\ -target [name] <arch><sub>-<os>-<abi> see the targets command
1939 \\ -includes Print the libc include directories for the target
1940 \\
1941;
1942
1943fn cmdInit(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
1944 const arena = graph.arena;
1945 const io = graph.io;
1946 const default_build_zig_basename = std.zig.build_zig_basename;
1947
1948 var template: enum { example, minimal } = .example;
1949 {
1950 var i: usize = 0;
1951 while (i < args.len) : (i += 1) {
1952 const arg = args[i];
1953 if (mem.startsWith(u8, arg, "-")) {
1954 if (mem.eql(u8, arg, "-m") or mem.eql(u8, arg, "--minimal")) {
1955 template = .minimal;
1956 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
1957 try Io.File.stdout().writeStreamingAll(io, usage_init);
1958 return process.cleanExit(io);
1959 } else {
1960 fatal("unrecognized parameter: {q}", .{arg});
1961 }
1962 } else {
1963 fatal("unexpected extra parameter: {q}", .{arg});
1964 }
1965 }
1966 }
1967
1968 const cwd_path = try std.zig.getResolvedCwd(io, arena);
1969 const cwd_basename = Dir.path.basename(cwd_path);
1970 const sanitized_root_name = try sanitizeExampleName(arena, cwd_basename);
1971
1972 const rng: std.Random.IoSource = .{ .io = io };
1973 const fingerprint: Package.Fingerprint = .generate(rng.interface(), sanitized_root_name);
1974
1975 switch (template) {
1976 .example => {
1977 var templates = Templates.find(gpa, io, graph.zig_lib_directory);
1978 defer templates.deinit(io);
1979
1980 const s = Dir.path.sep_str;
1981 const template_paths = [_][]const u8{
1982 default_build_zig_basename,
1983 Package.Manifest.basename,
1984 "src" ++ s ++ "main.zig",
1985 "src" ++ s ++ "root.zig",
1986 };
1987 var ok_count: usize = 0;
1988
1989 for (template_paths) |template_path| {
1990 if (templates.write(arena, io, Io.Dir.cwd(), sanitized_root_name, template_path, fingerprint)) |_| {
1991 log.info("created {s}", .{template_path});
1992 ok_count += 1;
1993 } else |err| switch (err) {
1994 error.PathAlreadyExists => log.info("preserving already existing file: {s}", .{
1995 template_path,
1996 }),
1997 else => log.err("unable to write {s}: {t}", .{ template_path, err }),
1998 }
1999 }
2000
2001 if (ok_count == template_paths.len) {
2002 log.info("see `zig build --help` for a menu of options", .{});
2003 }
2004 return process.cleanExit(io);
2005 },
2006 .minimal => {
2007 Templates.writeSimpleFile(io, Io.Dir.cwd(), Package.Manifest.basename,
2008 \\.{{
2009 \\ .name = .{s},
2010 \\ .version = "0.0.1",
2011 \\ .minimum_zig_version = "{s}",
2012 \\ .paths = .{{""}},
2013 \\ .fingerprint = 0x{x},
2014 \\}}
2015 \\
2016 , .{
2017 sanitized_root_name,
2018 builtin.zig_version_string,
2019 fingerprint.int(),
2020 }) catch |err| switch (err) {
2021 else => fatal("failed to create {q}: {t}", .{ Package.Manifest.basename, err }),
2022 error.PathAlreadyExists => fatal("refusing to overwrite {q}", .{Package.Manifest.basename}),
2023 };
2024 Templates.writeSimpleFile(io, Io.Dir.cwd(), default_build_zig_basename,
2025 \\const std = @import("std");
2026 \\
2027 \\pub fn build(b: *std.Build) void {{
2028 \\ _ = b; // stub
2029 \\}}
2030 \\
2031 , .{}) catch |err| switch (err) {
2032 else => fatal("failed to create {q}: {t}", .{ default_build_zig_basename, err }),
2033 // `build.zig` already existing is okay: the user has just used `zig init` to set up
2034 // their `build.zig.zon` *after* writing their `build.zig`. So this one isn't fatal.
2035 error.PathAlreadyExists => {
2036 log.info("successfully populated {q}, preserving existing {q}", .{
2037 Package.Manifest.basename, default_build_zig_basename,
2038 });
2039 return process.cleanExit(io);
2040 },
2041 };
2042 log.info("successfully populated {q} and {q}", .{ Package.Manifest.basename, default_build_zig_basename });
2043 return process.cleanExit(io);
2044 },
2045 }
2046}
2047
2048fn cmdLibC(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
2049 const environ_map = &graph.environ_map;
2050 const io = graph.io;
2051 const arena = graph.arena;
2052 const LibCInstallation = std.zig.LibCInstallation;
2053
2054 var input_file: ?[]const u8 = null;
2055 var target_arch_os_abi: []const u8 = "native";
2056 var print_includes: bool = false;
2057 const stdout = initStdoutWriter(io);
2058 {
2059 var i: usize = 0;
2060 while (i < args.len) : (i += 1) {
2061 const arg = args[i];
2062 if (mem.startsWith(u8, arg, "-")) {
2063 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
2064 try stdout.writeAll(usage_libc);
2065 try stdout.flush();
2066 return std.process.cleanExit(io);
2067 } else if (mem.eql(u8, arg, "-target")) {
2068 if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});
2069 i += 1;
2070 target_arch_os_abi = args[i];
2071 } else if (mem.eql(u8, arg, "-includes")) {
2072 print_includes = true;
2073 } else {
2074 fatal("unrecognized parameter: '{s}'", .{arg});
2075 }
2076 } else if (input_file != null) {
2077 fatal("unexpected extra parameter: '{s}'", .{arg});
2078 } else {
2079 input_file = arg;
2080 }
2081 }
2082 }
2083
2084 const target_query = std.zig.parseTargetQueryOrReportFatalError(gpa, .{
2085 .arch_os_abi = target_arch_os_abi,
2086 });
2087 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
2088
2089 if (print_includes) {
2090 const libc_installation: ?*LibCInstallation = libc: {
2091 if (input_file) |libc_file| {
2092 const libc = try arena.create(LibCInstallation);
2093 libc.* = LibCInstallation.parse(arena, io, libc_file, &target) catch |err| {
2094 fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err });
2095 };
2096 break :libc libc;
2097 } else {
2098 break :libc null;
2099 }
2100 };
2101
2102 const is_native_abi = target_query.isNativeAbi();
2103
2104 const libc_dirs = std.zig.LibCDirs.detect(
2105 arena,
2106 io,
2107 .{ .root_dir = graph.zig_lib_directory },
2108 &target,
2109 is_native_abi,
2110 true,
2111 libc_installation,
2112 environ_map,
2113 ) catch |err| {
2114 const zig_target = try target.zigTriple(arena);
2115 fatal("unable to detect libc for target {s}: {t}", .{ zig_target, err });
2116 };
2117
2118 if (libc_dirs.libc_include_dir_list.len == 0) {
2119 const zig_target = try target.zigTriple(arena);
2120 fatal("no include dirs detected for target {s}", .{zig_target});
2121 }
2122
2123 for (libc_dirs.libc_include_dir_list) |include_dir| {
2124 try stdout.writeAll(include_dir);
2125 try stdout.writeByte('\n');
2126 }
2127 try stdout.flush();
2128 return std.process.cleanExit(io);
2129 }
2130
2131 if (input_file) |libc_file| {
2132 var libc = LibCInstallation.parse(gpa, io, libc_file, &target) catch |err| {
2133 fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err });
2134 };
2135 defer libc.deinit(gpa);
2136 } else {
2137 if (!target_query.canDetectLibC()) {
2138 fatal("unable to detect libc for non-native target", .{});
2139 }
2140 var libc = LibCInstallation.findNative(gpa, io, .{
2141 .verbose = true,
2142 .target = &target,
2143 .environ_map = environ_map,
2144 }) catch |err| {
2145 fatal("unable to detect native libc: {t}", .{err});
2146 };
2147 defer libc.deinit(gpa);
2148
2149 try libc.render(stdout);
2150 try stdout.flush();
2151 }
2152}
2153
2154fn markFailedStepsDirty(maker: *Maker) void {
2155 const all_steps = maker.step_stack.keys();
2156
2157 for (all_steps) |step_index| {
2158 const step = maker.stepByIndex(step_index);
2159 switch (step.state) {
2160 .dependency_failure,
2161 .dependency_skipped,
2162 .failure,
2163 .skipped,
2164 => _ = maker.invalidateResult(step) catch |err| switch (err) {
2165 error.MustReconfigure => unreachable,
2166 },
2167 else => continue,
2168 }
2169 }
2170 // Now that all dirty steps have been found, the remaining steps that
2171 // succeeded from last run shall be marked "cached".
2172 for (all_steps) |step_index| {
2173 const step = maker.stepByIndex(step_index);
2174 switch (step.state) {
2175 .success => step.result_cached = true,
2176 else => continue,
2177 }
2178 }
2179}
2180
2181fn countSubProcesses(maker: *Maker) usize {
2182 const all_steps = maker.step_stack.keys();
2183 var count: usize = 0;
2184 for (all_steps) |step_index| {
2185 const s = maker.stepByIndex(step_index);
2186 count += @intFromBool(s.getZigProcess() != null);
2187 }
2188 return count;
2189}
2190
2191pub fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step {
2192 return &maker.steps[@backingInt(i)];
2193}
2194
2195fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const Configuration.Step.Index {
2196 const gpa = maker.gpa;
2197 const c = &maker.scanned_config.configuration;
2198
2199 if (step_names.len == 0) {
2200 return try gpa.dupe(Configuration.Step.Index, &.{c.default_step});
2201 }
2202
2203 var result: std.array_hash_map.Auto(Configuration.Step.Index, void) = .empty;
2204 defer result.deinit(gpa);
2205
2206 try result.ensureTotalCapacity(gpa, step_names.len);
2207
2208 for (0..step_names.len) |i| {
2209 const step_name = step_names[step_names.len - i - 1];
2210 const s = maker.scanned_config.top_level_steps.get(step_name) orelse {
2211 log.info("to list available steps: zig build -l", .{});
2212 fatal("no such step: {s}", .{step_name});
2213 };
2214 result.putAssumeCapacity(s, {});
2215 }
2216
2217 return try gpa.dupe(Configuration.Step.Index, result.keys());
2218}
2219
2220fn prepare(
2221 maker: *Maker,
2222 step_indices: []const Configuration.Step.Index,
2223 configure_source_files: *const Cache.Manifest.Files,
2224) !void {
2225 const gpa = maker.gpa;
2226 const graph = maker.graph;
2227 const arena = graph.arena;
2228 const seed: u32 = graph.random_seed;
2229 const initial_steps = &maker.initial_steps;
2230 const step_stack = &maker.step_stack;
2231 const c = &maker.scanned_config.configuration;
2232
2233 // The last element is a reserved special pseudostep which contains the
2234 // watch inputs for the configurer executable.
2235 for (maker.steps[0 .. maker.steps.len - 1], 0..) |*step, step_index_usize| {
2236 const step_index: Configuration.Step.Index = @fromBackingInt(@intCast(step_index_usize));
2237 step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) };
2238 }
2239 {
2240 const last_step = &maker.steps[maker.steps.len - 1];
2241 last_step.* = .{ .extended = .init(.top_level) };
2242 try last_step.setWatchInputsFromManifestFiles(maker, configure_source_files, graph.cache.prefixes());
2243 }
2244
2245 try initial_steps.ensureUnusedCapacity(gpa, step_indices.len);
2246 try step_stack.ensureUnusedCapacity(gpa, step_indices.len);
2247
2248 initial_steps.clearRetainingCapacity();
2249 step_stack.clearRetainingCapacity();
2250
2251 for (step_indices) |step| {
2252 initial_steps.putAssumeCapacity(step, {});
2253 step_stack.putAssumeCapacity(step, {});
2254 }
2255
2256 const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys());
2257
2258 var rng = std.Random.DefaultPrng.init(seed);
2259 const rand = rng.random();
2260 rand.shuffle(Configuration.Step.Index, starting_steps);
2261
2262 for (starting_steps) |s| {
2263 try constructGraphAndCheckForDependencyLoop(maker, s, &maker.step_stack, rand);
2264 }
2265
2266 {
2267 // Check that we have enough memory to complete the build.
2268 var any_problems = false;
2269 var max_needed: u64 = 0;
2270 for (step_stack.keys()) |step_index| {
2271 const make_step = maker.stepByIndex(step_index);
2272 const conf_step = step_index.ptr(c);
2273 const max_rss = conf_step.max_rss.toBytes();
2274 if (max_rss == 0) continue;
2275 max_needed = @max(max_needed, max_rss);
2276 if (max_rss > maker.available_rss) {
2277 if (maker.skip_oom_steps) {
2278 make_step.state = .skipped_oom;
2279 for (make_step.dependants.items) |dependant| {
2280 maker.stepByIndex(dependant).pending_deps -= 1;
2281 }
2282 } else {
2283 log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{
2284 conf_step.owner.depPrefixSlice(c),
2285 conf_step.name.slice(c),
2286 max_rss,
2287 maker.available_rss,
2288 });
2289 any_problems = true;
2290 }
2291 }
2292 }
2293 if (any_problems) {
2294 log.info("use --skip-oom-steps to proceed, skipping memory limited steps", .{});
2295 if (maker.max_rss_is_default) {
2296 log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{max_needed});
2297 }
2298 return error.InsufficientMemory;
2299 }
2300 }
2301}
2302
2303fn makeSteps(
2304 maker: *Maker,
2305 parent_progress_node: std.Progress.Node,
2306 fuzz: ?Fuzz.Mode,
2307) !void {
2308 const graph = maker.graph;
2309 const gpa = maker.gpa;
2310 const io = graph.io;
2311 const step_stack = &maker.step_stack;
2312 const top_level_steps = &maker.scanned_config.top_level_steps;
2313 const c = &maker.scanned_config.configuration;
2314
2315 if (maker.web_server) |ws| ws.startBuild();
2316
2317 if (maker.protocol_server) |s| {
2318 try s.serveBodylessMessage(.bsp_build_started);
2319 }
2320
2321 {
2322 // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer,
2323 // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking
2324 // a step is initial when it actually became ready due to an earlier initial step.
2325 var initial_set: std.ArrayList(Configuration.Step.Index) = .empty;
2326 defer initial_set.deinit(gpa);
2327 try initial_set.ensureUnusedCapacity(gpa, step_stack.count());
2328 for (step_stack.keys()) |step_index| {
2329 const s = maker.stepByIndex(step_index);
2330 if (s.state == .precheck_done and s.pending_deps == 0) {
2331 initial_set.appendAssumeCapacity(step_index);
2332 }
2333 }
2334
2335 const step_prog = parent_progress_node.start("steps", step_stack.count());
2336 defer step_prog.end();
2337
2338 var group: Io.Group = .init;
2339 defer group.cancel(io);
2340 // Start working on all of the initial steps...
2341 for (initial_set.items) |step_index| try stepReady(maker, &group, step_index, step_prog);
2342 // ...and `makeStep` will trigger every other step when their last dependency finishes.
2343 try group.await(io);
2344 }
2345
2346 if (maker.web_server) |ws| {
2347 if (fuzz) |mode| if (mode != .forever) fatal(
2348 "error: limited fuzzing is not implemented yet for --webui",
2349 .{},
2350 );
2351
2352 ws.finishBuild(.{ .fuzz = fuzz != null });
2353 }
2354
2355 if (maker.protocol_server) |s| {
2356 try s.serveBodylessMessage(.bsp_build_completed);
2357 }
2358
2359 assert(maker.memory_blocked_steps.items.len == 0);
2360
2361 var test_pass_count: usize = 0;
2362 var test_skip_count: usize = 0;
2363 var test_fail_count: usize = 0;
2364 var test_crash_count: usize = 0;
2365 var test_timeout_count: usize = 0;
2366
2367 var test_count: usize = 0;
2368
2369 var success_count: usize = 0;
2370 var skipped_count: usize = 0;
2371 var failure_count: usize = 0;
2372 var pending_count: usize = 0;
2373 var total_compile_errors: usize = 0;
2374
2375 var cleanup_task = io.async(cleanTmpFiles, .{ maker, step_stack.keys() });
2376 defer cleanup_task.await(io);
2377
2378 for (step_stack.keys()) |step_index| {
2379 const make_step = maker.stepByIndex(step_index);
2380 test_pass_count += make_step.test_results.passCount();
2381 test_skip_count += make_step.test_results.skip_count;
2382 test_fail_count += make_step.test_results.fail_count;
2383 test_crash_count += make_step.test_results.crash_count;
2384 test_timeout_count += make_step.test_results.timeout_count;
2385
2386 test_count += make_step.test_results.test_count;
2387
2388 switch (make_step.state) {
2389 .precheck_unstarted => unreachable,
2390 .precheck_started => unreachable,
2391 .precheck_done => unreachable,
2392 .dependency_failure, .dependency_skipped => pending_count += 1,
2393 .success => success_count += 1,
2394 .skipped, .skipped_oom => skipped_count += 1,
2395 .failure => {
2396 failure_count += 1;
2397 const compile_errors_len = make_step.result_error_bundle.errorMessageCount();
2398 if (compile_errors_len > 0) {
2399 total_compile_errors += compile_errors_len;
2400 }
2401 },
2402 }
2403 }
2404
2405 if (fuzz) |mode| blk: {
2406 switch (native_os) {
2407 // Current implementation depends on two things that need to be ported to Windows:
2408 // * Memory-mapping to share data between the fuzzer and build runner.
2409 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
2410 // many addresses to source locations).
2411 .windows => fatal("--fuzz not yet implemented for {t}", .{native_os}),
2412 else => {},
2413 }
2414 if (@bitSizeOf(usize) != 64) {
2415 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,
2416 // being compatible with file system's u64 return value. This is not the case
2417 // on 32-bit platforms.
2418 // Affects or affected by issues #5185, #22523, and #22464.
2419 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
2420 }
2421
2422 switch (mode) {
2423 .forever => break :blk,
2424 .limit => {},
2425 }
2426
2427 assert(mode == .limit);
2428 var f = Fuzz.init(maker, step_stack.keys(), parent_progress_node, mode) catch |err|
2429 fatal("failed to start fuzzer: {t}", .{err});
2430 defer f.deinit();
2431
2432 f.start();
2433 try f.waitAndPrintReport();
2434 }
2435
2436 // Every test has a state
2437 assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count);
2438
2439 if (failure_count == 0) {
2440 std.Progress.setStatus(.success);
2441 } else {
2442 std.Progress.setStatus(.failure);
2443 }
2444
2445 summary: {
2446 switch (maker.summary) {
2447 .all, .new, .line => {},
2448 .failures => if (failure_count == 0) break :summary,
2449 .none => break :summary,
2450 }
2451
2452 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
2453 defer io.unlockStderr();
2454 const t = stderr.terminal();
2455 const w = &stderr.file_writer.interface;
2456
2457 const total_count = success_count + failure_count + pending_count + skipped_count;
2458 t.setColor(.cyan) catch {};
2459 t.setColor(.bold) catch {};
2460 w.writeAll("Build Summary: ") catch {};
2461 t.setColor(.reset) catch {};
2462 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
2463 {
2464 t.setColor(.dim) catch {};
2465 var first = true;
2466 if (skipped_count > 0) {
2467 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};
2468 first = false;
2469 }
2470 if (failure_count > 0) {
2471 w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {};
2472 first = false;
2473 }
2474 if (!first) w.writeByte(')') catch {};
2475 t.setColor(.reset) catch {};
2476 }
2477
2478 if (test_count > 0) {
2479 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
2480 t.setColor(.dim) catch {};
2481 var first = true;
2482 if (test_skip_count > 0) {
2483 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};
2484 first = false;
2485 }
2486 if (test_fail_count > 0) {
2487 w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {};
2488 first = false;
2489 }
2490 if (test_crash_count > 0) {
2491 w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {};
2492 first = false;
2493 }
2494 if (test_timeout_count > 0) {
2495 w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {};
2496 first = false;
2497 }
2498 if (!first) w.writeByte(')') catch {};
2499 t.setColor(.reset) catch {};
2500 }
2501
2502 w.writeByte('\n') catch {};
2503
2504 if (maker.summary == .line) break :summary;
2505
2506 // Print a fancy tree with build results.
2507 var step_stack_copy = try step_stack.clone(gpa);
2508 defer step_stack_copy.deinit(gpa);
2509
2510 var print_node: PrintNode = .{ .parent = null };
2511 if (maker.initial_steps.count() == 0) {
2512 print_node.last = true;
2513 printTreeStep(maker, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) {
2514 error.Canceled => |e| return e,
2515 else => {},
2516 };
2517 } else {
2518 const last_index = if (maker.summary == .all) top_level_steps.count() else blk: {
2519 var i: usize = maker.initial_steps.count();
2520 while (i > 0) {
2521 i -= 1;
2522 const step_index = maker.initial_steps.keys()[i];
2523 const step = maker.stepByIndex(step_index);
2524 const found = switch (maker.summary) {
2525 .all, .line, .none => unreachable,
2526 .failures => step.state != .success,
2527 .new => !step.result_cached,
2528 };
2529 if (found) break :blk i;
2530 }
2531 break :blk top_level_steps.count();
2532 };
2533 for (maker.initial_steps.keys(), 0..) |step_index, i| {
2534 print_node.last = i + 1 == last_index;
2535 printTreeStep(maker, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) {
2536 error.Canceled => |e| return e,
2537 else => {},
2538 };
2539 }
2540 }
2541 w.writeByte('\n') catch {};
2542 }
2543
2544 if (maker.watch or maker.web_server != null or maker.protocol_server != null) return;
2545
2546 const code: u8 = code: {
2547 if (failure_count == 0) break :code 0; // success
2548 if (maker.error_style.verboseContext()) break :code 1; // failure; print build command
2549 break :code 2; // failure; do not print build command
2550 };
2551 if (code == 0) {
2552 removePoisonedConfiguration(io, maker.scanned_config);
2553 if (debugMakerLeaks()) return deinit(maker);
2554 }
2555 cleanup_task.await(io); // There is a defer above but an exit below.
2556 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
2557 process.exit(code);
2558}
2559
2560fn deinit(maker: *Maker) void {
2561 const gpa = maker.gpa;
2562 for (maker.steps) |*step| {
2563 step.clearResultStderr(gpa);
2564 step.clearFailedCommand(gpa);
2565 step.clearErrorBundle(gpa);
2566 step.inputs.deinit(gpa);
2567 }
2568}
2569
2570fn stepReady(
2571 maker: *Maker,
2572 group: *Io.Group,
2573 step_index: Configuration.Step.Index,
2574 root_prog_node: std.Progress.Node,
2575) Io.Cancelable!void {
2576 const graph = maker.graph;
2577 const io = graph.io;
2578 const c = &maker.scanned_config.configuration;
2579 const max_rss = step_index.ptr(c).max_rss.toBytes();
2580 if (max_rss != 0) {
2581 try maker.max_rss_mutex.lock(io);
2582 defer maker.max_rss_mutex.unlock(io);
2583 if (maker.available_rss < max_rss) {
2584 // Running this step right now could possibly exceed the allotted RSS.
2585 maker.memory_blocked_steps.append(maker.gpa, step_index) catch
2586 @panic("TODO eliminate memory allocation here");
2587 return;
2588 }
2589 maker.available_rss -= max_rss;
2590 }
2591 group.async(io, makeStep, .{ maker, group, step_index, root_prog_node });
2592}
2593
2594/// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready
2595/// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must
2596/// have already subtracted this value from `maker.available_rss`. This function will release the RSS
2597/// claim (i.e. add `s.max_rss` back into `maker.available_rss`) and queue any viable memory-blocked
2598/// steps after "make" completes for `s`.
2599fn makeStep(
2600 maker: *Maker,
2601 group: *Io.Group,
2602 step_index: Configuration.Step.Index,
2603 root_prog_node: std.Progress.Node,
2604) Io.Cancelable!void {
2605 const graph = maker.graph;
2606 const io = graph.io;
2607 const gpa = maker.gpa;
2608 const c = &maker.scanned_config.configuration;
2609 const conf_step = step_index.ptr(c);
2610 const step_name = conf_step.name.slice(c);
2611 const deps = conf_step.deps.slice(c);
2612 const make_step = maker.stepByIndex(step_index);
2613
2614 {
2615 const step_prog_node = root_prog_node.start(step_name, 0);
2616 defer step_prog_node.end();
2617
2618 if (maker.web_server) |ws| ws.updateStepStatus(step_index, .wip);
2619 if (maker.protocol_server) |s| {
2620 maker.protocol_server_mutex.lockUncancelable(io);
2621 defer maker.protocol_server_mutex.unlock(io);
2622
2623 s.serveU32Message(
2624 .bsp_step_started,
2625 @backingInt(step_index),
2626 ) catch @panic("TODO propagate error when failing to send protocol message");
2627 }
2628
2629 const new_state: Step.State = for (deps) |dep_index| {
2630 const dep_make_step = maker.stepByIndex(dep_index);
2631 switch (@atomicLoad(Step.State, &dep_make_step.state, .monotonic)) {
2632 .precheck_unstarted => unreachable,
2633 .precheck_started => unreachable,
2634 .precheck_done => unreachable,
2635
2636 .failure,
2637 .dependency_failure,
2638 => break .dependency_failure,
2639
2640 .dependency_skipped,
2641 .skipped_oom,
2642 .skipped,
2643 => break .dependency_skipped,
2644
2645 .success => {},
2646 }
2647 } else if (Step.make(step_index, maker, step_prog_node)) state: {
2648 break :state .success;
2649 } else |err| switch (err) {
2650 error.MakeFailed => .failure,
2651 error.MakeSkipped => .skipped,
2652 error.Canceled => |e| return e,
2653 };
2654
2655 @atomicStore(Step.State, &make_step.state, new_state, .monotonic);
2656
2657 const success = switch (new_state) {
2658 .precheck_unstarted => unreachable,
2659 .precheck_started => unreachable,
2660 .precheck_done => unreachable,
2661
2662 .failure,
2663 .dependency_failure,
2664 .dependency_skipped,
2665 .skipped_oom,
2666 .skipped,
2667 => false,
2668
2669 .success,
2670 => true,
2671 };
2672
2673 if (maker.web_server) |ws| {
2674 ws.updateStepStatus(step_index, if (success) .success else .failure);
2675 }
2676 if (maker.protocol_server != null) {
2677 maker.protocol_server_mutex.lockUncancelable(io);
2678 defer maker.protocol_server_mutex.unlock(io);
2679
2680 const status: Server.Message.BuildStepCompleted.Status = switch (new_state) {
2681 .precheck_unstarted => unreachable,
2682 .precheck_started => unreachable,
2683 .precheck_done => unreachable,
2684 .success => .success,
2685 .failure, .dependency_failure => .failure,
2686 .dependency_skipped, .skipped => .skipped,
2687 .skipped_oom => .skipped_oom,
2688 };
2689 serveBuildStepCompleted(
2690 maker,
2691 step_index,
2692 status,
2693 ) catch |err| std.debug.panic("TODO propagate error when failing to send protocol message: {t}", .{err});
2694 }
2695
2696 if (!success) std.Progress.setStatus(.failure_working);
2697 }
2698
2699 // No matter the result, we want to display error/warning messages.
2700 if (make_step.result_error_bundle.errorMessageCount() > 0 or
2701 make_step.result_error_msgs.items.len > 0 or
2702 make_step.result_stderr.len > 0)
2703 {
2704 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
2705 defer io.unlockStderr();
2706 printErrorMessages(maker, step_index, .{}, stderr.terminal(), maker.error_style, maker.multiline_errors) catch |err| switch (err) {
2707 error.Canceled => |e| return e,
2708 error.WriteFailed => switch (stderr.file_writer.err.?) {
2709 error.Canceled => |e| return e,
2710 else => {},
2711 },
2712 else => {},
2713 };
2714 }
2715
2716 const max_rss = conf_step.max_rss.toBytes();
2717 if (max_rss != 0) {
2718 var dispatch_set: std.ArrayList(Configuration.Step.Index) = .empty;
2719 defer dispatch_set.deinit(gpa);
2720
2721 // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set`
2722 // as a staging buffer to avoid recursing into `makeStep` while `maker.max_rss_mutex` is held.
2723 {
2724 try maker.max_rss_mutex.lock(io);
2725 defer maker.max_rss_mutex.unlock(io);
2726 maker.available_rss += max_rss;
2727 dispatch_set.ensureUnusedCapacity(gpa, maker.memory_blocked_steps.items.len) catch
2728 @panic("TODO eliminate memory allocation here");
2729 while (maker.memory_blocked_steps.last()) |candidate_index| {
2730 const candidate_max_rss = candidate_index.ptr(c).max_rss.toBytes();
2731 if (maker.available_rss < candidate_max_rss) break;
2732 assert(maker.memory_blocked_steps.pop() == candidate_index);
2733 dispatch_set.appendAssumeCapacity(candidate_index);
2734 }
2735 }
2736 for (dispatch_set.items) |candidate| {
2737 group.async(io, makeStep, .{ maker, group, candidate, root_prog_node });
2738 }
2739 }
2740
2741 for (make_step.dependants.items) |dependant_index| {
2742 const dependant = maker.stepByIndex(dependant_index);
2743 // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0.
2744 if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) {
2745 try stepReady(maker, group, dependant_index, root_prog_node);
2746 }
2747 }
2748}
2749
2750fn printTreeStep(
2751 maker: *Maker,
2752 step_index: Configuration.Step.Index,
2753 stderr: Io.Terminal,
2754 parent_node: *PrintNode,
2755 step_stack: *std.array_hash_map.Auto(Configuration.Step.Index, void),
2756) !void {
2757 const writer = stderr.writer;
2758 const first = step_stack.swapRemove(step_index);
2759 const summary = maker.summary;
2760 const c = &maker.scanned_config.configuration;
2761 const conf_step = step_index.ptr(c);
2762 const make_step = maker.stepByIndex(step_index);
2763 const skip = switch (summary) {
2764 .none, .line => unreachable,
2765 .all => false,
2766 .new => make_step.result_cached,
2767 .failures => make_step.state == .success,
2768 };
2769 if (skip) return;
2770 try printPrefix(parent_node, stderr);
2771
2772 if (parent_node.parent != null) {
2773 if (parent_node.last) {
2774 try printChildNodePrefix(stderr);
2775 } else {
2776 try writer.writeAll(switch (stderr.mode) {
2777 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
2778 else => "+- ",
2779 });
2780 }
2781 }
2782
2783 if (!first) try stderr.setColor(.dim);
2784
2785 // dep_prefix omitted here because it is redundant with the tree.
2786 try writer.writeAll(conf_step.name.slice(c));
2787
2788 const deps = conf_step.deps.slice(c);
2789
2790 if (first) {
2791 try printStepStatus(maker, step_index, stderr);
2792
2793 const last_index = if (summary == .all) deps.len -| 1 else blk: {
2794 var i: usize = deps.len;
2795 while (i > 0) {
2796 i -= 1;
2797
2798 const dep_index = deps[i];
2799 const dep = maker.stepByIndex(dep_index);
2800 const found = switch (summary) {
2801 .all, .line, .none => unreachable,
2802 .failures => dep.state != .success,
2803 .new => !dep.result_cached,
2804 };
2805 if (found) break :blk i;
2806 }
2807 break :blk deps.len -| 1;
2808 };
2809 for (deps, 0..) |dep, i| {
2810 var print_node: PrintNode = .{
2811 .parent = parent_node,
2812 .last = i == last_index,
2813 };
2814 try printTreeStep(maker, dep, stderr, &print_node, step_stack);
2815 }
2816 } else {
2817 if (deps.len == 0) {
2818 try writer.writeAll(" (reused)\n");
2819 } else {
2820 try writer.print(" (+{d} more reused dependencies)\n", .{deps.len});
2821 }
2822 try stderr.setColor(.reset);
2823 }
2824}
2825
2826fn printStepStatus(maker: *Maker, step_index: Configuration.Step.Index, stderr: Io.Terminal) !void {
2827 const s = maker.stepByIndex(step_index);
2828 const writer = stderr.writer;
2829 switch (s.state) {
2830 .precheck_unstarted => unreachable,
2831 .precheck_started => unreachable,
2832 .precheck_done => unreachable,
2833
2834 .dependency_failure => {
2835 try stderr.setColor(.dim);
2836 try writer.writeAll(" transitive failure\n");
2837 try stderr.setColor(.reset);
2838 },
2839
2840 .dependency_skipped => {
2841 try stderr.setColor(.dim);
2842 try writer.writeAll(" transitive skip\n");
2843 try stderr.setColor(.reset);
2844 },
2845
2846 .success => {
2847 try stderr.setColor(.green);
2848 if (s.result_cached) {
2849 try writer.writeAll(" cached");
2850 } else if (s.test_results.test_count > 0) {
2851 const pass_count = s.test_results.passCount();
2852 assert(s.test_results.test_count == pass_count + s.test_results.skip_count);
2853 try writer.print(" {d} pass", .{pass_count});
2854 if (s.test_results.skip_count > 0) {
2855 try stderr.setColor(.reset);
2856 try writer.writeAll(", ");
2857 try stderr.setColor(.yellow);
2858 try writer.print("{d} skip", .{s.test_results.skip_count});
2859 }
2860 try stderr.setColor(.reset);
2861 try writer.print(" ({d} total)", .{s.test_results.test_count});
2862 } else {
2863 try writer.writeAll(" success");
2864 }
2865 try stderr.setColor(.reset);
2866 if (s.result_duration_ns) |ns| {
2867 try stderr.setColor(.dim);
2868 if (ns >= std.time.ns_per_min) {
2869 try writer.print(" {d}m", .{ns / std.time.ns_per_min});
2870 } else if (ns >= std.time.ns_per_s) {
2871 try writer.print(" {d}s", .{ns / std.time.ns_per_s});
2872 } else if (ns >= std.time.ns_per_ms) {
2873 try writer.print(" {d}ms", .{ns / std.time.ns_per_ms});
2874 } else if (ns >= std.time.ns_per_us) {
2875 try writer.print(" {d}us", .{ns / std.time.ns_per_us});
2876 } else {
2877 try writer.print(" {d}ns", .{ns});
2878 }
2879 try stderr.setColor(.reset);
2880 }
2881 if (s.result_peak_rss != 0) {
2882 const rss = s.result_peak_rss;
2883 try stderr.setColor(.dim);
2884 if (rss >= 1000_000_000) {
2885 try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000});
2886 } else if (rss >= 1000_000) {
2887 try writer.print(" MaxRSS:{d}M", .{rss / 1000_000});
2888 } else if (rss >= 1000) {
2889 try writer.print(" MaxRSS:{d}K", .{rss / 1000});
2890 } else {
2891 try writer.print(" MaxRSS:{d}B", .{rss});
2892 }
2893 try stderr.setColor(.reset);
2894 }
2895 try writer.writeAll("\n");
2896 },
2897 .skipped => {
2898 try stderr.setColor(.yellow);
2899 try writer.writeAll(" skipped\n");
2900 try stderr.setColor(.reset);
2901 },
2902 .skipped_oom => {
2903 const c = &maker.scanned_config.configuration;
2904 const max_rss = step_index.ptr(c).max_rss.toBytes();
2905 try stderr.setColor(.yellow);
2906 try writer.writeAll(" skipped (not enough memory)");
2907 try stderr.setColor(.dim);
2908 try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{
2909 max_rss, maker.available_rss,
2910 });
2911 try stderr.setColor(.reset);
2912 },
2913 .failure => {
2914 try printStepFailure(maker, step_index, stderr, false);
2915 try stderr.setColor(.reset);
2916 },
2917 }
2918}
2919
2920fn printStepFailure(
2921 maker: *Maker,
2922 step_index: Configuration.Step.Index,
2923 stderr: Io.Terminal,
2924 dim: bool,
2925) !void {
2926 const w = stderr.writer;
2927 const s = maker.stepByIndex(step_index);
2928 if (s.result_error_bundle.errorMessageCount() > 0) {
2929 try stderr.setColor(.red);
2930 try w.print(" {d} errors\n", .{
2931 s.result_error_bundle.errorMessageCount(),
2932 });
2933 } else if (!s.test_results.isSuccess()) {
2934 // These first values include all of the test "statuses". Every test is either passsed,
2935 // skipped, failed, crashed, or timed out.
2936 try stderr.setColor(.green);
2937 try w.print(" {d} pass", .{s.test_results.passCount()});
2938 try stderr.setColor(.reset);
2939 if (dim) try stderr.setColor(.dim);
2940 if (s.test_results.skip_count > 0) {
2941 try w.writeAll(", ");
2942 try stderr.setColor(.yellow);
2943 try w.print("{d} skip", .{s.test_results.skip_count});
2944 try stderr.setColor(.reset);
2945 if (dim) try stderr.setColor(.dim);
2946 }
2947 if (s.test_results.fail_count > 0) {
2948 try w.writeAll(", ");
2949 try stderr.setColor(.red);
2950 try w.print("{d} fail", .{s.test_results.fail_count});
2951 try stderr.setColor(.reset);
2952 if (dim) try stderr.setColor(.dim);
2953 }
2954 if (s.test_results.crash_count > 0) {
2955 try w.writeAll(", ");
2956 try stderr.setColor(.red);
2957 try w.print("{d} crash", .{s.test_results.crash_count});
2958 try stderr.setColor(.reset);
2959 if (dim) try stderr.setColor(.dim);
2960 }
2961 if (s.test_results.timeout_count > 0) {
2962 try w.writeAll(", ");
2963 try stderr.setColor(.red);
2964 try w.print("{d} timeout", .{s.test_results.timeout_count});
2965 try stderr.setColor(.reset);
2966 if (dim) try stderr.setColor(.dim);
2967 }
2968 try w.print(" ({d} total)", .{s.test_results.test_count});
2969
2970 // Memory leaks are intentionally written after the total, because is isn't a test *status*,
2971 // but just a flag that any tests -- even passed ones -- can have. We also use a different
2972 // separator, so it looks like:
2973 // 2 pass, 1 skip, 2 fail (5 total); 2 leaks
2974 if (s.test_results.leak_count > 0) {
2975 try w.writeAll("; ");
2976 try stderr.setColor(.red);
2977 try w.print("{d} leaks", .{s.test_results.leak_count});
2978 try stderr.setColor(.reset);
2979 if (dim) try stderr.setColor(.dim);
2980 }
2981
2982 // It's usually not helpful to know how many error logs there were because they tend to
2983 // just come with other errors (e.g. crashes and leaks print stack traces, and clean
2984 // failures print error traces). So only mention them if they're the only thing causing
2985 // the failure.
2986 const show_err_logs: bool = show: {
2987 var alt_results = s.test_results;
2988 alt_results.log_err_count = 0;
2989 break :show alt_results.isSuccess();
2990 };
2991 if (show_err_logs) {
2992 try w.writeAll("; ");
2993 try stderr.setColor(.red);
2994 try w.print("{d} error logs", .{s.test_results.log_err_count});
2995 try stderr.setColor(.reset);
2996 if (dim) try stderr.setColor(.dim);
2997 }
2998
2999 try w.writeAll("\n");
3000 } else if (s.result_error_msgs.items.len > 0) {
3001 try stderr.setColor(.red);
3002 try w.writeAll(" failure\n");
3003 } else {
3004 assert(s.result_stderr.len > 0);
3005 try stderr.setColor(.red);
3006 try w.writeAll(" w\n");
3007 }
3008}
3009
3010fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void {
3011 const parent = node.parent orelse return;
3012 const writer = stderr.writer;
3013 if (parent.parent == null) return;
3014 try printPrefix(parent, stderr);
3015 if (parent.last) {
3016 try writer.writeAll(" ");
3017 } else {
3018 try writer.writeAll(switch (stderr.mode) {
3019 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
3020 else => "| ",
3021 });
3022 }
3023}
3024
3025fn printChildNodePrefix(stderr: Io.Terminal) !void {
3026 try stderr.writer.writeAll(switch (stderr.mode) {
3027 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
3028 else => "+- ",
3029 });
3030}
3031
3032/// Traverse the dependency graph depth-first and make it undirected by having
3033/// steps know their dependants (they only know dependencies at start).
3034/// Along the way, check that there is no dependency loop, and record the steps
3035/// in traversal order in `step_stack`.
3036/// Each step has its dependencies traversed in random order, this accomplishes
3037/// two things:
3038/// - `step_stack` will be in randomized-depth-first order, so the build runner
3039/// spawns initial steps in a random order
3040/// - each step's `dependants` list is also filled in a random order, so that
3041/// when it finishes executing in `makeStep`, it spawns next steps to run in
3042/// random order
3043fn constructGraphAndCheckForDependencyLoop(
3044 maker: *Maker,
3045 step_index: Configuration.Step.Index,
3046 step_stack: *std.array_hash_map.Auto(Configuration.Step.Index, void),
3047 rand: std.Random,
3048) error{ DependencyLoopDetected, OutOfMemory }!void {
3049 const c = &maker.scanned_config.configuration;
3050 const gpa = maker.gpa;
3051 const arena = maker.graph.arena;
3052 const make_step = maker.stepByIndex(step_index);
3053 switch (make_step.state) {
3054 .precheck_started => {
3055 log.err("dependency loop detected: {s}", .{step_index.ptr(c).name.slice(c)});
3056 return error.DependencyLoopDetected;
3057 },
3058 .precheck_unstarted => {
3059 make_step.state = .precheck_started;
3060
3061 const step = step_index.ptr(c);
3062 const dependencies = step.deps.slice(c);
3063 try step_stack.ensureUnusedCapacity(gpa, dependencies.len);
3064
3065 // We dupe to avoid shuffling the steps in the summary, it depends
3066 // on dependencies' order.
3067 const deps = try gpa.dupe(Configuration.Step.Index, dependencies);
3068 defer gpa.free(deps);
3069
3070 rand.shuffle(Configuration.Step.Index, deps);
3071
3072 for (deps) |dep| {
3073 const dep_step = maker.stepByIndex(dep);
3074 try step_stack.put(gpa, dep, {});
3075 try dep_step.dependants.append(arena, step_index);
3076 constructGraphAndCheckForDependencyLoop(maker, dep, step_stack, rand) catch |err| switch (err) {
3077 error.DependencyLoopDetected => {
3078 log.info("needed by: {s}", .{step_index.ptr(c).name.slice(c)});
3079 return err;
3080 },
3081 else => return err,
3082 };
3083 }
3084
3085 make_step.state = .precheck_done;
3086 make_step.pending_deps = @intCast(dependencies.len);
3087 },
3088 .precheck_done => {},
3089
3090 // These don't happen until we actually run the step graph.
3091 .dependency_failure => unreachable,
3092 .dependency_skipped => unreachable,
3093 .success => unreachable,
3094 .failure => unreachable,
3095 .skipped => unreachable,
3096 .skipped_oom => unreachable,
3097 }
3098}
3099
3100/// When file watching, prepares the step for being re-evaluated. Returns
3101/// `true` if the step was newly invalidated, `false` if it was already
3102/// invalidated.
3103pub fn invalidateResult(maker: *Maker, step: *Step) error{MustReconfigure}!bool {
3104 if (step == &maker.steps[maker.steps.len - 1]) return error.MustReconfigure;
3105 if (step.state == .precheck_done) return false;
3106 assert(step.pending_deps == 0);
3107 step.state = .precheck_done;
3108 step.reset(maker);
3109 for (step.dependants.items) |dependant_index| {
3110 const dependant = maker.stepByIndex(dependant_index);
3111 _ = try invalidateResult(maker, dependant);
3112 dependant.pending_deps += 1;
3113 }
3114 return true;
3115}
3116
3117pub fn printErrorMessages(
3118 maker: *Maker,
3119 failing_step_index: Configuration.Step.Index,
3120 options: std.zig.ErrorBundle.RenderOptions,
3121 stderr: Io.Terminal,
3122 error_style: ErrorStyle,
3123 multiline_errors: MultilineErrors,
3124) !void {
3125 const c = &maker.scanned_config.configuration;
3126 const gpa = maker.gpa;
3127 const writer = stderr.writer;
3128 if (error_style.verboseContext()) {
3129 // Provide context for where these error messages are coming from by
3130 // printing the corresponding Step subtree.
3131 var step_stack: std.ArrayList(Configuration.Step.Index) = .empty;
3132 defer step_stack.deinit(gpa);
3133 try step_stack.append(gpa, failing_step_index);
3134 while (true) {
3135 const last_step = maker.stepByIndex(step_stack.items[step_stack.items.len - 1]);
3136 if (last_step.dependants.items.len == 0) break;
3137 try step_stack.append(gpa, last_step.dependants.items[0]);
3138 }
3139
3140 // Now, `step_stack` has the subtree that we want to print, in reverse order.
3141 try stderr.setColor(.dim);
3142 var indent: usize = 0;
3143 while (step_stack.pop()) |step_index| : (indent += 1) {
3144 if (indent > 0) {
3145 try writer.splatByteAll(' ', (indent - 1) * 3);
3146 try printChildNodePrefix(stderr);
3147 }
3148
3149 try writer.writeAll(step_index.ptr(c).name.slice(c));
3150
3151 if (step_index == failing_step_index) {
3152 try printStepFailure(maker, step_index, stderr, true);
3153 } else {
3154 try writer.writeAll("\n");
3155 }
3156 }
3157 try stderr.setColor(.reset);
3158 } else {
3159 // Just print the failing step itself.
3160 try stderr.setColor(.dim);
3161 try writer.writeAll(failing_step_index.ptr(c).name.slice(c));
3162 try printStepFailure(maker, failing_step_index, stderr, true);
3163 try stderr.setColor(.reset);
3164 }
3165
3166 const failing_step = maker.stepByIndex(failing_step_index);
3167
3168 if (failing_step.result_stderr.len > 0) {
3169 try writer.writeAll(failing_step.result_stderr);
3170 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
3171 try writer.writeAll("\n");
3172 }
3173 }
3174
3175 try failing_step.result_error_bundle.renderToTerminal(options, stderr);
3176
3177 for (failing_step.result_error_msgs.items) |msg| {
3178 try stderr.setColor(.red);
3179 try writer.writeAll("error:");
3180 try stderr.setColor(.reset);
3181 if (std.mem.findScalar(u8, msg, '\n') == null) {
3182 try writer.print(" {s}\n", .{msg});
3183 } else switch (multiline_errors) {
3184 .indent => {
3185 var it = std.mem.splitScalar(u8, msg, '\n');
3186 try writer.print(" {s}\n", .{it.first()});
3187 while (it.next()) |line| {
3188 try writer.print(" {s}\n", .{line});
3189 }
3190 },
3191 .newline => try writer.print("\n{s}\n", .{msg}),
3192 .none => try writer.print(" {s}\n", .{msg}),
3193 }
3194 }
3195
3196 if (error_style.verboseContext()) {
3197 if (failing_step.result_failed_command) |cmd_str| {
3198 try stderr.setColor(.red);
3199 try writer.writeAll("failed command: ");
3200 try stderr.setColor(.reset);
3201 try writer.writeAll(cmd_str);
3202 try writer.writeByte('\n');
3203 }
3204 }
3205
3206 if (failing_step.result_oom) {
3207 try stderr.setColor(.red);
3208 try writer.writeAll("error information missing due to allocation failure");
3209 try stderr.setColor(.reset);
3210 try writer.writeByte('\n');
3211 }
3212
3213 try writer.writeByte('\n');
3214}
3215
3216fn nextArg(args: []const []const u8, i: *usize) ?[]const u8 {
3217 if (i.* >= args.len) return null;
3218 defer i.* += 1;
3219 return args[i.*];
3220}
3221
3222fn nextArgOrFatal(args: []const []const u8, i: *usize) []const u8 {
3223 return nextArg(args, i) orelse fatalWithHint("expected another argument after {q}", .{args[i.* - 1]});
3224}
3225
3226fn prefixedArgOrFatal(args: []const []const u8, i: *usize, prefix: []const u8) []const u8 {
3227 const arg = nextArgOrFatal(args, i);
3228 if (mem.cutPrefix(u8, arg, prefix)) |rest| return rest;
3229 fatal("expected {q} to instead begin with {q}", .{ arg, prefix });
3230}
3231
3232fn argsRest(args: []const []const u8, idx: usize) ?[]const []const u8 {
3233 if (idx >= args.len) return null;
3234 return args[idx..];
3235}
3236
3237fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
3238 log.info("to access the help menu: zig build -h", .{});
3239 fatal(f, args);
3240}
3241
3242fn cleanTmpFiles(maker: *Maker, steps: []const Configuration.Step.Index) void {
3243 const graph = maker.graph;
3244 const io = graph.io;
3245 const conf = &maker.scanned_config.configuration;
3246
3247 for (steps) |step_index| {
3248 const conf_step = step_index.ptr(conf);
3249 const wf = conf_step.extended.cast(conf, Configuration.Step.WriteFile) orelse continue;
3250 if (wf.flags.mode != .tmp) continue;
3251 const step = maker.stepByIndex(step_index);
3252 if (step.state != .success) continue;
3253 const tmp_path = generatedPath(maker, wf.generated_directory).*;
3254 tmp_path.root_dir.handle.deleteTree(io, tmp_path.subPathOrDot()) catch |err|
3255 log.warn("failed to delete temporary path {f}: {t}", .{ tmp_path, err });
3256 }
3257}
3258
3259fn serveBSPHandshake(s: *const std.zig.Server) !void {
3260 const handshake_header: Server.Message.Handshake = .{
3261 .version = Server.build_system_version,
3262 .flags = .{
3263 .file_system_watch_supported = Watch.have_impl,
3264 },
3265 };
3266 try s.serveMessageHeader(.{
3267 .tag = .bsp_handshake,
3268 .bytes_len = @sizeOf(Server.Message.Handshake),
3269 });
3270 try s.out.writeStruct(handshake_header, .little);
3271 try s.out.flush();
3272}
3273
3274fn serveBuildStepCompleted(
3275 maker: *Maker,
3276 step_index: Configuration.Step.Index,
3277 status: Server.Message.BuildStepCompleted.Status,
3278) !void {
3279 const s: *Server = maker.protocol_server.?;
3280 const step = maker.stepByIndex(step_index);
3281 const error_bundle = step.result_error_bundle;
3282
3283 const body: Server.Message.BuildStepCompleted = .{
3284 .step_index = step_index,
3285 .status = status,
3286 .error_bundle = .{
3287 .extra_len = @intCast(error_bundle.extra.len),
3288 .string_bytes_len = @intCast(error_bundle.string_bytes.len),
3289 },
3290 };
3291 const eb_bytes_len = @sizeOf(u32) * error_bundle.extra.len + error_bundle.string_bytes.len;
3292 const bytes_len = @sizeOf(Server.Message.BuildStepCompleted) + eb_bytes_len;
3293 try s.serveMessageHeader(.{
3294 .tag = .bsp_step_completed,
3295 .bytes_len = @intCast(bytes_len),
3296 });
3297 try s.out.writeStruct(body, .little);
3298 try s.out.writeSliceEndian(u32, error_bundle.extra, .little);
3299 try s.out.writeAll(error_bundle.string_bytes);
3300 try s.out.flush();
3301}
3302
3303fn initStdoutWriter(io: Io) *Writer {
3304 stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation);
3305 return &stdout_writer_allocation.interface;
3306}
3307
3308/// `asking_step` is only used for debugging purposes; it's the step being run
3309/// that is asking for the path.
3310pub fn resolveLazyPath(
3311 maker: *const Maker,
3312 arena: Allocator,
3313 lazy_path: Configuration.LazyPath,
3314 asking_step_index: Configuration.Step.Index,
3315) error{ OutOfMemory, MakeFailed }!Path {
3316 const c = &maker.scanned_config.configuration;
3317 return switch (lazy_path) {
3318 .source_path => |sp| try packagePath(maker, arena, sp.owner, sp.sub_path.slice(c)),
3319 .relative => |relative| relativePath(maker, arena, relative),
3320 .generated => |gen| {
3321 const base = generatedPath(maker, gen.index).*;
3322 var file_path = base;
3323 for (0..gen.flags.up) |_| {
3324 file_path.sub_path = Dir.path.dirname(file_path.sub_path) orelse {
3325 const s = stepByIndex(maker, asking_step_index);
3326 return s.fail(maker, "invalid LazyPath traversal: up {d} times from {f}", .{
3327 gen.flags.up, base,
3328 });
3329 };
3330 }
3331 return file_path.join(arena, gen.sub_path.slice(c));
3332 },
3333 };
3334}
3335
3336pub fn resolveLazyPathIndex(
3337 maker: *const Maker,
3338 arena: Allocator,
3339 lazy_path_index: Configuration.LazyPath.Index,
3340 asking_step_index: Configuration.Step.Index,
3341) error{ OutOfMemory, MakeFailed }!Path {
3342 const c = &maker.scanned_config.configuration;
3343 return resolveLazyPath(maker, arena, lazy_path_index.get(c), asking_step_index);
3344}
3345
3346/// `resolveLazyPath` is preferred, but this can be necessary when passing Path
3347/// objects to child processes.
3348pub fn resolveLazyPathAbs(
3349 maker: *const Maker,
3350 arena: Allocator,
3351 lazy_path: Configuration.LazyPath,
3352 asking_step_index: Configuration.Step.Index,
3353) error{ OutOfMemory, MakeFailed }![]const u8 {
3354 const p = try resolveLazyPath(maker, arena, lazy_path, asking_step_index);
3355 const root_dir_path = p.root_dir.path orelse return p.subPathOrDot();
3356 if (p.sub_path.len == 0) return root_dir_path;
3357 return Dir.path.join(arena, &.{ root_dir_path, p.sub_path });
3358}
3359
3360/// `resolveLazyPath` is preferred, but this can be necessary when passing Path
3361/// objects to child processes.
3362pub fn resolveLazyPathIndexAbs(
3363 maker: *const Maker,
3364 arena: Allocator,
3365 lazy_path_index: Configuration.LazyPath.Index,
3366 asking_step_index: Configuration.Step.Index,
3367) error{ OutOfMemory, MakeFailed }![]const u8 {
3368 const c = &maker.scanned_config.configuration;
3369 return resolveLazyPathAbs(maker, arena, lazy_path_index.get(c), asking_step_index);
3370}
3371
3372pub fn generatedPath(maker: *const Maker, index: Configuration.GeneratedFileIndex) *Path {
3373 return &maker.generated_files[@backingInt(index)];
3374}
3375
3376pub fn packagePath(
3377 maker: *const Maker,
3378 arena: Allocator,
3379 package_index: Configuration.Package.Index,
3380 sub_path: []const u8,
3381) Allocator.Error!Path {
3382 const c = &maker.scanned_config.configuration;
3383 const graph = maker.graph;
3384 const package = package_index.get(c) orelse return .{
3385 .root_dir = graph.build_root_directory,
3386 .sub_path = sub_path,
3387 };
3388
3389 // Currently, neither configurer nor Maker is aware of the standard zig
3390 // package path, and the root path is stored as a bare string rather than
3391 // relative to a known base directory. Without changing that, we must
3392 // construct a cwd relative path here.
3393 return .{
3394 .root_dir = .cwd(),
3395 .sub_path = try Dir.path.join(arena, &.{ package.root_path.slice(c), sub_path }),
3396 };
3397}
3398
3399pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configuration.LazyPath.Relative) Allocator.Error!Path {
3400 const graph = maker.graph;
3401 const c = &maker.scanned_config.configuration;
3402 const sub_path = relative.sub_path.slice(c);
3403 return switch (relative.flags.base) {
3404 .cwd => .{
3405 .root_dir = .cwd(),
3406 .sub_path = sub_path,
3407 },
3408 .local_cache => .{
3409 .root_dir = graph.local_cache_root,
3410 .sub_path = sub_path,
3411 },
3412 .global_cache => .{
3413 .root_dir = graph.global_cache_root,
3414 .sub_path = sub_path,
3415 },
3416 .build_root => .{
3417 .root_dir = graph.build_root_directory,
3418 .sub_path = sub_path,
3419 },
3420 .zig_exe => .{
3421 .root_dir = .cwd(),
3422 .sub_path = if (sub_path.len == 0)
3423 graph.zig_exe
3424 else
3425 try Io.Dir.path.join(arena, &.{ graph.zig_exe, sub_path }),
3426 },
3427 .zig_lib => .{
3428 .root_dir = graph.zig_lib_directory,
3429 .sub_path = sub_path,
3430 },
3431 .install_prefix => try maker.install_paths.prefix.join(arena, sub_path),
3432 .install_lib => try maker.install_paths.lib.join(arena, sub_path),
3433 .install_bin => try maker.install_paths.bin.join(arena, sub_path),
3434 .install_include => try maker.install_paths.include.join(arena, sub_path),
3435 };
3436}
3437
3438pub fn resolveInstallDir(
3439 maker: *Maker,
3440 arena: Allocator,
3441 dest_dir: Configuration.InstallDestDir,
3442) Allocator.Error!Path {
3443 const c = &maker.scanned_config.configuration;
3444 return switch (dest_dir.unpack().?) {
3445 .prefix => maker.install_paths.prefix,
3446 .lib => maker.install_paths.lib,
3447 .bin => maker.install_paths.bin,
3448 .header => maker.install_paths.include,
3449 .sub_path => |s| try maker.install_paths.prefix.join(arena, s.slice(c)),
3450 };
3451}
3452
3453pub fn installLazyPathSub(
3454 maker: *Maker,
3455 arena: Allocator,
3456 source: Configuration.LazyPath.Index,
3457 dest_dir: Configuration.InstallDestDir,
3458 sub_path: []const u8,
3459 asking_step_index: Configuration.Step.Index,
3460) !Dir.PrevStatus {
3461 const src_path = try resolveLazyPathIndex(maker, arena, source, asking_step_index);
3462 const dest_dir_path = try resolveInstallDir(maker, arena, dest_dir);
3463 const dest_path = try dest_dir_path.join(arena, sub_path);
3464 return installPath(maker, arena, src_path, dest_path, asking_step_index);
3465}
3466
3467pub fn installLazyPath(
3468 maker: *Maker,
3469 arena: Allocator,
3470 source: Configuration.LazyPath.Index,
3471 dest_dir: Configuration.InstallDestDir,
3472 asking_step_index: Configuration.Step.Index,
3473) !Dir.PrevStatus {
3474 const src_path = try resolveLazyPathIndex(maker, arena, source, asking_step_index);
3475 const dest_dir_path = try resolveInstallDir(maker, arena, dest_dir);
3476 const dest_path = try dest_dir_path.join(arena, src_path.basename());
3477 return installPath(maker, arena, src_path, dest_path, asking_step_index);
3478}
3479
3480pub fn installGenerated(
3481 maker: *Maker,
3482 arena: Allocator,
3483 source: Configuration.GeneratedFileIndex,
3484 dest_dir: Configuration.InstallDestDir,
3485 asking_step_index: Configuration.Step.Index,
3486) !Dir.PrevStatus {
3487 const src_path = generatedPath(maker, source).*;
3488 const dest_dir_path = try resolveInstallDir(maker, arena, dest_dir);
3489 const dest_path = try dest_dir_path.join(arena, src_path.basename());
3490 return installPath(maker, arena, src_path, dest_path, asking_step_index);
3491}
3492
3493pub fn truncatePath(
3494 maker: *Maker,
3495 arena: Allocator,
3496 dest_path: Path,
3497 asking_step_index: Configuration.Step.Index,
3498) Step.ExtendedMakeError!void {
3499 const graph = maker.graph;
3500 const io = graph.io;
3501 if (graph.verbose) try graph.handleVerbose(null, null, &.{
3502 "truncate", try dest_path.toString(arena),
3503 });
3504 const err = e: {
3505 var file = f: {
3506 break :f dest_path.root_dir.handle.createFile(io, dest_path.sub_path, .{}) catch |err| switch (err) {
3507 error.FileNotFound => {
3508 const parent_path = dest_path.dirname() orelse break :e err;
3509 parent_path.root_dir.handle.createDirPath(io, parent_path.sub_path) catch |in| switch (in) {
3510 error.Canceled => |e| return e,
3511 else => |e| {
3512 const s = stepByIndex(maker, asking_step_index);
3513 return s.fail(maker, "failed creating directory {f}: {t}", .{ parent_path, e });
3514 },
3515 };
3516 break :f dest_path.root_dir.handle.createFile(io, dest_path.sub_path, .{}) catch |in| break :e in;
3517 },
3518 error.Canceled => |e| return e,
3519 else => |e| break :e e,
3520 };
3521 };
3522 file.close(io);
3523 return;
3524 };
3525 const s = stepByIndex(maker, asking_step_index);
3526 return s.fail(maker, "failed truncating file {f}: {t}", .{ dest_path, err });
3527}
3528
3529pub fn installPath(
3530 maker: *Maker,
3531 arena: Allocator,
3532 src_path: Path,
3533 dest_path: Path,
3534 asking_step_index: Configuration.Step.Index,
3535) Step.ExtendedMakeError!Dir.PrevStatus {
3536 const graph = maker.graph;
3537 const io = graph.io;
3538 if (graph.verbose) try graph.handleVerbose(null, null, &.{
3539 "install", "-C", try src_path.toString(arena), try dest_path.toString(arena),
3540 });
3541 return Dir.updateFile(
3542 src_path.root_dir.handle,
3543 io,
3544 src_path.sub_path,
3545 dest_path.root_dir.handle,
3546 dest_path.sub_path,
3547 .{},
3548 ) catch |err| {
3549 const s = stepByIndex(maker, asking_step_index);
3550 return s.fail(maker, "failed updating file from {f} to {f}: {t}", .{ src_path, dest_path, err });
3551 };
3552}
3553
3554/// Wrapper around `Dir.createDirPathStatus` that handles verbose and error output.
3555pub fn installDir(
3556 maker: *Maker,
3557 arena: Allocator,
3558 dest_path: Path,
3559 asking_step_index: Configuration.Step.Index,
3560) Step.ExtendedMakeError!Dir.CreatePathStatus {
3561 const graph = maker.graph;
3562 const io = graph.io;
3563 if (graph.verbose) try graph.handleVerbose(null, null, &.{
3564 "install", "-d", try dest_path.toString(arena),
3565 });
3566 return dest_path.root_dir.handle.createDirPathStatus(io, dest_path.sub_path, .default_dir) catch |err| {
3567 const s = stepByIndex(maker, asking_step_index);
3568 return s.fail(maker, "failed creating dir {f}: {t}", .{ dest_path, err });
3569 };
3570}
3571
3572pub fn installSymLinks(
3573 maker: *Maker,
3574 arena: Allocator,
3575 output_path: Path,
3576 compile_step_index: Configuration.Step.Index,
3577 asking_step_index: Configuration.Step.Index,
3578) !void {
3579 const c = &maker.scanned_config.configuration;
3580 const conf_step = compile_step_index.ptr(c);
3581 const conf_comp = conf_step.extended.get(c.extra).compile;
3582 const root_module = conf_comp.root_module.get(c);
3583 const target = root_module.resolved_target.get(c).?.result.get(c);
3584 const os_tag = target.flags.os_tag.unwrap().?;
3585
3586 assert(conf_comp.flags3.kind == .lib);
3587 assert(conf_comp.flags2.linkage == .dynamic);
3588 assert(os_tag != .windows);
3589
3590 const version = std.SemanticVersion.parse(conf_comp.version.value.?.slice(c)) catch unreachable;
3591 const name = conf_comp.root_name.slice(c);
3592
3593 const filename_major_only, const filename_name_only = if (os_tag.isDarwin()) .{
3594 try arena.print("lib{s}.{d}.dylib", .{ name, version.major }),
3595 try arena.print("lib{s}.dylib", .{name}),
3596 } else .{
3597 try arena.print("lib{s}.so.{d}", .{ name, version.major }),
3598 try arena.print("lib{s}.so", .{name}),
3599 };
3600
3601 return installSymLinksInner(maker, arena, output_path, asking_step_index, filename_major_only, filename_name_only);
3602}
3603
3604fn installSymLinksInner(
3605 maker: *Maker,
3606 arena: Allocator,
3607 output_path: Path,
3608 asking_step_index: Configuration.Step.Index,
3609 filename_major_only: []const u8,
3610 filename_name_only: []const u8,
3611) !void {
3612 const io = maker.graph.io;
3613 const step = stepByIndex(maker, asking_step_index);
3614 const out_basename = Io.Dir.path.basename(output_path.sub_path);
3615
3616 const out_dir = output_path.dirname().?;
3617 const major_only_path = try out_dir.join(arena, filename_major_only);
3618 const name_only_path = try out_dir.join(arena, filename_name_only);
3619
3620 // libfoo.so.1 to libfoo.so.1.2.3
3621 major_only_path.root_dir.handle.symLinkAtomic(io, out_basename, major_only_path.sub_path, .{}) catch |err|
3622 return step.fail(maker, "failed symlinking {f} to {s}: {t}", .{ output_path, out_basename, err });
3623
3624 // libfoo.so to libfoo.so.1
3625 name_only_path.root_dir.handle.symLinkAtomic(io, filename_major_only, name_only_path.sub_path, .{}) catch |err|
3626 return step.fail(maker, "failed symlinking {f} to {s}: {t}", .{ name_only_path, filename_major_only, err });
3627}
3628
3629fn cleanExit(io: Io, scanned_config: *const ScannedConfig) void {
3630 removePoisonedConfiguration(io, scanned_config);
3631 return process.cleanExit(io);
3632}
3633
3634fn removePoisonedConfiguration(io: Io, scanned_config: *const ScannedConfig) void {
3635 if (scanned_config.configuration.poisoned) {
3636 // This configuration file was good for only 1 invocation of the maker
3637 // process. Delete it to save space on disk.
3638 scanned_config.path.root_dir.handle.deleteFile(io, scanned_config.path.sub_path) catch |err|
3639 log.warn("failed deleting poisoned configuration file {f}: {t}", .{ scanned_config.path, err });
3640 }
3641}
3642
3643inline fn debugMakerLeaks() bool {
3644 if (!is_debug_mode) return false;
3645 return debug_maker_leaks;
3646}
3647
3648const BuildRoot = struct {
3649 directory: Cache.Directory,
3650 close_directory: bool,
3651 build_zig_basename: []const u8,
3652
3653 fn deinit(br: *BuildRoot, io: Io) void {
3654 if (br.close_directory) br.directory.handle.close(io);
3655 br.* = undefined;
3656 }
3657};
3658
3659const FindBuildRootOptions = struct {
3660 build_file: ?[]const u8 = null,
3661 cwd_path: []const u8,
3662};
3663
3664fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !BuildRoot {
3665 const build_zig_basename = if (options.build_file) |bf|
3666 Dir.path.basename(bf)
3667 else
3668 std.zig.build_zig_basename;
3669
3670 if (options.build_file) |bf| {
3671 if (Dir.path.dirname(bf)) |dirname| {
3672 const dir = Io.Dir.cwd().openDir(io, dirname, .{}) catch |err| {
3673 fatal("failed opening directory containing {q}: {t}", .{ bf, err });
3674 };
3675 return .{
3676 .build_zig_basename = build_zig_basename,
3677 .directory = .{ .path = dirname, .handle = dir },
3678 .close_directory = true,
3679 };
3680 }
3681
3682 return .{
3683 .build_zig_basename = build_zig_basename,
3684 .directory = .cwd(),
3685 .close_directory = false,
3686 };
3687 }
3688 // Search up parent directories until we find build.zig.
3689 var dirname: ?[]const u8 = null;
3690 while (true) {
3691 const joined_path = if (dirname) |d|
3692 try Dir.path.join(arena, &.{ d, build_zig_basename })
3693 else
3694 build_zig_basename;
3695 if (Io.Dir.cwd().access(io, joined_path, .{})) |_| {
3696 const dir = if (dirname) |d|
3697 Io.Dir.cwd().openDir(io, d, .{}) catch |err| {
3698 fatal("unable to open directory while searching for build.zig file, {q}: {t}", .{ d, err });
3699 }
3700 else
3701 Io.Dir.cwd();
3702 return .{
3703 .build_zig_basename = build_zig_basename,
3704 .directory = .{
3705 .path = dirname,
3706 .handle = dir,
3707 },
3708 .close_directory = dirname != null,
3709 };
3710 } else |err| switch (err) {
3711 error.FileNotFound => {
3712 dirname = Dir.path.dirname(dirname orelse options.cwd_path) orelse {
3713 log.info("initialize {s} template file with \"zig init\"", .{std.zig.build_zig_basename});
3714 log.info("see \"zig --help\" for more options", .{});
3715 fatal("no build.zig file found, in the current directory or any parent directories", .{});
3716 };
3717 continue;
3718 },
3719 else => |e| return e,
3720 }
3721 }
3722}
3723
3724const Fork = struct {
3725 path: Path,
3726 manifest_ast: std.zig.Ast,
3727 manifest: Package.Manifest,
3728 error_bundle: std.zig.ErrorBundle.Wip,
3729 failed: bool,
3730 arena_allocator: std.heap.ArenaAllocator,
3731
3732 fn init(cwd_relative_path: []const u8) Fork {
3733 return .{
3734 .manifest_ast = undefined,
3735 .manifest = undefined,
3736 .error_bundle = undefined,
3737 .arena_allocator = undefined,
3738 .path = .{
3739 .root_dir = .cwd(),
3740 .sub_path = cwd_relative_path,
3741 },
3742 .failed = false,
3743 };
3744 }
3745
3746 fn load(io: Io, gpa: Allocator, fork: *Fork, color: Color) Io.Cancelable!void {
3747 loadFallible(io, gpa, fork, color) catch |err| switch (err) {
3748 error.Canceled => |e| return e,
3749 error.AlreadyReported => fork.failed = true,
3750 else => |e| {
3751 log.err("failed to load fork at {f}: {t}", .{ fork.path, e });
3752 fork.failed = true;
3753 },
3754 };
3755 }
3756
3757 fn loadFallible(io: Io, gpa: Allocator, fork: *Fork, color: Color) !void {
3758 fork.arena_allocator = .init(gpa);
3759 const arena = fork.arena_allocator.allocator();
3760
3761 var error_bundle: std.zig.ErrorBundle.Wip = undefined;
3762 try error_bundle.init(gpa);
3763 defer error_bundle.deinit();
3764
3765 const manifest_path = try fork.path.join(arena, Package.Manifest.basename);
3766
3767 Package.Manifest.load(
3768 io,
3769 arena,
3770 manifest_path,
3771 &fork.manifest_ast,
3772 &error_bundle,
3773 &fork.manifest,
3774 true,
3775 ) catch |err| switch (err) {
3776 error.Canceled => |e| return e,
3777 error.ErrorsBundled => {
3778 assert(error_bundle.root_list.items.len > 0);
3779 var errors = try error_bundle.toOwnedBundle("");
3780 errors.renderToStderr(io, .{}, color) catch {};
3781 return error.AlreadyReported;
3782 },
3783 else => |e| {
3784 log.err("failed to load package manifest {f}: {t}", .{ manifest_path, e });
3785 return error.AlreadyReported;
3786 },
3787 };
3788 }
3789
3790 fn deinitList(forks: []Fork) void {
3791 for (forks) |*fork| fork.arena_allocator.deinit();
3792 }
3793};
3794
3795fn parseRandomSeed(arg: []const u8) u32 {
3796 return std.fmt.parseUnsigned(u32, arg, 0) catch |err|
3797 fatal("failed parsing random seed {q} as unsigned 32-bit integer: {t}", .{ arg, err });
3798}
3799
3800fn randInt(io: Io, comptime T: type) T {
3801 var x: T = undefined;
3802 io.random(@ptrCast(&x));
3803 return x;
3804}
3805
3806const LoadManifestOptions = struct {
3807 root_name: []const u8,
3808 dir: Io.Dir,
3809 color: Color,
3810};
3811
3812fn loadManifest(
3813 gpa: Allocator,
3814 arena: Allocator,
3815 io: Io,
3816 options: LoadManifestOptions,
3817) !struct { Package.Manifest, std.zig.Ast } {
3818 const rng: std.Random.IoSource = .{ .io = io };
3819
3820 const manifest_bytes = while (true) {
3821 break options.dir.readFileAllocOptions(
3822 io,
3823 Package.Manifest.basename,
3824 arena,
3825 .limited(Package.Manifest.max_bytes),
3826 .@"1",
3827 0,
3828 ) catch |err| switch (err) {
3829 error.FileNotFound => {
3830 Templates.writeSimpleFile(io, options.dir, Package.Manifest.basename,
3831 \\.{{
3832 \\ .name = .{s},
3833 \\ .version = "0.0.1",
3834 \\ .minimum_zig_version = "{s}",
3835 \\ .paths = .{{""}},
3836 \\ .fingerprint = 0x{x},
3837 \\}}
3838 \\
3839 , .{
3840 options.root_name,
3841 builtin.zig_version_string,
3842 Package.Fingerprint.generate(rng.interface(), options.root_name).int(),
3843 }) catch |e| {
3844 fatal("unable to write {s}: {t}", .{ Package.Manifest.basename, e });
3845 };
3846 continue;
3847 },
3848 else => |e| fatal("unable to load {s}: {t}", .{ Package.Manifest.basename, e }),
3849 };
3850 };
3851 var ast = try std.zig.Ast.parse(gpa, manifest_bytes, .{ .mode = .zon });
3852 errdefer ast.deinit(gpa);
3853
3854 if (ast.errors.len > 0) {
3855 try std.zig.printAstErrorsToStderr(gpa, io, ast, Package.Manifest.basename, options.color);
3856 process.exit(2);
3857 }
3858
3859 var manifest = try Package.Manifest.parse(gpa, &ast, rng.interface(), .{});
3860 errdefer manifest.deinit(gpa);
3861
3862 if (manifest.errors.len > 0) {
3863 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
3864 try wip_errors.init(gpa);
3865 defer wip_errors.deinit();
3866
3867 const src_path = try wip_errors.addString(Package.Manifest.basename);
3868 try manifest.copyErrorsIntoBundle(ast, src_path, &wip_errors);
3869
3870 var error_bundle = try wip_errors.toOwnedBundle("");
3871 defer error_bundle.deinit(gpa);
3872 error_bundle.renderToStderr(io, .{}, options.color) catch {};
3873
3874 process.exit(2);
3875 }
3876 return .{ manifest, ast };
3877}
3878
3879fn sanitizeExampleName(arena: Allocator, bytes: []const u8) error{OutOfMemory}![]const u8 {
3880 var result: std.ArrayList(u8) = .empty;
3881 for (bytes, 0..) |byte, i| switch (byte) {
3882 '0'...'9' => {
3883 if (i == 0) try result.append(arena, '_');
3884 try result.append(arena, byte);
3885 },
3886 '_', 'a'...'z', 'A'...'Z' => try result.append(arena, byte),
3887 '-', '.', ' ' => try result.append(arena, '_'),
3888 else => continue,
3889 };
3890 if (!std.zig.isValidId(result.items)) return "foo";
3891 if (result.items.len > Package.Manifest.max_name_len)
3892 result.shrinkRetainingCapacity(Package.Manifest.max_name_len);
3893
3894 return result.toOwnedSlice(arena);
3895}
3896
3897test sanitizeExampleName {
3898 var arena_instance = std.heap.ArenaAllocator.init(std.testing.allocator);
3899 defer arena_instance.deinit();
3900 const arena = arena_instance.allocator();
3901
3902 try std.testing.expectEqualStrings("foo_bar", try sanitizeExampleName(arena, "foo bar+"));
3903 try std.testing.expectEqualStrings("foo", try sanitizeExampleName(arena, ""));
3904 try std.testing.expectEqualStrings("foo", try sanitizeExampleName(arena, "!"));
3905 try std.testing.expectEqualStrings("a", try sanitizeExampleName(arena, "!a"));
3906 try std.testing.expectEqualStrings("a_b", try sanitizeExampleName(arena, "a.b!"));
3907 try std.testing.expectEqualStrings("_01234", try sanitizeExampleName(arena, "01234"));
3908 try std.testing.expectEqualStrings("foo", try sanitizeExampleName(arena, "error"));
3909 try std.testing.expectEqualStrings("foo", try sanitizeExampleName(arena, "test"));
3910 try std.testing.expectEqualStrings("tests", try sanitizeExampleName(arena, "tests"));
3911 try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project"));
3912}
3913
3914const Templates = struct {
3915 zig_lib_directory: Cache.Directory,
3916 dir: Io.Dir,
3917 buffer: std.array_list.Managed(u8),
3918
3919 fn deinit(templates: *Templates, io: Io) void {
3920 templates.zig_lib_directory.handle.close(io);
3921 templates.dir.close(io);
3922 templates.buffer.deinit();
3923 templates.* = undefined;
3924 }
3925
3926 fn write(
3927 templates: *Templates,
3928 arena: Allocator,
3929 io: Io,
3930 out_dir: Io.Dir,
3931 root_name: []const u8,
3932 template_path: []const u8,
3933 fingerprint: Package.Fingerprint,
3934 ) !void {
3935 if (Dir.path.dirname(template_path)) |dirname| {
3936 out_dir.createDirPath(io, dirname) catch |err| {
3937 fatal("unable to make path {q}: {t}", .{ dirname, err });
3938 };
3939 }
3940
3941 const max_bytes = 10 * 1024 * 1024;
3942 const contents = templates.dir.readFileAlloc(io, template_path, arena, .limited(max_bytes)) catch |err| {
3943 fatal("unable to read template file {q}: {t}", .{ template_path, err });
3944 };
3945 templates.buffer.clearRetainingCapacity();
3946 try templates.buffer.ensureUnusedCapacity(contents.len);
3947 var i: usize = 0;
3948 while (i < contents.len) {
3949 if (contents[i] == '_' or contents[i] == '.') {
3950 // Both '_' and '.' are allowed because depending on the context
3951 // one prefix will be valid, while the other might not.
3952 if (std.mem.startsWith(u8, contents[i + 1 ..], "NAME")) {
3953 try templates.buffer.appendSlice(root_name);
3954 i += "_NAME".len;
3955 continue;
3956 } else if (std.mem.startsWith(u8, contents[i + 1 ..], "FINGERPRINT")) {
3957 try templates.buffer.print("0x{x}", .{fingerprint.int()});
3958 i += "_FINGERPRINT".len;
3959 continue;
3960 } else if (std.mem.startsWith(u8, contents[i + 1 ..], "ZIGVER")) {
3961 try templates.buffer.appendSlice(builtin.zig_version_string);
3962 i += "_ZIGVER".len;
3963 continue;
3964 }
3965 }
3966
3967 try templates.buffer.append(contents[i]);
3968 i += 1;
3969 }
3970
3971 return out_dir.writeFile(io, .{
3972 .sub_path = template_path,
3973 .data = templates.buffer.items,
3974 .flags = .{ .exclusive = true },
3975 });
3976 }
3977
3978 fn find(gpa: Allocator, io: Io, zig_lib_directory: Cache.Directory) Templates {
3979 const template_path: Path = .{
3980 .root_dir = zig_lib_directory,
3981 .sub_path = "init",
3982 };
3983 const template_dir = template_path.root_dir.handle.openDir(io, template_path.sub_path, .{}) catch |err|
3984 fatal("unable to open zig project template directory {f}: {t}", .{ template_path, err });
3985 return .{
3986 .zig_lib_directory = zig_lib_directory,
3987 .dir = template_dir,
3988 .buffer = std.array_list.Managed(u8).init(gpa),
3989 };
3990 }
3991
3992 fn writeSimpleFile(io: Io, dir: Io.Dir, file_name: []const u8, comptime format: []const u8, args: anytype) !void {
3993 const f = try dir.createFile(io, file_name, .{ .exclusive = true });
3994 defer f.close(io);
3995 var buf: [4096]u8 = undefined;
3996 var fw = f.writer(io, &buf);
3997 try fw.interface.print(format, args);
3998 try fw.interface.flush();
3999 }
4000};
4001
4002fn confPathDepToCachePath(
4003 arena: Allocator,
4004 graph: *const Graph,
4005 c: *const Configuration,
4006 path_dep: Configuration.PathDep,
4007) Allocator.Error!Path {
4008 const sub_path = path_dep.sub.slice(c);
4009 return switch (path_dep.flags.base) {
4010 .cwd => .{
4011 .root_dir = .cwd(),
4012 .sub_path = sub_path,
4013 },
4014 .local_cache => .{
4015 .root_dir = graph.local_cache_root,
4016 .sub_path = sub_path,
4017 },
4018 .global_cache => .{
4019 .root_dir = graph.global_cache_root,
4020 .sub_path = sub_path,
4021 },
4022 .build_root => .{
4023 .root_dir = graph.build_root_directory,
4024 .sub_path = switch (path_dep.pkg.unwrap().?) {
4025 .root => sub_path,
4026 else => |index| try Dir.path.join(arena, &.{ index.get(c).?.root_path.slice(c), sub_path }),
4027 },
4028 },
4029 .zig_lib => .{
4030 .root_dir = graph.zig_lib_directory,
4031 .sub_path = sub_path,
4032 },
4033 .zig_exe => @panic("TODO"),
4034 .install_prefix => @panic("TODO"),
4035 .install_lib => @panic("TODO"),
4036 .install_bin => @panic("TODO"),
4037 .install_include => @panic("TODO"),
4038 };
4039}
4040
4041fn fatalEnumHint(comptime E: type, arg: []const u8, param: ?[]const u8) noreturn {
4042 var buf: [100]u8 = undefined;
4043 var w: Io.Writer = .fixed(&buf);
4044 for (@typeInfo(E).@"enum".field_names) |field_name| {
4045 w.writeAll(field_name) catch unreachable;
4046 w.writeByte('|') catch unreachable;
4047 }
4048 const buffered = w.buffered();
4049 const enum_options_text = buffered[0 .. buffered.len - 1];
4050 if (param) |p| {
4051 fatalWithHint("expected [{s}] after {q}; found {q}", .{ enum_options_text, arg, p });
4052 } else {
4053 fatalWithHint("expected [{s}] after {q}", .{ enum_options_text, arg });
4054 }
4055}
4056
4057fn nextEnumArg(args: []const []const u8, i: *usize, comptime E: type) E {
4058 const arg = args[i.* - 1];
4059 const next_arg = nextArg(args, i) orelse fatalEnumHint(E, arg, null);
4060 return stringToEnum(E, next_arg) orelse fatalEnumHint(E, arg, next_arg);
4061}