Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

main

Maker.main
pub fn main(init: process.Init.Minimal) !void

File

lib/compiler/Maker.zig:134

Code

pub fn main(init: process.Init.Minimal) !void {
    // The build runner is long-lived in the following use cases:
    // * `--watch` mode
    // * `--webui` mode
    // * `--fuzz` mode
    // * A project that has a large, complex build graph.
    const gpa = if (use_safe_allocator) safe_allocator_instance.allocator() else std.heap.smp_allocator;
    defer if (use_safe_allocator) {
        _ = safe_allocator_instance.deinit();
    };

    var threaded: std.Io.Threaded = .init(gpa, .{
        .environ = init.environ,
        .argv0 = .init(init.args),
    });
    defer threaded.deinit();
    const io = threaded.io();

    var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
    defer arena_instance.deinit();
    defer if (debugMakerLeaks()) log.debug("used {Bi} of arena", .{arena_instance.queryCapacity()});
    const arena = arena_instance.allocator();

    const args = try init.args.toSlice(arena);
    var arg_i: usize = 1;
    const cmd_name = nextArgOrFatal(args, &arg_i);
    const zig_lib_arg = prefixedArgOrFatal(args, &arg_i, "--zig-lib=");
    const zig_exe_arg = prefixedArgOrFatal(args, &arg_i, "--zig=");
    const global_cache_arg = prefixedArgOrFatal(args, &arg_i, "--global-cache=");
    const seed_arg = prefixedArgOrFatal(args, &arg_i, "--seed=");

    const cwd: Dir = .cwd();

    const zig_lib_directory: Cache.Directory = if (std.mem.eql(u8, zig_lib_arg, ".")) .cwd() else .{
        .path = zig_lib_arg,
        .handle = try cwd.openDir(io, zig_lib_arg, .{}),
    };

    const global_cache_directory: Cache.Directory = if (std.mem.eql(u8, global_cache_arg, ".")) .cwd() else .{
        .path = global_cache_arg,
        .handle = try cwd.createDirPathOpen(io, global_cache_arg, .{}),
    };

    var graph: Graph = .{
        .io = io,
        .arena = arena,
        .cache = undefined,
        .zig_exe = zig_exe_arg,
        .environ_map = try init.environ.createMap(arena),
        .global_cache_root = global_cache_directory,
        .local_cache_root = undefined,
        .zig_lib_directory = zig_lib_directory,
        .build_root_directory = undefined,
        .random_seed = parseRandomSeed(seed_arg),
    };

    const cmd = stringToEnum(enum { libc, init, fetch, build }, cmd_name) orelse
        fatal("bad command name: {q}", .{cmd_name});
    switch (cmd) {
        .libc => return cmdLibC(gpa, &graph, args[arg_i..]),
        .init => return cmdInit(gpa, &graph, args[arg_i..]),
        .fetch => return cmdFetch(gpa, &graph, args[arg_i..]),
        .build => {},
    }

    var step_names: std.ArrayList([]const u8) = .empty;
    var help_menu = false;
    var steps_menu = false;
    var print_configuration: PrintConfiguration = .none;
    var override_install_prefix: ?[]const u8 = null;
    var override_lib_dir: ?[]const u8 = null;
    var override_bin_dir: ?[]const u8 = null;
    var override_include_dir: ?[]const u8 = null;
    var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(&graph.environ_map);
    var override_pkg_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_PKG_DIR.get(&graph.environ_map);
    var error_style: ErrorStyle = .verbose;
    var multiline_errors: MultilineErrors = .indent;
    var summary: ?Summary = null;
    var max_rss: u64 = 0;
    var skip_oom_steps = false;
    var test_timeout_ns: ?u64 = null;
    var color: Color = .settingFromEnvironment(&graph.environ_map);
    var watch = false;
    var fuzz: ?Fuzz.Mode = null;
    var debounce_interval_ms: u16 = 50;
    var webui_listen: ?Io.net.IpAddress = null;
    var debug_pkg_config = false;
    var run_args: ?[]const []const u8 = null;
    var build_file: ?[]const u8 = null;

    var configure_argv: std.ArrayList([]const u8) = .empty;
    var cached_passthru_configure: std.ArrayList(u32) = .empty;
    var forks: std.ArrayList(Fork) = .empty;
    var system_pkg_dir_path: ?[]const u8 = null;
    var fetch_only = false;
    var fetch_mode: Fetch.JobQueue.Mode = .needed;
    var debug_target: ?[]const u8 = null;
    var cache_poison: std.Build.Graph.CachePoison = .pure;

    if (EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.environ_map)) |str| {
        if (stringToEnum(ErrorStyle, str)) |style| {
            error_style = style;
        }
    }

    if (EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.environ_map)) |str| {
        if (stringToEnum(MultilineErrors, str)) |style| {
            multiline_errors = style;
        }
    }

    try configure_argv.ensureUnusedCapacity(arena, 16);
    try cached_passthru_configure.ensureUnusedCapacity(arena, 16);

    _ = configure_argv.addOneAssumeCapacity(); // configurer executable
    configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--zig", graph.zig_exe };
    configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--build-root", undefined };
    const conf_argv_index_build_root = configure_argv.items.len - 1;

    while (nextArg(args, &arg_i)) |arg| {
        if (mem.startsWith(u8, arg, "-")) {
            try configure_argv.ensureUnusedCapacity(arena, 2);
            if (mem.startsWith(u8, arg, "-D") or
                mem.startsWith(u8, arg, "-fsys=") or
                mem.startsWith(u8, arg, "-fno-sys=") or
                mem.startsWith(u8, arg, "--release=") or
                mem.eql(u8, arg, "--release"))
            {
                try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
                configure_argv.appendAssumeCapacity(arg);
            } else if (mem.eql(u8, arg, "--system")) {
                system_pkg_dir_path = nextArgOrFatal(args, &arg_i);

                try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
                configure_argv.appendAssumeCapacity(arg); // Intentionally "--system" only; not the path.
            } else if (mem.cutPrefix(u8, arg, "--color=")) |rest| {
                color = stringToEnum(Color, rest) orelse
                    fatalWithHint("expected --color=[auto|on|off]; found {q}", .{arg});

                try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
                configure_argv.appendAssumeCapacity(arg);
            } else if (mem.eql(u8, arg, "--color")) {
                const next_arg = nextArgOrFatal(args, &arg_i);
                color = stringToEnum(Color, next_arg) orelse
                    fatalWithHint("expected [auto|on|off] found {q}", .{next_arg});

                try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
                configure_argv.appendAssumeCapacity(try arena.print("--color={t}", .{color}));
            } else if (mem.eql(u8, arg, "--cache-poison")) {
                cache_poison = .poisoned;
                configure_argv.appendAssumeCapacity("--cache-poison=poisoned");
            } else if (mem.cutPrefix(u8, arg, "--cache-poison=")) |rest| {
                // We have to report parse failure here otherwise we would
                // potentially get false positive cache hits for misspellings.
                cache_poison = stringToEnum(std.Build.Graph.CachePoison, rest) orelse
                    fatalWithHint("expected --cache-poison=[pure|poisoned|disallowed|ignored]; found: {s}", .{arg});
                if (cache_poison != .pure) configure_argv.appendAssumeCapacity(arg);
            } else if (mem.eql(u8, arg, "--verbose")) {
                // Intentionally is added both to make and configure but
                // does not go into the cache hash.
                configure_argv.appendAssumeCapacity(arg);
                graph.verbose = true;
            } else if (mem.eql(u8, arg, "--search-prefix")) {
                const prefix = nextArgOrFatal(args, &arg_i);

                // This argument is cache poisonous: it does not go into
                // the cache and configurer must set the poison bit when
                // choosing to observe it.
                configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ arg, prefix };

                try graph.search_prefixes.append(arena, prefix);
            } else if (mem.eql(u8, arg, "--cache-dir")) {
                override_local_cache_dir = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--pkg-dir")) {
                override_pkg_dir = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--fetch")) {
                fetch_only = true;
            } else if (mem.cutPrefix(u8, arg, "--fetch=")) |rest| {
                fetch_only = true;
                fetch_mode = stringToEnum(Fetch.JobQueue.Mode, rest) orelse
                    fatal("expected [needed|all] after \"--fetch=\", found {q}", .{rest});
            } else if (mem.cutPrefix(u8, arg, "--fork=")) |rest| {
                try forks.append(arena, .init(rest));
            } else if (mem.eql(u8, arg, "--fork")) {
                try forks.append(arena, .init(nextArgOrFatal(args, &arg_i)));
            } else if (mem.startsWith(u8, arg, "--zig-lib=")) {
                fatal("--zig-lib= argument is special and must be first", .{});
            } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
                help_menu = true;
            } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
                steps_menu = true;
            } else if (mem.eql(u8, arg, "--print-configuration")) {
                print_configuration = .zon;
            } else if (mem.eql(u8, arg, "--print-configuration-path")) {
                print_configuration = .path;
            } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
                override_install_prefix = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--build-file")) {
                build_file = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
                override_lib_dir = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
                override_bin_dir = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
                override_include_dir = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--sysroot")) {
                graph.sysroot = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--maxrss")) {
                const max_rss_text = nextArgOrFatal(args, &arg_i);
                max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err|
                    fatal("invalid byte size {q}: {t}", .{ max_rss_text, err });
            } else if (mem.eql(u8, arg, "--skip-oom-steps")) {
                skip_oom_steps = true;
            } else if (mem.eql(u8, arg, "--test-timeout")) {
                const units: []const struct { []const u8, u64 } = &.{
                    .{ "ns", 1 },
                    .{ "nanosecond", 1 },
                    .{ "us", std.time.ns_per_us },
                    .{ "microsecond", std.time.ns_per_us },
                    .{ "ms", std.time.ns_per_ms },
                    .{ "millisecond", std.time.ns_per_ms },
                    .{ "s", std.time.ns_per_s },
                    .{ "second", std.time.ns_per_s },
                    .{ "m", std.time.ns_per_min },
                    .{ "minute", std.time.ns_per_min },
                    .{ "h", std.time.ns_per_hour },
                    .{ "hour", std.time.ns_per_hour },
                };
                const timeout_str = nextArgOrFatal(args, &arg_i);
                const num_end_idx = std.mem.findLastNone(u8, timeout_str, "abcdefghijklmnopqrstuvwxyz") orelse fatal(
                    "invalid timeout {q}: expected unit (ns, us, ms, s, m, h)",
                    .{timeout_str},
                );
                const num_str = timeout_str[0 .. num_end_idx + 1];
                const unit_str = timeout_str[num_end_idx + 1 ..];
                const unit_factor: f64 = for (units) |unit_and_factor| {
                    if (std.mem.eql(u8, unit_str, unit_and_factor[0])) {
                        break @floatFromInt(unit_and_factor[1]);
                    }
                } else fatal(
                    "invalid timeout {q}: invalid unit {q} (expected ns, us, ms, s, m, h)",
                    .{ timeout_str, unit_str },
                );
                const num_parsed = std.fmt.parseFloat(f64, num_str) catch |err| fatal(
                    "invalid timeout {q}: invalid number {q} ({t})",
                    .{ timeout_str, num_str, err },
                );
                test_timeout_ns = std.math.lossyCast(u64, unit_factor * num_parsed);
            } else if (mem.eql(u8, arg, "--libc")) {
                graph.libc_file = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--error-style")) {
                const next_arg = nextArg(args, &arg_i) orelse
                    fatalWithHint("expected style after {q}", .{arg});
                error_style = stringToEnum(ErrorStyle, next_arg) orelse {
                    fatalWithHint("expected style after {q}, found {q}", .{ arg, next_arg });
                };
            } else if (mem.eql(u8, arg, "--multiline-errors")) {
                const next_arg = nextArg(args, &arg_i) orelse
                    fatalWithHint("expected style after {q}", .{arg});
                multiline_errors = stringToEnum(MultilineErrors, next_arg) orelse {
                    fatalWithHint("expected style after {q}, found {q}", .{ arg, next_arg });
                };
            } else if (mem.eql(u8, arg, "--summary")) {
                const next_arg = nextArg(args, &arg_i) orelse
                    fatalWithHint("expected [all|new|failures|line|none] after {q}", .{arg});
                summary = stringToEnum(Summary, next_arg) orelse {
                    fatalWithHint("expected [all|new|failures|line|none] after {q}, found {q}", .{
                        arg, next_arg,
                    });
                };
            } else if (mem.cutPrefix(u8, arg, "--seed=")) |rest| {
                graph.random_seed = parseRandomSeed(rest);
            } else if (mem.eql(u8, arg, "--build-id")) {
                graph.build_id = .fast;
            } else if (mem.cutPrefix(u8, arg, "--build-id=")) |style| {
                graph.build_id = std.zig.BuildId.parse(style) catch |err|
                    fatal("unable to parse --build-id style {q}: {t}", .{ style, err });
            } else if (mem.eql(u8, arg, "--debounce")) {
                const next_arg = nextArg(args, &arg_i) orelse
                    fatalWithHint("expected u16 after {q}", .{arg});
                debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| {
                    fatal("unable to parse debounce interval {q} as unsigned 16-bit integer: {t}", .{
                        next_arg, err,
                    });
                };
            } else if (mem.eql(u8, arg, "--webui")) {
                if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
            } else if (mem.startsWith(u8, arg, "--webui=")) {
                const addr_str = arg["--webui=".len..];
                if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{});
                webui_listen = Io.net.IpAddress.parseLiteral(addr_str) catch |err| {
                    fatal("invalid web UI address {q}: {t}", .{ addr_str, err });
                };
            } else if (mem.eql(u8, arg, "--debug-target")) {
                debug_target = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--debug-log")) {
                try graph.debug_log_scopes.append(arena, nextArgOrFatal(args, &arg_i));
            } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
                graph.debug_compile_errors = true;
            } else if (mem.eql(u8, arg, "--debug-incremental")) {
                graph.debug_incremental = true;
            } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
                debug_pkg_config = true;
            } else if (mem.eql(u8, arg, "--debug-rt")) {
                graph.debug_compiler_runtime_libs = .Debug;
            } else if (mem.cutPrefix(u8, arg, "--debug-rt=")) |rest| {
                graph.debug_compiler_runtime_libs = stringToEnum(std.lang.OptimizeMode, rest) orelse
                    fatal("unrecognized optimization mode: {s}", .{rest});
            } else if (is_debug_mode and mem.eql(u8, arg, "--debug-maker-leaks")) {
                debug_maker_leaks = true;
            } else if (mem.eql(u8, arg, "--libc-runtimes") or mem.eql(u8, arg, "--glibc-runtimes")) {
                // --glibc-runtimes was the old name of the flag; kept for compatibility for now.
                graph.libc_runtimes_dir = nextArgOrFatal(args, &arg_i);
            } else if (mem.eql(u8, arg, "--verbose-air")) {
                graph.verbose_air = true;
            } else if (mem.eql(u8, arg, "--verbose-cc")) {
                graph.verbose_cc = true;
            } else if (mem.eql(u8, arg, "--verbose-link")) {
                graph.verbose_link = true;
            } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
                graph.verbose_llvm_ir = true;
            } else if (mem.eql(u8, arg, "--watch")) {
                watch = true;
            } else if (mem.eql(u8, arg, "--time-report")) {
                graph.time_report = true;
                if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
            } else if (mem.eql(u8, arg, "--fuzz")) {
                fuzz = .{ .forever = undefined };
                graph.fuzzing = true;
                if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
            } else if (mem.startsWith(u8, arg, "--fuzz=")) {
                const value = arg["--fuzz=".len..];
                if (value.len == 0) fatal("missing argument to --fuzz", .{});

                const unit: u8 = value[value.len - 1];
                const digits = switch (unit) {
                    '0'...'9' => value,
                    'K', 'M', 'G' => value[0 .. value.len - 1],
                    else => fatal(
                        "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
                        .{},
                    ),
                };

                const amount = std.fmt.parseInt(u64, digits, 10) catch {
                    fatal(
                        "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
                        .{},
                    );
                };

                const normalized_amount = std.math.mul(u64, amount, switch (unit) {
                    else => unreachable,
                    '0'...'9' => 1,
                    'K' => 1000,
                    'M' => 1_000_000,
                    'G' => 1_000_000_000,
                }) catch fatal("fuzzing limit amount overflows u64", .{});

                fuzz = .{
                    .limit = .{
                        .amount = normalized_amount,
                    },
                };
                graph.fuzzing = true;
            } else if (mem.eql(u8, arg, "-fincremental")) {
                graph.incremental = true;
            } else if (mem.eql(u8, arg, "-fno-incremental")) {
                graph.incremental = false;
            } else if (mem.eql(u8, arg, "-fwine")) {
                graph.enable_wine = true;
            } else if (mem.eql(u8, arg, "-fno-wine")) {
                graph.enable_wine = false;
            } else if (mem.eql(u8, arg, "-fqemu")) {
                graph.enable_qemu = true;
            } else if (mem.eql(u8, arg, "-fno-qemu")) {
                graph.enable_qemu = false;
            } else if (mem.eql(u8, arg, "-fwasmtime")) {
                graph.enable_wasmtime = true;
            } else if (mem.eql(u8, arg, "-fno-wasmtime")) {
                graph.enable_wasmtime = false;
            } else if (mem.eql(u8, arg, "-frosetta")) {
                graph.enable_rosetta = true;
            } else if (mem.eql(u8, arg, "-fno-rosetta")) {
                graph.enable_rosetta = false;
            } else if (mem.eql(u8, arg, "-fdarling")) {
                graph.enable_darling = true;
            } else if (mem.eql(u8, arg, "-fno-darling")) {
                graph.enable_darling = false;
            } else if (mem.eql(u8, arg, "-fallow-so-scripts")) {
                graph.allow_so_scripts = true;
            } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) {
                graph.allow_so_scripts = false;
            } else if (mem.eql(u8, arg, "-freference-trace")) {
                graph.reference_trace = 256;
            } else if (mem.cutPrefix(u8, arg, "-freference-trace=")) |num| {
                graph.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err|
                    fatal("unable to parse reference_trace count {q}: {t}", .{ num, err });
            } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
                graph.reference_trace = null;
            } else if (mem.eql(u8, arg, "--error-limit")) {
                const next_arg = nextArgOrFatal(args, &arg_i);
                graph.error_limit = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err|
                    fatal("unable to parse error limit {q}: {t}", .{ next_arg, err });
            } else if (mem.cutPrefix(u8, arg, "-j")) |text| {
                const n = std.fmt.parseUnsigned(u32, text, 10) catch |err|
                    fatal("unable to parse jobs count {q}: {t}", .{ text, err });
                if (n < 1) fatal("number of jobs must be at least 1", .{});
                threaded.setAsyncLimit(.limited(n));
                graph.max_jobs = n;
            } else if (mem.eql(u8, arg, "--")) {
                run_args = argsRest(args, arg_i);
                break;
            } else {
                fatalWithHint("unrecognized argument: {s}", .{arg});
            }
        } else {
            try step_names.append(arena, arg);
        }
    }

    const early_exit_mode = fetch_only or help_menu or steps_menu or print_configuration != .none;
    const server_mode = !early_exit_mode and (watch or webui_listen != null or fuzz != null);

    process.raiseFileDescriptorLimit();

    const cwd_path = std.zig.getResolvedCwd(io, arena) catch |err|
        fatal("resolving current directory path failed: {t}", .{err});

    var build_root = try findBuildRoot(arena, io, .{
        .cwd_path = cwd_path,
        .build_file = build_file,
    });
    defer build_root.deinit(io);

    graph.build_root_directory = build_root.directory;
    graph.local_cache_root = if (override_local_cache_dir) |unresolved_path| std.zig.Directories.openUnresolved(
        arena,
        io,
        cwd_path,
        unresolved_path,
        .@"local cache",
    ) else .{
        .path = try build_root.directory.join(arena, &.{default_local_zig_cache_basename}),
        .handle = try build_root.directory.handle.createDirPathOpen(io, default_local_zig_cache_basename, .{}),
    };
    graph.cache = .{
        .io = io,
        .gpa = gpa,
        .manifest_dir = try graph.local_cache_root.handle.createDirPathOpen(io, "h", .{}),
        .cwd = cwd_path,
    };

    graph.cache.addPrefix(.{ .path = null, .handle = cwd });
    graph.cache.addPrefix(zig_lib_directory);
    graph.cache.addPrefix(graph.local_cache_root);
    graph.cache.addPrefix(global_cache_directory);
    graph.cache.addPrefix(graph.build_root_directory);
    comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd));
    comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib));
    comptime assert(2 == @backingInt(std.zig.Server.Message.PathPrefix.local_cache));
    comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache));

    graph.cache.hash.addBytes(builtin.zig_version_string);

    const NO_COLOR = EnvVar.NO_COLOR.isSet(&graph.environ_map);
    const CLICOLOR_FORCE = EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map);

    graph.stderr_mode = switch (color) {
        .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),
        .on => .escape_codes,
        .off => .no_color,
    };

    const pkg_root: Path = if (override_pkg_dir) |p|
        .initCwd(p)
    else if (system_pkg_dir_path) |p|
        .initCwd(p)
    else
        .{
            .root_dir = build_root.directory,
            .sub_path = "zig-pkg",
        };

    const main_progress_node = std.Progress.start(io, .{
        .disable_printing = (graph.stderr_mode.? == .no_color),
    });
    defer main_progress_node.end();

    const install_prefix_path: Path = if (graph.environ_map.get("DESTDIR")) |dest_dir| .{
        .root_dir = .cwd(),
        .sub_path = try Dir.path.join(arena, &.{ dest_dir, override_install_prefix orelse "/usr" }),
    } else if (override_install_prefix) |cwd_relative| .{
        .root_dir = .cwd(),
        .sub_path = cwd_relative,
    } else .{
        .root_dir = graph.build_root_directory,
        .sub_path = "zig-out",
    };

    const install_lib_path: Path = if (override_lib_dir) |cwd_relative| .{
        .root_dir = .cwd(),
        .sub_path = cwd_relative,
    } else try install_prefix_path.join(arena, "lib");

    const install_bin_path: Path = if (override_bin_dir) |cwd_relative| .{
        .root_dir = .cwd(),
        .sub_path = cwd_relative,
    } else try install_prefix_path.join(arena, "bin");

    const install_include_path: Path = if (override_include_dir) |cwd_relative| .{
        .root_dir = .cwd(),
        .sub_path = cwd_relative,
    } else try install_prefix_path.join(arena, "include");

    const now = Io.Clock.Timestamp.now(io, .awake);

    var web_server_allocation: AvoidableWebServer = undefined;
    const web_server: ?*AvoidableWebServer = if (webui_listen) |listen_address| ws: {
        if (builtin.single_threaded) fatal("--webui is not yet supported on single-threaded hosts", .{});
        web_server_allocation = .init(.{
            .graph = &graph,
            .root_prog_node = main_progress_node,
            .listen_address = listen_address,
            .base_timestamp = now,
        });
        web_server_allocation.start() catch |err| fatal("failed to start web server: {t}", .{err});
        break :ws &web_server_allocation;
    } else null;

    while (true) {
        // If this fails, we can still start the server and wait for user
        // to request a rebuild. If it returns error.FailedButCacheIntact
        // we can even still do file system watching and automatically
        // rebuild on source changes.
        if (configure(&graph, .{
            .configure_argv = configure_argv.items,
            .conf_argv_index_build_root = conf_argv_index_build_root,
            .cached_passthru_configure = cached_passthru_configure.items,

            .cache_poison = cache_poison,
            .pkg_root = pkg_root,
            .build_root = build_root,
            .cwd_path = cwd_path,
            .color = color,
            .debug_target = debug_target,
            .parent_progress_node = main_progress_node,
            .fetch_mode = fetch_mode,
            .system_pkg_dir_path = system_pkg_dir_path,
            .fetch_only = fetch_only,
            .print_configuration = print_configuration,
            .forks = forks.items,
        })) |scanned_config| {
            if (help_menu) {
                scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {
                    error.WriteFailed => return stdout_writer_allocation.err.?,
                    else => |e| return e,
                };
                try stdout_writer_allocation.flush();
                return cleanExit(io, &scanned_config);
            } else if (steps_menu) {
                scanned_config.printSteps(&graph, initStdoutWriter(io)) catch |err| switch (err) {
                    error.WriteFailed => return stdout_writer_allocation.err.?,
                    else => |e| return e,
                };
                try stdout_writer_allocation.flush();
                return cleanExit(io, &scanned_config);
            } else switch (print_configuration) {
                .none => {},
                .zon => {
                    scanned_config.print(initStdoutWriter(io)) catch return stdout_writer_allocation.err.?;
                    try stdout_writer_allocation.flush();
                    return cleanExit(io, &scanned_config);
                },
                .path => unreachable,
            }

            var maker: Maker = .{
                .gpa = gpa,
                .graph = &graph,
                .scanned_config = &scanned_config,
                .install_paths = .{
                    .prefix = install_prefix_path,
                    .lib = install_lib_path,
                    .bin = install_bin_path,
                    .include = install_include_path,
                },

                .steps = try arena.alloc(Step, scanned_config.configuration.steps.len),
                .generated_files = try arena.alloc(Path, scanned_config.configuration.generated_files_len),
                .run_args = run_args,

                .available_rss = max_rss,
                .max_rss_is_default = false,
                .max_rss_mutex = .init,
                .skip_oom_steps = skip_oom_steps,
                .unit_test_timeout_ns = test_timeout_ns,

                .watch = watch,
                .web_server = web_server,
                .memory_blocked_steps = .empty,
                .step_stack = .empty,
                .pkg_config = .{ .debug = debug_pkg_config },

                .error_style = error_style,
                .multiline_errors = multiline_errors,
                .summary = summary orelse if (watch or webui_listen != null) .new else .failures,
            };
            defer {
                maker.memory_blocked_steps.deinit(gpa);
                maker.step_stack.deinit(gpa);
            }

            if (maker.available_rss == 0) {
                maker.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
                maker.max_rss_is_default = true;
            }

            maker.prepare(step_names.items) catch |err| switch (err) {
                error.DependencyLoopDetected, error.InsufficientMemory => {
                    // TODO handle DependencyLoopDetected as error.FailedButCacheIntact
                    // and handle InsufficientMemory as error.AlreadyReported
                    _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
                    process.exit(1);
                },
                else => |e| return e,
            };

            var w: Watch = w: {
                if (!watch) break :w undefined;
                if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{native_os});
                break :w try .init(&maker);
            };

            if (web_server) |ws| try ws.updateConfiguration(&maker);

            rebuild: while (true) : (if (maker.error_style.clearOnUpdate()) {
                const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
                defer io.unlockStderr();
                stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H") catch |err| switch (err) {
                    error.WriteFailed => return stderr.file_writer.err.?,
                };
            }) {
                if (web_server) |ws| ws.startBuild();

                try maker.makeStepNames(step_names.items, main_progress_node, fuzz);

                if (web_server) |ws| {
                    if (fuzz) |mode| if (mode != .forever) fatal(
                        "error: limited fuzzing is not implemented yet for --webui",
                        .{},
                    );

                    ws.finishBuild(.{ .fuzz = fuzz != null });
                }

                if (web_server) |ws| {
                    const c = &scanned_config.configuration;
                    assert(!watch); // fatal error after CLI parsing
                    while (true) switch (try ws.wait()) {
                        .rebuild => {
                            for (maker.step_stack.keys()) |step_index| {
                                const step = maker.stepByIndex(step_index);
                                step.state = .precheck_done;
                                const deps = step_index.ptr(c).deps.slice(c);
                                step.pending_deps = @intCast(deps.len);
                                step.reset(&maker);
                            }
                            continue :rebuild;
                        },
                    };
                }

                if (!maker.watch) return;

                // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
                if (!Watch.have_impl) unreachable;

                try w.update(maker.step_stack.keys());

                // Wait until a file system notification arrives. Read all such events
                // until the buffer is empty. Then wait for a debounce interval, resetting
                // if any more events come in. After the debounce interval has passed,
                // trigger a rebuild on all steps with modified inputs, as well as their
                // recursive dependants.
                var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
                const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{
                    w.dir_count, countSubProcesses(&maker),
                }) catch &caption_buf;
                var debouncing_node = main_progress_node.start(caption, 0);
                var in_debounce = false;
                while (true) switch (try w.wait(if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) {
                    .timeout => {
                        assert(in_debounce);
                        debouncing_node.end();
                        markFailedStepsDirty(&maker);
                        continue :rebuild;
                    },
                    .dirty => if (!in_debounce) {
                        in_debounce = true;
                        debouncing_node.end();
                        debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
                    },
                    .clean => {},
                };
            }
        } else |err| {
            const can_fs_watch = switch (err) {
                error.AlreadyReported => false,
                error.FailedButCacheIntact => true,
                else => |e| w: {
                    log.err("configuration failed: {t}", .{e});
                    break :w false;
                },
            };
            if (!server_mode) {
                _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
                process.exit(1);
            }
            if (watch and can_fs_watch) {
                fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{});
            } else {
                fatal("(zig build system) TODO stay running and wait for user to request rebuild even when build.zig compilation fails", .{});
            }
        }
    }
}