master
   1const runner = @This();
   2const builtin = @import("builtin");
   3
   4const std = @import("std");
   5const Io = std.Io;
   6const assert = std.debug.assert;
   7const fmt = std.fmt;
   8const mem = std.mem;
   9const process = std.process;
  10const File = std.fs.File;
  11const Step = std.Build.Step;
  12const Watch = std.Build.Watch;
  13const WebServer = std.Build.WebServer;
  14const Allocator = std.mem.Allocator;
  15const fatal = std.process.fatal;
  16const Writer = std.Io.Writer;
  17const tty = std.Io.tty;
  18
  19pub const root = @import("@build");
  20pub const dependencies = @import("@dependencies");
  21
  22pub const std_options: std.Options = .{
  23    .side_channels_mitigations = .none,
  24    .http_disable_tls = true,
  25    .crypto_fork_safety = false,
  26};
  27
  28pub fn main() !void {
  29    // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not
  30    // always the case. So, we do need a true gpa for some things.
  31    var debug_gpa_state: std.heap.DebugAllocator(.{}) = .init;
  32    defer _ = debug_gpa_state.deinit();
  33    const gpa = debug_gpa_state.allocator();
  34
  35    // ...but we'll back our arena by `std.heap.page_allocator` for efficiency.
  36    var single_threaded_arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
  37    defer single_threaded_arena.deinit();
  38    var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = single_threaded_arena.allocator() };
  39    const arena = thread_safe_arena.allocator();
  40
  41    const args = try process.argsAlloc(arena);
  42
  43    var threaded: std.Io.Threaded = .init(gpa);
  44    defer threaded.deinit();
  45    const io = threaded.io();
  46
  47    // skip my own exe name
  48    var arg_idx: usize = 1;
  49
  50    const zig_exe = nextArg(args, &arg_idx) orelse fatal("missing zig compiler path", .{});
  51    const zig_lib_dir = nextArg(args, &arg_idx) orelse fatal("missing zig lib directory path", .{});
  52    const build_root = nextArg(args, &arg_idx) orelse fatal("missing build root directory path", .{});
  53    const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{});
  54    const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{});
  55
  56    const zig_lib_directory: std.Build.Cache.Directory = .{
  57        .path = zig_lib_dir,
  58        .handle = try std.fs.cwd().openDir(zig_lib_dir, .{}),
  59    };
  60
  61    const build_root_directory: std.Build.Cache.Directory = .{
  62        .path = build_root,
  63        .handle = try std.fs.cwd().openDir(build_root, .{}),
  64    };
  65
  66    const local_cache_directory: std.Build.Cache.Directory = .{
  67        .path = cache_root,
  68        .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}),
  69    };
  70
  71    const global_cache_directory: std.Build.Cache.Directory = .{
  72        .path = global_cache_root,
  73        .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),
  74    };
  75
  76    var graph: std.Build.Graph = .{
  77        .io = io,
  78        .arena = arena,
  79        .cache = .{
  80            .io = io,
  81            .gpa = arena,
  82            .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
  83        },
  84        .zig_exe = zig_exe,
  85        .env_map = try process.getEnvMap(arena),
  86        .global_cache_root = global_cache_directory,
  87        .zig_lib_directory = zig_lib_directory,
  88        .host = .{
  89            .query = .{},
  90            .result = try std.zig.system.resolveTargetQuery(io, .{}),
  91        },
  92        .time_report = false,
  93    };
  94
  95    graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
  96    graph.cache.addPrefix(build_root_directory);
  97    graph.cache.addPrefix(local_cache_directory);
  98    graph.cache.addPrefix(global_cache_directory);
  99    graph.cache.hash.addBytes(builtin.zig_version_string);
 100
 101    const builder = try std.Build.create(
 102        &graph,
 103        build_root_directory,
 104        local_cache_directory,
 105        dependencies.root_deps,
 106    );
 107
 108    var targets = std.array_list.Managed([]const u8).init(arena);
 109    var debug_log_scopes = std.array_list.Managed([]const u8).init(arena);
 110
 111    var install_prefix: ?[]const u8 = null;
 112    var dir_list = std.Build.DirList{};
 113    var error_style: ErrorStyle = .verbose;
 114    var multiline_errors: MultilineErrors = .indent;
 115    var summary: ?Summary = null;
 116    var max_rss: u64 = 0;
 117    var skip_oom_steps = false;
 118    var test_timeout_ns: ?u64 = null;
 119    var color: Color = .auto;
 120    var help_menu = false;
 121    var steps_menu = false;
 122    var output_tmp_nonce: ?[16]u8 = null;
 123    var watch = false;
 124    var fuzz: ?std.Build.Fuzz.Mode = null;
 125    var debounce_interval_ms: u16 = 50;
 126    var webui_listen: ?Io.net.IpAddress = null;
 127
 128    if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| {
 129        if (std.meta.stringToEnum(ErrorStyle, str)) |style| {
 130            error_style = style;
 131        }
 132    }
 133
 134    if (try std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(arena)) |str| {
 135        if (std.meta.stringToEnum(MultilineErrors, str)) |style| {
 136            multiline_errors = style;
 137        }
 138    }
 139
 140    while (nextArg(args, &arg_idx)) |arg| {
 141        if (mem.startsWith(u8, arg, "-Z")) {
 142            if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg});
 143            output_tmp_nonce = arg[2..18].*;
 144        } else if (mem.startsWith(u8, arg, "-D")) {
 145            const option_contents = arg[2..];
 146            if (option_contents.len == 0)
 147                fatalWithHint("expected option name after '-D'", .{});
 148            if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
 149                const option_name = option_contents[0..name_end];
 150                const option_value = option_contents[name_end + 1 ..];
 151                if (try builder.addUserInputOption(option_name, option_value))
 152                    fatal("  access the help menu with 'zig build -h'", .{});
 153            } else {
 154                if (try builder.addUserInputFlag(option_contents))
 155                    fatal("  access the help menu with 'zig build -h'", .{});
 156            }
 157        } else if (mem.startsWith(u8, arg, "-")) {
 158            if (mem.eql(u8, arg, "--verbose")) {
 159                builder.verbose = true;
 160            } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
 161                help_menu = true;
 162            } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
 163                install_prefix = nextArgOrFatal(args, &arg_idx);
 164            } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
 165                steps_menu = true;
 166            } else if (mem.startsWith(u8, arg, "-fsys=")) {
 167                const name = arg["-fsys=".len..];
 168                graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM");
 169            } else if (mem.startsWith(u8, arg, "-fno-sys=")) {
 170                const name = arg["-fno-sys=".len..];
 171                graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM");
 172            } else if (mem.eql(u8, arg, "--release")) {
 173                builder.release_mode = .any;
 174            } else if (mem.startsWith(u8, arg, "--release=")) {
 175                const text = arg["--release=".len..];
 176                builder.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse {
 177                    fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{
 178                        arg, text,
 179                    });
 180                };
 181            } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
 182                dir_list.lib_dir = nextArgOrFatal(args, &arg_idx);
 183            } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
 184                dir_list.exe_dir = nextArgOrFatal(args, &arg_idx);
 185            } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
 186                dir_list.include_dir = nextArgOrFatal(args, &arg_idx);
 187            } else if (mem.eql(u8, arg, "--sysroot")) {
 188                builder.sysroot = nextArgOrFatal(args, &arg_idx);
 189            } else if (mem.eql(u8, arg, "--maxrss")) {
 190                const max_rss_text = nextArgOrFatal(args, &arg_idx);
 191                max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| {
 192                    std.debug.print("invalid byte size: '{s}': {s}\n", .{
 193                        max_rss_text, @errorName(err),
 194                    });
 195                    process.exit(1);
 196                };
 197            } else if (mem.eql(u8, arg, "--skip-oom-steps")) {
 198                skip_oom_steps = true;
 199            } else if (mem.eql(u8, arg, "--test-timeout")) {
 200                const units: []const struct { []const u8, u64 } = &.{
 201                    .{ "ns", 1 },
 202                    .{ "nanosecond", 1 },
 203                    .{ "us", std.time.ns_per_us },
 204                    .{ "microsecond", std.time.ns_per_us },
 205                    .{ "ms", std.time.ns_per_ms },
 206                    .{ "millisecond", std.time.ns_per_ms },
 207                    .{ "s", std.time.ns_per_s },
 208                    .{ "second", std.time.ns_per_s },
 209                    .{ "m", std.time.ns_per_min },
 210                    .{ "minute", std.time.ns_per_min },
 211                    .{ "h", std.time.ns_per_hour },
 212                    .{ "hour", std.time.ns_per_hour },
 213                };
 214                const timeout_str = nextArgOrFatal(args, &arg_idx);
 215                const num_end_idx = std.mem.findLastNone(u8, timeout_str, "abcdefghijklmnopqrstuvwxyz") orelse fatal(
 216                    "invalid timeout '{s}': expected unit (ns, us, ms, s, m, h)",
 217                    .{timeout_str},
 218                );
 219                const num_str = timeout_str[0 .. num_end_idx + 1];
 220                const unit_str = timeout_str[num_end_idx + 1 ..];
 221                const unit_factor: f64 = for (units) |unit_and_factor| {
 222                    if (std.mem.eql(u8, unit_str, unit_and_factor[0])) {
 223                        break @floatFromInt(unit_and_factor[1]);
 224                    }
 225                } else fatal(
 226                    "invalid timeout '{s}': invalid unit '{s}' (expected ns, us, ms, s, m, h)",
 227                    .{ timeout_str, unit_str },
 228                );
 229                const num_parsed = std.fmt.parseFloat(f64, num_str) catch |err| fatal(
 230                    "invalid timeout '{s}': invalid number '{s}' ({t})",
 231                    .{ timeout_str, num_str, err },
 232                );
 233                test_timeout_ns = std.math.lossyCast(u64, unit_factor * num_parsed);
 234            } else if (mem.eql(u8, arg, "--search-prefix")) {
 235                const search_prefix = nextArgOrFatal(args, &arg_idx);
 236                builder.addSearchPrefix(search_prefix);
 237            } else if (mem.eql(u8, arg, "--libc")) {
 238                builder.libc_file = nextArgOrFatal(args, &arg_idx);
 239            } else if (mem.eql(u8, arg, "--color")) {
 240                const next_arg = nextArg(args, &arg_idx) orelse
 241                    fatalWithHint("expected [auto|on|off] after '{s}'", .{arg});
 242                color = std.meta.stringToEnum(Color, next_arg) orelse {
 243                    fatalWithHint("expected [auto|on|off] after '{s}', found '{s}'", .{
 244                        arg, next_arg,
 245                    });
 246                };
 247            } else if (mem.eql(u8, arg, "--error-style")) {
 248                const next_arg = nextArg(args, &arg_idx) orelse
 249                    fatalWithHint("expected style after '{s}'", .{arg});
 250                error_style = std.meta.stringToEnum(ErrorStyle, next_arg) orelse {
 251                    fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg });
 252                };
 253            } else if (mem.eql(u8, arg, "--multiline-errors")) {
 254                const next_arg = nextArg(args, &arg_idx) orelse
 255                    fatalWithHint("expected style after '{s}'", .{arg});
 256                multiline_errors = std.meta.stringToEnum(MultilineErrors, next_arg) orelse {
 257                    fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg });
 258                };
 259            } else if (mem.eql(u8, arg, "--summary")) {
 260                const next_arg = nextArg(args, &arg_idx) orelse
 261                    fatalWithHint("expected [all|new|failures|line|none] after '{s}'", .{arg});
 262                summary = std.meta.stringToEnum(Summary, next_arg) orelse {
 263                    fatalWithHint("expected [all|new|failures|line|none] after '{s}', found '{s}'", .{
 264                        arg, next_arg,
 265                    });
 266                };
 267            } else if (mem.eql(u8, arg, "--seed")) {
 268                const next_arg = nextArg(args, &arg_idx) orelse
 269                    fatalWithHint("expected u32 after '{s}'", .{arg});
 270                graph.random_seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
 271                    fatal("unable to parse seed '{s}' as unsigned 32-bit integer: {s}\n", .{
 272                        next_arg, @errorName(err),
 273                    });
 274                };
 275            } else if (mem.eql(u8, arg, "--build-id")) {
 276                builder.build_id = .fast;
 277            } else if (mem.startsWith(u8, arg, "--build-id=")) {
 278                const style = arg["--build-id=".len..];
 279                builder.build_id = std.zig.BuildId.parse(style) catch |err| {
 280                    fatal("unable to parse --build-id style '{s}': {s}", .{
 281                        style, @errorName(err),
 282                    });
 283                };
 284            } else if (mem.eql(u8, arg, "--debounce")) {
 285                const next_arg = nextArg(args, &arg_idx) orelse
 286                    fatalWithHint("expected u16 after '{s}'", .{arg});
 287                debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| {
 288                    fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {s}\n", .{
 289                        next_arg, @errorName(err),
 290                    });
 291                };
 292            } else if (mem.eql(u8, arg, "--webui")) {
 293                if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
 294            } else if (mem.startsWith(u8, arg, "--webui=")) {
 295                const addr_str = arg["--webui=".len..];
 296                if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{});
 297                webui_listen = Io.net.IpAddress.parseLiteral(addr_str) catch |err| {
 298                    fatal("invalid web UI address '{s}': {s}", .{ addr_str, @errorName(err) });
 299                };
 300            } else if (mem.eql(u8, arg, "--debug-log")) {
 301                const next_arg = nextArgOrFatal(args, &arg_idx);
 302                try debug_log_scopes.append(next_arg);
 303            } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
 304                builder.debug_pkg_config = true;
 305            } else if (mem.eql(u8, arg, "--debug-rt")) {
 306                graph.debug_compiler_runtime_libs = true;
 307            } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
 308                builder.debug_compile_errors = true;
 309            } else if (mem.eql(u8, arg, "--debug-incremental")) {
 310                builder.debug_incremental = true;
 311            } else if (mem.eql(u8, arg, "--system")) {
 312                // The usage text shows another argument after this parameter
 313                // but it is handled by the parent process. The build runner
 314                // only sees this flag.
 315                graph.system_package_mode = true;
 316            } else if (mem.eql(u8, arg, "--libc-runtimes") or mem.eql(u8, arg, "--glibc-runtimes")) {
 317                // --glibc-runtimes was the old name of the flag; kept for compatibility for now.
 318                builder.libc_runtimes_dir = nextArgOrFatal(args, &arg_idx);
 319            } else if (mem.eql(u8, arg, "--verbose-link")) {
 320                builder.verbose_link = true;
 321            } else if (mem.eql(u8, arg, "--verbose-air")) {
 322                builder.verbose_air = true;
 323            } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
 324                builder.verbose_llvm_ir = "-";
 325            } else if (mem.startsWith(u8, arg, "--verbose-llvm-ir=")) {
 326                builder.verbose_llvm_ir = arg["--verbose-llvm-ir=".len..];
 327            } else if (mem.startsWith(u8, arg, "--verbose-llvm-bc=")) {
 328                builder.verbose_llvm_bc = arg["--verbose-llvm-bc=".len..];
 329            } else if (mem.eql(u8, arg, "--verbose-cimport")) {
 330                builder.verbose_cimport = true;
 331            } else if (mem.eql(u8, arg, "--verbose-cc")) {
 332                builder.verbose_cc = true;
 333            } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
 334                builder.verbose_llvm_cpu_features = true;
 335            } else if (mem.eql(u8, arg, "--watch")) {
 336                watch = true;
 337            } else if (mem.eql(u8, arg, "--time-report")) {
 338                graph.time_report = true;
 339                if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
 340            } else if (mem.eql(u8, arg, "--fuzz")) {
 341                fuzz = .{ .forever = undefined };
 342                if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
 343            } else if (mem.startsWith(u8, arg, "--fuzz=")) {
 344                const value = arg["--fuzz=".len..];
 345                if (value.len == 0) fatal("missing argument to --fuzz", .{});
 346
 347                const unit: u8 = value[value.len - 1];
 348                const digits = switch (unit) {
 349                    '0'...'9' => value,
 350                    'K', 'M', 'G' => value[0 .. value.len - 1],
 351                    else => fatal(
 352                        "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
 353                        .{},
 354                    ),
 355                };
 356
 357                const amount = std.fmt.parseInt(u64, digits, 10) catch {
 358                    fatal(
 359                        "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
 360                        .{},
 361                    );
 362                };
 363
 364                const normalized_amount = std.math.mul(u64, amount, switch (unit) {
 365                    else => unreachable,
 366                    '0'...'9' => 1,
 367                    'K' => 1000,
 368                    'M' => 1_000_000,
 369                    'G' => 1_000_000_000,
 370                }) catch fatal("fuzzing limit amount overflows u64", .{});
 371
 372                fuzz = .{
 373                    .limit = .{
 374                        .amount = normalized_amount,
 375                    },
 376                };
 377            } else if (mem.eql(u8, arg, "-fincremental")) {
 378                graph.incremental = true;
 379            } else if (mem.eql(u8, arg, "-fno-incremental")) {
 380                graph.incremental = false;
 381            } else if (mem.eql(u8, arg, "-fwine")) {
 382                builder.enable_wine = true;
 383            } else if (mem.eql(u8, arg, "-fno-wine")) {
 384                builder.enable_wine = false;
 385            } else if (mem.eql(u8, arg, "-fqemu")) {
 386                builder.enable_qemu = true;
 387            } else if (mem.eql(u8, arg, "-fno-qemu")) {
 388                builder.enable_qemu = false;
 389            } else if (mem.eql(u8, arg, "-fwasmtime")) {
 390                builder.enable_wasmtime = true;
 391            } else if (mem.eql(u8, arg, "-fno-wasmtime")) {
 392                builder.enable_wasmtime = false;
 393            } else if (mem.eql(u8, arg, "-frosetta")) {
 394                builder.enable_rosetta = true;
 395            } else if (mem.eql(u8, arg, "-fno-rosetta")) {
 396                builder.enable_rosetta = false;
 397            } else if (mem.eql(u8, arg, "-fdarling")) {
 398                builder.enable_darling = true;
 399            } else if (mem.eql(u8, arg, "-fno-darling")) {
 400                builder.enable_darling = false;
 401            } else if (mem.eql(u8, arg, "-fallow-so-scripts")) {
 402                graph.allow_so_scripts = true;
 403            } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) {
 404                graph.allow_so_scripts = false;
 405            } else if (mem.eql(u8, arg, "-freference-trace")) {
 406                builder.reference_trace = 256;
 407            } else if (mem.startsWith(u8, arg, "-freference-trace=")) {
 408                const num = arg["-freference-trace=".len..];
 409                builder.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
 410                    std.debug.print("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) });
 411                    process.exit(1);
 412                };
 413            } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
 414                builder.reference_trace = null;
 415            } else if (mem.cutPrefix(u8, arg, "-j")) |text| {
 416                const n = std.fmt.parseUnsigned(u32, text, 10) catch |err|
 417                    fatal("unable to parse jobs count '{s}': {t}", .{ text, err });
 418                if (n < 1) fatal("number of jobs must be at least 1", .{});
 419                threaded.setAsyncLimit(.limited(n));
 420            } else if (mem.eql(u8, arg, "--")) {
 421                builder.args = argsRest(args, arg_idx);
 422                break;
 423            } else {
 424                fatalWithHint("unrecognized argument: '{s}'", .{arg});
 425            }
 426        } else {
 427            try targets.append(arg);
 428        }
 429    }
 430
 431    if (webui_listen != null) {
 432        if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{});
 433        if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{});
 434    }
 435
 436    const ttyconf = color.detectTtyConf();
 437
 438    const main_progress_node = std.Progress.start(.{
 439        .disable_printing = (color == .off),
 440    });
 441    defer main_progress_node.end();
 442
 443    builder.debug_log_scopes = debug_log_scopes.items;
 444    builder.resolveInstallPrefix(install_prefix, dir_list);
 445    {
 446        var prog_node = main_progress_node.start("Configure", 0);
 447        defer prog_node.end();
 448        try builder.runBuild(root);
 449        createModuleDependencies(builder) catch @panic("OOM");
 450    }
 451
 452    if (graph.needed_lazy_dependencies.entries.len != 0) {
 453        var buffer: std.ArrayList(u8) = .empty;
 454        for (graph.needed_lazy_dependencies.keys()) |k| {
 455            try buffer.appendSlice(arena, k);
 456            try buffer.append(arena, '\n');
 457        }
 458        const s = std.fs.path.sep_str;
 459        const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{}));
 460        local_cache_directory.handle.writeFile(.{
 461            .sub_path = tmp_sub_path,
 462            .data = buffer.items,
 463            .flags = .{ .exclusive = true },
 464        }) catch |err| {
 465            fatal("unable to write configuration results to '{f}{s}': {s}", .{
 466                local_cache_directory, tmp_sub_path, @errorName(err),
 467            });
 468        };
 469        process.exit(3); // Indicate configure phase failed with meaningful stdout.
 470    }
 471
 472    if (builder.validateUserInputDidItFail()) {
 473        fatal("  access the help menu with 'zig build -h'", .{});
 474    }
 475
 476    validateSystemLibraryOptions(builder);
 477
 478    if (help_menu) {
 479        var w = initStdoutWriter();
 480        printUsage(builder, w) catch return stdout_writer_allocation.err.?;
 481        w.flush() catch return stdout_writer_allocation.err.?;
 482        return;
 483    }
 484
 485    if (steps_menu) {
 486        var w = initStdoutWriter();
 487        printSteps(builder, w) catch return stdout_writer_allocation.err.?;
 488        w.flush() catch return stdout_writer_allocation.err.?;
 489        return;
 490    }
 491
 492    var run: Run = .{
 493        .gpa = gpa,
 494
 495        .max_rss = max_rss,
 496        .max_rss_is_default = false,
 497        .max_rss_mutex = .init,
 498        .skip_oom_steps = skip_oom_steps,
 499        .unit_test_timeout_ns = test_timeout_ns,
 500
 501        .watch = watch,
 502        .web_server = undefined, // set after `prepare`
 503        .memory_blocked_steps = .empty,
 504        .step_stack = .empty,
 505
 506        .claimed_rss = 0,
 507        .error_style = error_style,
 508        .multiline_errors = multiline_errors,
 509        .summary = summary orelse if (watch or webui_listen != null) .line else .failures,
 510
 511        .ttyconf = ttyconf,
 512    };
 513    defer {
 514        run.memory_blocked_steps.deinit(gpa);
 515        run.step_stack.deinit(gpa);
 516    }
 517
 518    if (run.max_rss == 0) {
 519        run.max_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
 520        run.max_rss_is_default = true;
 521    }
 522
 523    prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) {
 524        error.DependencyLoopDetected => {
 525            // Perhaps in the future there could be an Advanced Options flag such as
 526            // --debug-build-runner-leaks which would make this code return instead of
 527            // calling exit.
 528            std.debug.lockStdErr();
 529            process.exit(1);
 530        },
 531        else => |e| return e,
 532    };
 533
 534    var w: Watch = w: {
 535        if (!watch) break :w undefined;
 536        if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag});
 537        break :w try .init();
 538    };
 539
 540    const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});
 541
 542    run.web_server = if (webui_listen) |listen_address| ws: {
 543        if (builtin.single_threaded) unreachable; // `fatal` above
 544        break :ws .init(.{
 545            .gpa = gpa,
 546            .ttyconf = ttyconf,
 547            .graph = &graph,
 548            .all_steps = run.step_stack.keys(),
 549            .root_prog_node = main_progress_node,
 550            .watch = watch,
 551            .listen_address = listen_address,
 552            .base_timestamp = now,
 553        });
 554    } else null;
 555
 556    if (run.web_server) |*ws| {
 557        ws.start() catch |err| fatal("failed to start web server: {t}", .{err});
 558    }
 559
 560    rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {
 561        const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);
 562        defer std.debug.unlockStderrWriter();
 563        try bw.writeAll("\x1B[2J\x1B[3J\x1B[H");
 564    }) {
 565        if (run.web_server) |*ws| ws.startBuild();
 566
 567        try runStepNames(
 568            builder,
 569            targets.items,
 570            main_progress_node,
 571            &run,
 572            fuzz,
 573        );
 574
 575        if (run.web_server) |*web_server| {
 576            if (fuzz) |mode| if (mode != .forever) fatal(
 577                "error: limited fuzzing is not implemented yet for --webui",
 578                .{},
 579            );
 580
 581            web_server.finishBuild(.{ .fuzz = fuzz != null });
 582        }
 583
 584        if (run.web_server) |*ws| {
 585            assert(!watch); // fatal error after CLI parsing
 586            while (true) switch (try ws.wait()) {
 587                .rebuild => {
 588                    for (run.step_stack.keys()) |step| {
 589                        step.state = .precheck_done;
 590                        step.reset(gpa);
 591                    }
 592                    continue :rebuild;
 593                },
 594            };
 595        }
 596
 597        // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
 598        if (!Watch.have_impl) unreachable;
 599
 600        try w.update(gpa, run.step_stack.keys());
 601
 602        // Wait until a file system notification arrives. Read all such events
 603        // until the buffer is empty. Then wait for a debounce interval, resetting
 604        // if any more events come in. After the debounce interval has passed,
 605        // trigger a rebuild on all steps with modified inputs, as well as their
 606        // recursive dependants.
 607        var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
 608        const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{
 609            w.dir_count, countSubProcesses(run.step_stack.keys()),
 610        }) catch &caption_buf;
 611        var debouncing_node = main_progress_node.start(caption, 0);
 612        var in_debounce = false;
 613        while (true) switch (try w.wait(gpa, if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) {
 614            .timeout => {
 615                assert(in_debounce);
 616                debouncing_node.end();
 617                markFailedStepsDirty(gpa, run.step_stack.keys());
 618                continue :rebuild;
 619            },
 620            .dirty => if (!in_debounce) {
 621                in_debounce = true;
 622                debouncing_node.end();
 623                debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
 624            },
 625            .clean => {},
 626        };
 627    }
 628}
 629
 630fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void {
 631    for (all_steps) |step| switch (step.state) {
 632        .dependency_failure, .failure, .skipped => step.recursiveReset(gpa),
 633        else => continue,
 634    };
 635    // Now that all dirty steps have been found, the remaining steps that
 636    // succeeded from last run shall be marked "cached".
 637    for (all_steps) |step| switch (step.state) {
 638        .success => step.result_cached = true,
 639        else => continue,
 640    };
 641}
 642
 643fn countSubProcesses(all_steps: []const *Step) usize {
 644    var count: usize = 0;
 645    for (all_steps) |s| {
 646        count += @intFromBool(s.getZigProcess() != null);
 647    }
 648    return count;
 649}
 650
 651const Run = struct {
 652    gpa: Allocator,
 653    max_rss: u64,
 654    max_rss_is_default: bool,
 655    max_rss_mutex: Io.Mutex,
 656    skip_oom_steps: bool,
 657    unit_test_timeout_ns: ?u64,
 658    watch: bool,
 659    web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn,
 660    /// Allocated into `gpa`.
 661    memory_blocked_steps: std.ArrayList(*Step),
 662    /// Allocated into `gpa`.
 663    step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),
 664    /// Similar to the `tty.Config` returned by `std.debug.lockStderrWriter`,
 665    /// but also respects the '--color' flag.
 666    ttyconf: tty.Config,
 667
 668    claimed_rss: usize,
 669    error_style: ErrorStyle,
 670    multiline_errors: MultilineErrors,
 671    summary: Summary,
 672};
 673
 674fn prepare(
 675    arena: Allocator,
 676    b: *std.Build,
 677    step_names: []const []const u8,
 678    run: *Run,
 679    seed: u32,
 680) !void {
 681    const gpa = run.gpa;
 682    const step_stack = &run.step_stack;
 683
 684    if (step_names.len == 0) {
 685        try step_stack.put(gpa, b.default_step, {});
 686    } else {
 687        try step_stack.ensureUnusedCapacity(gpa, step_names.len);
 688        for (0..step_names.len) |i| {
 689            const step_name = step_names[step_names.len - i - 1];
 690            const s = b.top_level_steps.get(step_name) orelse {
 691                std.debug.print("no step named '{s}'\n  access the help menu with 'zig build -h'\n", .{step_name});
 692                process.exit(1);
 693            };
 694            step_stack.putAssumeCapacity(&s.step, {});
 695        }
 696    }
 697
 698    const starting_steps = try arena.dupe(*Step, step_stack.keys());
 699
 700    var rng = std.Random.DefaultPrng.init(seed);
 701    const rand = rng.random();
 702    rand.shuffle(*Step, starting_steps);
 703
 704    for (starting_steps) |s| {
 705        try constructGraphAndCheckForDependencyLoop(gpa, b, s, &run.step_stack, rand);
 706    }
 707
 708    {
 709        // Check that we have enough memory to complete the build.
 710        var any_problems = false;
 711        for (step_stack.keys()) |s| {
 712            if (s.max_rss == 0) continue;
 713            if (s.max_rss > run.max_rss) {
 714                if (run.skip_oom_steps) {
 715                    s.state = .skipped_oom;
 716                } else {
 717                    std.debug.print("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory\n", .{
 718                        s.owner.dep_prefix, s.name, s.max_rss, run.max_rss,
 719                    });
 720                    any_problems = true;
 721                }
 722            }
 723        }
 724        if (any_problems) {
 725            if (run.max_rss_is_default) {
 726                std.debug.print("note: use --maxrss to override the default", .{});
 727            }
 728        }
 729    }
 730}
 731
 732fn runStepNames(
 733    b: *std.Build,
 734    step_names: []const []const u8,
 735    parent_prog_node: std.Progress.Node,
 736    run: *Run,
 737    fuzz: ?std.Build.Fuzz.Mode,
 738) !void {
 739    const gpa = run.gpa;
 740    const io = b.graph.io;
 741    const step_stack = &run.step_stack;
 742
 743    {
 744        const step_prog = parent_prog_node.start("steps", step_stack.count());
 745        defer step_prog.end();
 746
 747        var group: Io.Group = .init;
 748        defer group.wait(io);
 749
 750        // Here we spawn the initial set of tasks with a nice heuristic -
 751        // dependency order. Each worker when it finishes a step will then
 752        // check whether it should run any dependants.
 753        const steps_slice = step_stack.keys();
 754        for (0..steps_slice.len) |i| {
 755            const step = steps_slice[steps_slice.len - i - 1];
 756            if (step.state == .skipped_oom) continue;
 757
 758            group.async(io, workerMakeOneStep, .{ &group, b, step, step_prog, run });
 759        }
 760    }
 761
 762    assert(run.memory_blocked_steps.items.len == 0);
 763
 764    var test_pass_count: usize = 0;
 765    var test_skip_count: usize = 0;
 766    var test_fail_count: usize = 0;
 767    var test_crash_count: usize = 0;
 768    var test_timeout_count: usize = 0;
 769
 770    var test_count: usize = 0;
 771
 772    var success_count: usize = 0;
 773    var skipped_count: usize = 0;
 774    var failure_count: usize = 0;
 775    var pending_count: usize = 0;
 776    var total_compile_errors: usize = 0;
 777
 778    for (step_stack.keys()) |s| {
 779        test_pass_count += s.test_results.passCount();
 780        test_skip_count += s.test_results.skip_count;
 781        test_fail_count += s.test_results.fail_count;
 782        test_crash_count += s.test_results.crash_count;
 783        test_timeout_count += s.test_results.timeout_count;
 784
 785        test_count += s.test_results.test_count;
 786
 787        switch (s.state) {
 788            .precheck_unstarted => unreachable,
 789            .precheck_started => unreachable,
 790            .running => unreachable,
 791            .precheck_done => {
 792                // precheck_done is equivalent to dependency_failure in the case of
 793                // transitive dependencies. For example:
 794                // A -> B -> C (failure)
 795                // B will be marked as dependency_failure, while A may never be queued, and thus
 796                // remain in the initial state of precheck_done.
 797                s.state = .dependency_failure;
 798                if (run.web_server) |*ws| ws.updateStepStatus(s, .failure);
 799                pending_count += 1;
 800            },
 801            .dependency_failure => pending_count += 1,
 802            .success => success_count += 1,
 803            .skipped, .skipped_oom => skipped_count += 1,
 804            .failure => {
 805                failure_count += 1;
 806                const compile_errors_len = s.result_error_bundle.errorMessageCount();
 807                if (compile_errors_len > 0) {
 808                    total_compile_errors += compile_errors_len;
 809                }
 810            },
 811        }
 812    }
 813
 814    if (fuzz) |mode| blk: {
 815        switch (builtin.os.tag) {
 816            // Current implementation depends on two things that need to be ported to Windows:
 817            // * Memory-mapping to share data between the fuzzer and build runner.
 818            // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
 819            //   many addresses to source locations).
 820            .windows => fatal("--fuzz not yet implemented for {s}", .{@tagName(builtin.os.tag)}),
 821            else => {},
 822        }
 823        if (@bitSizeOf(usize) != 64) {
 824            // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,
 825            // being compatible with `std.fs.getEndPos() u64`'s return value. This is not the case
 826            // on 32-bit platforms.
 827            // Affects or affected by issues #5185, #22523, and #22464.
 828            fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
 829        }
 830
 831        switch (mode) {
 832            .forever => break :blk,
 833            .limit => {},
 834        }
 835
 836        assert(mode == .limit);
 837        var f = std.Build.Fuzz.init(
 838            gpa,
 839            io,
 840            run.ttyconf,
 841            step_stack.keys(),
 842            parent_prog_node,
 843            mode,
 844        ) catch |err| fatal("failed to start fuzzer: {s}", .{@errorName(err)});
 845        defer f.deinit();
 846
 847        f.start();
 848        f.waitAndPrintReport();
 849    }
 850
 851    // Every test has a state
 852    assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count);
 853
 854    if (failure_count == 0) {
 855        std.Progress.setStatus(.success);
 856    } else {
 857        std.Progress.setStatus(.failure);
 858    }
 859
 860    summary: {
 861        switch (run.summary) {
 862            .all, .new, .line => {},
 863            .failures => if (failure_count == 0) break :summary,
 864            .none => break :summary,
 865        }
 866
 867        const w, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);
 868        defer std.debug.unlockStderrWriter();
 869        const ttyconf = run.ttyconf;
 870
 871        const total_count = success_count + failure_count + pending_count + skipped_count;
 872        ttyconf.setColor(w, .cyan) catch {};
 873        ttyconf.setColor(w, .bold) catch {};
 874        w.writeAll("Build Summary: ") catch {};
 875        ttyconf.setColor(w, .reset) catch {};
 876        w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
 877        {
 878            ttyconf.setColor(w, .dim) catch {};
 879            var first = true;
 880            if (skipped_count > 0) {
 881                w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};
 882                first = false;
 883            }
 884            if (failure_count > 0) {
 885                w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {};
 886                first = false;
 887            }
 888            if (!first) w.writeByte(')') catch {};
 889            ttyconf.setColor(w, .reset) catch {};
 890        }
 891
 892        if (test_count > 0) {
 893            w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
 894            ttyconf.setColor(w, .dim) catch {};
 895            var first = true;
 896            if (test_skip_count > 0) {
 897                w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};
 898                first = false;
 899            }
 900            if (test_fail_count > 0) {
 901                w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {};
 902                first = false;
 903            }
 904            if (test_crash_count > 0) {
 905                w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {};
 906                first = false;
 907            }
 908            if (test_timeout_count > 0) {
 909                w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {};
 910                first = false;
 911            }
 912            if (!first) w.writeByte(')') catch {};
 913            ttyconf.setColor(w, .reset) catch {};
 914        }
 915
 916        w.writeAll("\n") catch {};
 917
 918        if (run.summary == .line) break :summary;
 919
 920        // Print a fancy tree with build results.
 921        var step_stack_copy = try step_stack.clone(gpa);
 922        defer step_stack_copy.deinit(gpa);
 923
 924        var print_node: PrintNode = .{ .parent = null };
 925        if (step_names.len == 0) {
 926            print_node.last = true;
 927            printTreeStep(b, b.default_step, run, w, ttyconf, &print_node, &step_stack_copy) catch {};
 928        } else {
 929            const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {
 930                var i: usize = step_names.len;
 931                while (i > 0) {
 932                    i -= 1;
 933                    const step = b.top_level_steps.get(step_names[i]).?.step;
 934                    const found = switch (run.summary) {
 935                        .all, .line, .none => unreachable,
 936                        .failures => step.state != .success,
 937                        .new => !step.result_cached,
 938                    };
 939                    if (found) break :blk i;
 940                }
 941                break :blk b.top_level_steps.count();
 942            };
 943            for (step_names, 0..) |step_name, i| {
 944                const tls = b.top_level_steps.get(step_name).?;
 945                print_node.last = i + 1 == last_index;
 946                printTreeStep(b, &tls.step, run, w, ttyconf, &print_node, &step_stack_copy) catch {};
 947            }
 948        }
 949        w.writeByte('\n') catch {};
 950    }
 951
 952    if (run.watch or run.web_server != null) return;
 953
 954    // Perhaps in the future there could be an Advanced Options flag such as
 955    // --debug-build-runner-leaks which would make this code return instead of
 956    // calling exit.
 957
 958    const code: u8 = code: {
 959        if (failure_count == 0) break :code 0; // success
 960        if (run.error_style.verboseContext()) break :code 1; // failure; print build command
 961        break :code 2; // failure; do not print build command
 962    };
 963    std.debug.lockStdErr();
 964    process.exit(code);
 965}
 966
 967const PrintNode = struct {
 968    parent: ?*PrintNode,
 969    last: bool = false,
 970};
 971
 972fn printPrefix(node: *PrintNode, stderr: *Writer, ttyconf: tty.Config) !void {
 973    const parent = node.parent orelse return;
 974    if (parent.parent == null) return;
 975    try printPrefix(parent, stderr, ttyconf);
 976    if (parent.last) {
 977        try stderr.writeAll("   ");
 978    } else {
 979        try stderr.writeAll(switch (ttyconf) {
 980            .no_color, .windows_api => "|  ",
 981            .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42  ", // │
 982        });
 983    }
 984}
 985
 986fn printChildNodePrefix(stderr: *Writer, ttyconf: tty.Config) !void {
 987    try stderr.writeAll(switch (ttyconf) {
 988        .no_color, .windows_api => "+- ",
 989        .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
 990    });
 991}
 992
 993fn printStepStatus(
 994    s: *Step,
 995    stderr: *Writer,
 996    ttyconf: tty.Config,
 997    run: *const Run,
 998) !void {
 999    switch (s.state) {
1000        .precheck_unstarted => unreachable,
1001        .precheck_started => unreachable,
1002        .precheck_done => unreachable,
1003        .running => unreachable,
1004
1005        .dependency_failure => {
1006            try ttyconf.setColor(stderr, .dim);
1007            try stderr.writeAll(" transitive failure\n");
1008            try ttyconf.setColor(stderr, .reset);
1009        },
1010
1011        .success => {
1012            try ttyconf.setColor(stderr, .green);
1013            if (s.result_cached) {
1014                try stderr.writeAll(" cached");
1015            } else if (s.test_results.test_count > 0) {
1016                const pass_count = s.test_results.passCount();
1017                assert(s.test_results.test_count == pass_count + s.test_results.skip_count);
1018                try stderr.print(" {d} pass", .{pass_count});
1019                if (s.test_results.skip_count > 0) {
1020                    try ttyconf.setColor(stderr, .reset);
1021                    try stderr.writeAll(", ");
1022                    try ttyconf.setColor(stderr, .yellow);
1023                    try stderr.print("{d} skip", .{s.test_results.skip_count});
1024                }
1025                try ttyconf.setColor(stderr, .reset);
1026                try stderr.print(" ({d} total)", .{s.test_results.test_count});
1027            } else {
1028                try stderr.writeAll(" success");
1029            }
1030            try ttyconf.setColor(stderr, .reset);
1031            if (s.result_duration_ns) |ns| {
1032                try ttyconf.setColor(stderr, .dim);
1033                if (ns >= std.time.ns_per_min) {
1034                    try stderr.print(" {d}m", .{ns / std.time.ns_per_min});
1035                } else if (ns >= std.time.ns_per_s) {
1036                    try stderr.print(" {d}s", .{ns / std.time.ns_per_s});
1037                } else if (ns >= std.time.ns_per_ms) {
1038                    try stderr.print(" {d}ms", .{ns / std.time.ns_per_ms});
1039                } else if (ns >= std.time.ns_per_us) {
1040                    try stderr.print(" {d}us", .{ns / std.time.ns_per_us});
1041                } else {
1042                    try stderr.print(" {d}ns", .{ns});
1043                }
1044                try ttyconf.setColor(stderr, .reset);
1045            }
1046            if (s.result_peak_rss != 0) {
1047                const rss = s.result_peak_rss;
1048                try ttyconf.setColor(stderr, .dim);
1049                if (rss >= 1000_000_000) {
1050                    try stderr.print(" MaxRSS:{d}G", .{rss / 1000_000_000});
1051                } else if (rss >= 1000_000) {
1052                    try stderr.print(" MaxRSS:{d}M", .{rss / 1000_000});
1053                } else if (rss >= 1000) {
1054                    try stderr.print(" MaxRSS:{d}K", .{rss / 1000});
1055                } else {
1056                    try stderr.print(" MaxRSS:{d}B", .{rss});
1057                }
1058                try ttyconf.setColor(stderr, .reset);
1059            }
1060            try stderr.writeAll("\n");
1061        },
1062        .skipped, .skipped_oom => |skip| {
1063            try ttyconf.setColor(stderr, .yellow);
1064            try stderr.writeAll(" skipped");
1065            if (skip == .skipped_oom) {
1066                try stderr.writeAll(" (not enough memory)");
1067                try ttyconf.setColor(stderr, .dim);
1068                try stderr.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
1069                try ttyconf.setColor(stderr, .yellow);
1070            }
1071            try stderr.writeAll("\n");
1072            try ttyconf.setColor(stderr, .reset);
1073        },
1074        .failure => {
1075            try printStepFailure(s, stderr, ttyconf, false);
1076            try ttyconf.setColor(stderr, .reset);
1077        },
1078    }
1079}
1080
1081fn printStepFailure(
1082    s: *Step,
1083    stderr: *Writer,
1084    ttyconf: tty.Config,
1085    dim: bool,
1086) !void {
1087    if (s.result_error_bundle.errorMessageCount() > 0) {
1088        try ttyconf.setColor(stderr, .red);
1089        try stderr.print(" {d} errors\n", .{
1090            s.result_error_bundle.errorMessageCount(),
1091        });
1092    } else if (!s.test_results.isSuccess()) {
1093        // These first values include all of the test "statuses". Every test is either passsed,
1094        // skipped, failed, crashed, or timed out.
1095        try ttyconf.setColor(stderr, .green);
1096        try stderr.print(" {d} pass", .{s.test_results.passCount()});
1097        try ttyconf.setColor(stderr, .reset);
1098        if (dim) try ttyconf.setColor(stderr, .dim);
1099        if (s.test_results.skip_count > 0) {
1100            try stderr.writeAll(", ");
1101            try ttyconf.setColor(stderr, .yellow);
1102            try stderr.print("{d} skip", .{s.test_results.skip_count});
1103            try ttyconf.setColor(stderr, .reset);
1104            if (dim) try ttyconf.setColor(stderr, .dim);
1105        }
1106        if (s.test_results.fail_count > 0) {
1107            try stderr.writeAll(", ");
1108            try ttyconf.setColor(stderr, .red);
1109            try stderr.print("{d} fail", .{s.test_results.fail_count});
1110            try ttyconf.setColor(stderr, .reset);
1111            if (dim) try ttyconf.setColor(stderr, .dim);
1112        }
1113        if (s.test_results.crash_count > 0) {
1114            try stderr.writeAll(", ");
1115            try ttyconf.setColor(stderr, .red);
1116            try stderr.print("{d} crash", .{s.test_results.crash_count});
1117            try ttyconf.setColor(stderr, .reset);
1118            if (dim) try ttyconf.setColor(stderr, .dim);
1119        }
1120        if (s.test_results.timeout_count > 0) {
1121            try stderr.writeAll(", ");
1122            try ttyconf.setColor(stderr, .red);
1123            try stderr.print("{d} timeout", .{s.test_results.timeout_count});
1124            try ttyconf.setColor(stderr, .reset);
1125            if (dim) try ttyconf.setColor(stderr, .dim);
1126        }
1127        try stderr.print(" ({d} total)", .{s.test_results.test_count});
1128
1129        // Memory leaks are intentionally written after the total, because is isn't a test *status*,
1130        // but just a flag that any tests -- even passed ones -- can have. We also use a different
1131        // separator, so it looks like:
1132        //   2 pass, 1 skip, 2 fail (5 total); 2 leaks
1133        if (s.test_results.leak_count > 0) {
1134            try stderr.writeAll("; ");
1135            try ttyconf.setColor(stderr, .red);
1136            try stderr.print("{d} leaks", .{s.test_results.leak_count});
1137            try ttyconf.setColor(stderr, .reset);
1138            if (dim) try ttyconf.setColor(stderr, .dim);
1139        }
1140
1141        // It's usually not helpful to know how many error logs there were because they tend to
1142        // just come with other errors (e.g. crashes and leaks print stack traces, and clean
1143        // failures print error traces). So only mention them if they're the only thing causing
1144        // the failure.
1145        const show_err_logs: bool = show: {
1146            var alt_results = s.test_results;
1147            alt_results.log_err_count = 0;
1148            break :show alt_results.isSuccess();
1149        };
1150        if (show_err_logs) {
1151            try stderr.writeAll("; ");
1152            try ttyconf.setColor(stderr, .red);
1153            try stderr.print("{d} error logs", .{s.test_results.log_err_count});
1154            try ttyconf.setColor(stderr, .reset);
1155            if (dim) try ttyconf.setColor(stderr, .dim);
1156        }
1157
1158        try stderr.writeAll("\n");
1159    } else if (s.result_error_msgs.items.len > 0) {
1160        try ttyconf.setColor(stderr, .red);
1161        try stderr.writeAll(" failure\n");
1162    } else {
1163        assert(s.result_stderr.len > 0);
1164        try ttyconf.setColor(stderr, .red);
1165        try stderr.writeAll(" stderr\n");
1166    }
1167}
1168
1169fn printTreeStep(
1170    b: *std.Build,
1171    s: *Step,
1172    run: *const Run,
1173    stderr: *Writer,
1174    ttyconf: tty.Config,
1175    parent_node: *PrintNode,
1176    step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
1177) !void {
1178    const first = step_stack.swapRemove(s);
1179    const summary = run.summary;
1180    const skip = switch (summary) {
1181        .none, .line => unreachable,
1182        .all => false,
1183        .new => s.result_cached,
1184        .failures => s.state == .success,
1185    };
1186    if (skip) return;
1187    try printPrefix(parent_node, stderr, ttyconf);
1188
1189    if (!first) try ttyconf.setColor(stderr, .dim);
1190    if (parent_node.parent != null) {
1191        if (parent_node.last) {
1192            try printChildNodePrefix(stderr, ttyconf);
1193        } else {
1194            try stderr.writeAll(switch (ttyconf) {
1195                .no_color, .windows_api => "+- ",
1196                .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
1197            });
1198        }
1199    }
1200
1201    // dep_prefix omitted here because it is redundant with the tree.
1202    try stderr.writeAll(s.name);
1203
1204    if (first) {
1205        try printStepStatus(s, stderr, ttyconf, run);
1206
1207        const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: {
1208            var i: usize = s.dependencies.items.len;
1209            while (i > 0) {
1210                i -= 1;
1211
1212                const step = s.dependencies.items[i];
1213                const found = switch (summary) {
1214                    .all, .line, .none => unreachable,
1215                    .failures => step.state != .success,
1216                    .new => !step.result_cached,
1217                };
1218                if (found) break :blk i;
1219            }
1220            break :blk s.dependencies.items.len -| 1;
1221        };
1222        for (s.dependencies.items, 0..) |dep, i| {
1223            var print_node: PrintNode = .{
1224                .parent = parent_node,
1225                .last = i == last_index,
1226            };
1227            try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack);
1228        }
1229    } else {
1230        if (s.dependencies.items.len == 0) {
1231            try stderr.writeAll(" (reused)\n");
1232        } else {
1233            try stderr.print(" (+{d} more reused dependencies)\n", .{
1234                s.dependencies.items.len,
1235            });
1236        }
1237        try ttyconf.setColor(stderr, .reset);
1238    }
1239}
1240
1241/// Traverse the dependency graph depth-first and make it undirected by having
1242/// steps know their dependants (they only know dependencies at start).
1243/// Along the way, check that there is no dependency loop, and record the steps
1244/// in traversal order in `step_stack`.
1245/// Each step has its dependencies traversed in random order, this accomplishes
1246/// two things:
1247/// - `step_stack` will be in randomized-depth-first order, so the build runner
1248///   spawns steps in a random (but optimized) order
1249/// - each step's `dependants` list is also filled in a random order, so that
1250///   when it finishes executing in `workerMakeOneStep`, it spawns next steps
1251///   to run in random order
1252fn constructGraphAndCheckForDependencyLoop(
1253    gpa: Allocator,
1254    b: *std.Build,
1255    s: *Step,
1256    step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
1257    rand: std.Random,
1258) !void {
1259    switch (s.state) {
1260        .precheck_started => {
1261            std.debug.print("dependency loop detected:\n  {s}\n", .{s.name});
1262            return error.DependencyLoopDetected;
1263        },
1264        .precheck_unstarted => {
1265            s.state = .precheck_started;
1266
1267            try step_stack.ensureUnusedCapacity(gpa, s.dependencies.items.len);
1268
1269            // We dupe to avoid shuffling the steps in the summary, it depends
1270            // on s.dependencies' order.
1271            const deps = gpa.dupe(*Step, s.dependencies.items) catch @panic("OOM");
1272            defer gpa.free(deps);
1273
1274            rand.shuffle(*Step, deps);
1275
1276            for (deps) |dep| {
1277                try step_stack.put(gpa, dep, {});
1278                try dep.dependants.append(b.allocator, s);
1279                constructGraphAndCheckForDependencyLoop(gpa, b, dep, step_stack, rand) catch |err| {
1280                    if (err == error.DependencyLoopDetected) {
1281                        std.debug.print("  {s}\n", .{s.name});
1282                    }
1283                    return err;
1284                };
1285            }
1286
1287            s.state = .precheck_done;
1288        },
1289        .precheck_done => {},
1290
1291        // These don't happen until we actually run the step graph.
1292        .dependency_failure => unreachable,
1293        .running => unreachable,
1294        .success => unreachable,
1295        .failure => unreachable,
1296        .skipped => unreachable,
1297        .skipped_oom => unreachable,
1298    }
1299}
1300
1301fn workerMakeOneStep(
1302    group: *Io.Group,
1303    b: *std.Build,
1304    s: *Step,
1305    prog_node: std.Progress.Node,
1306    run: *Run,
1307) void {
1308    const io = b.graph.io;
1309    const gpa = run.gpa;
1310
1311    // First, check the conditions for running this step. If they are not met,
1312    // then we return without doing the step, relying on another worker to
1313    // queue this step up again when dependencies are met.
1314    for (s.dependencies.items) |dep| {
1315        switch (@atomicLoad(Step.State, &dep.state, .seq_cst)) {
1316            .success, .skipped => continue,
1317            .failure, .dependency_failure, .skipped_oom => {
1318                @atomicStore(Step.State, &s.state, .dependency_failure, .seq_cst);
1319                if (run.web_server) |*ws| ws.updateStepStatus(s, .failure);
1320                return;
1321            },
1322            .precheck_done, .running => {
1323                // dependency is not finished yet.
1324                return;
1325            },
1326            .precheck_unstarted => unreachable,
1327            .precheck_started => unreachable,
1328        }
1329    }
1330
1331    if (s.max_rss != 0) {
1332        run.max_rss_mutex.lockUncancelable(io);
1333        defer run.max_rss_mutex.unlock(io);
1334
1335        // Avoid running steps twice.
1336        if (s.state != .precheck_done) {
1337            // Another worker got the job.
1338            return;
1339        }
1340
1341        const new_claimed_rss = run.claimed_rss + s.max_rss;
1342        if (new_claimed_rss > run.max_rss) {
1343            // Running this step right now could possibly exceed the allotted RSS.
1344            // Add this step to the queue of memory-blocked steps.
1345            run.memory_blocked_steps.append(gpa, s) catch @panic("OOM");
1346            return;
1347        }
1348
1349        run.claimed_rss = new_claimed_rss;
1350        s.state = .running;
1351    } else {
1352        // Avoid running steps twice.
1353        if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .seq_cst, .seq_cst) != null) {
1354            // Another worker got the job.
1355            return;
1356        }
1357    }
1358
1359    const sub_prog_node = prog_node.start(s.name, 0);
1360    defer sub_prog_node.end();
1361
1362    if (run.web_server) |*ws| ws.updateStepStatus(s, .wip);
1363
1364    const make_result = s.make(.{
1365        .progress_node = sub_prog_node,
1366        .watch = run.watch,
1367        .web_server = if (run.web_server) |*ws| ws else null,
1368        .ttyconf = run.ttyconf,
1369        .unit_test_timeout_ns = run.unit_test_timeout_ns,
1370        .gpa = gpa,
1371    });
1372
1373    // No matter the result, we want to display error/warning messages.
1374    const show_compile_errors = s.result_error_bundle.errorMessageCount() > 0;
1375    const show_error_msgs = s.result_error_msgs.items.len > 0;
1376    const show_stderr = s.result_stderr.len > 0;
1377    if (show_error_msgs or show_compile_errors or show_stderr) {
1378        const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);
1379        defer std.debug.unlockStderrWriter();
1380        const ttyconf = run.ttyconf;
1381        printErrorMessages(gpa, s, .{}, bw, ttyconf, run.error_style, run.multiline_errors) catch {};
1382    }
1383
1384    handle_result: {
1385        if (make_result) |_| {
1386            @atomicStore(Step.State, &s.state, .success, .seq_cst);
1387            if (run.web_server) |*ws| ws.updateStepStatus(s, .success);
1388        } else |err| switch (err) {
1389            error.MakeFailed => {
1390                @atomicStore(Step.State, &s.state, .failure, .seq_cst);
1391                if (run.web_server) |*ws| ws.updateStepStatus(s, .failure);
1392                std.Progress.setStatus(.failure_working);
1393                break :handle_result;
1394            },
1395            error.MakeSkipped => {
1396                @atomicStore(Step.State, &s.state, .skipped, .seq_cst);
1397                if (run.web_server) |*ws| ws.updateStepStatus(s, .success);
1398            },
1399        }
1400
1401        // Successful completion of a step, so we queue up its dependants as well.
1402        for (s.dependants.items) |dep| {
1403            group.async(io, workerMakeOneStep, .{ group, b, dep, prog_node, run });
1404        }
1405    }
1406
1407    // If this is a step that claims resources, we must now queue up other
1408    // steps that are waiting for resources.
1409    if (s.max_rss != 0) {
1410        var dispatch_deps: std.ArrayList(*Step) = .empty;
1411        defer dispatch_deps.deinit(gpa);
1412        dispatch_deps.ensureUnusedCapacity(gpa, run.memory_blocked_steps.items.len) catch @panic("OOM");
1413
1414        {
1415            run.max_rss_mutex.lockUncancelable(io);
1416            defer run.max_rss_mutex.unlock(io);
1417
1418            // Give the memory back to the scheduler.
1419            run.claimed_rss -= s.max_rss;
1420            // Avoid kicking off too many tasks that we already know will not have
1421            // enough resources.
1422            var remaining = run.max_rss - run.claimed_rss;
1423            var i: usize = 0;
1424            for (run.memory_blocked_steps.items) |dep| {
1425                assert(dep.max_rss != 0);
1426                if (dep.max_rss <= remaining) {
1427                    remaining -= dep.max_rss;
1428                    dispatch_deps.appendAssumeCapacity(dep);
1429                } else {
1430                    run.memory_blocked_steps.items[i] = dep;
1431                    i += 1;
1432                }
1433            }
1434            run.memory_blocked_steps.shrinkRetainingCapacity(i);
1435        }
1436        for (dispatch_deps.items) |dep| {
1437            // Must be called without max_rss_mutex held in case it executes recursively.
1438            group.async(io, workerMakeOneStep, .{ group, b, dep, prog_node, run });
1439        }
1440    }
1441}
1442
1443pub fn printErrorMessages(
1444    gpa: Allocator,
1445    failing_step: *Step,
1446    options: std.zig.ErrorBundle.RenderOptions,
1447    stderr: *Writer,
1448    ttyconf: tty.Config,
1449    error_style: ErrorStyle,
1450    multiline_errors: MultilineErrors,
1451) !void {
1452    if (error_style.verboseContext()) {
1453        // Provide context for where these error messages are coming from by
1454        // printing the corresponding Step subtree.
1455        var step_stack: std.ArrayList(*Step) = .empty;
1456        defer step_stack.deinit(gpa);
1457        try step_stack.append(gpa, failing_step);
1458        while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1459            try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
1460        }
1461
1462        // Now, `step_stack` has the subtree that we want to print, in reverse order.
1463        try ttyconf.setColor(stderr, .dim);
1464        var indent: usize = 0;
1465        while (step_stack.pop()) |s| : (indent += 1) {
1466            if (indent > 0) {
1467                try stderr.splatByteAll(' ', (indent - 1) * 3);
1468                try printChildNodePrefix(stderr, ttyconf);
1469            }
1470
1471            try stderr.writeAll(s.name);
1472
1473            if (s == failing_step) {
1474                try printStepFailure(s, stderr, ttyconf, true);
1475            } else {
1476                try stderr.writeAll("\n");
1477            }
1478        }
1479        try ttyconf.setColor(stderr, .reset);
1480    } else {
1481        // Just print the failing step itself.
1482        try ttyconf.setColor(stderr, .dim);
1483        try stderr.writeAll(failing_step.name);
1484        try printStepFailure(failing_step, stderr, ttyconf, true);
1485        try ttyconf.setColor(stderr, .reset);
1486    }
1487
1488    if (failing_step.result_stderr.len > 0) {
1489        try stderr.writeAll(failing_step.result_stderr);
1490        if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1491            try stderr.writeAll("\n");
1492        }
1493    }
1494
1495    try failing_step.result_error_bundle.renderToWriter(options, stderr, ttyconf);
1496
1497    for (failing_step.result_error_msgs.items) |msg| {
1498        try ttyconf.setColor(stderr, .red);
1499        try stderr.writeAll("error:");
1500        try ttyconf.setColor(stderr, .reset);
1501        if (std.mem.indexOfScalar(u8, msg, '\n') == null) {
1502            try stderr.print(" {s}\n", .{msg});
1503        } else switch (multiline_errors) {
1504            .indent => {
1505                var it = std.mem.splitScalar(u8, msg, '\n');
1506                try stderr.print(" {s}\n", .{it.first()});
1507                while (it.next()) |line| {
1508                    try stderr.print("       {s}\n", .{line});
1509                }
1510            },
1511            .newline => try stderr.print("\n{s}\n", .{msg}),
1512            .none => try stderr.print(" {s}\n", .{msg}),
1513        }
1514    }
1515
1516    if (error_style.verboseContext()) {
1517        if (failing_step.result_failed_command) |cmd_str| {
1518            try ttyconf.setColor(stderr, .red);
1519            try stderr.writeAll("failed command: ");
1520            try ttyconf.setColor(stderr, .reset);
1521            try stderr.writeAll(cmd_str);
1522            try stderr.writeByte('\n');
1523        }
1524    }
1525
1526    try stderr.writeByte('\n');
1527}
1528
1529fn printSteps(builder: *std.Build, w: *Writer) !void {
1530    const arena = builder.graph.arena;
1531    for (builder.top_level_steps.values()) |top_level_step| {
1532        const name = if (&top_level_step.step == builder.default_step)
1533            try fmt.allocPrint(arena, "{s} (default)", .{top_level_step.step.name})
1534        else
1535            top_level_step.step.name;
1536        try w.print("  {s:<28} {s}\n", .{ name, top_level_step.description });
1537    }
1538}
1539
1540fn printUsage(b: *std.Build, w: *Writer) !void {
1541    try w.print(
1542        \\Usage: {s} build [steps] [options]
1543        \\
1544        \\Steps:
1545        \\
1546    , .{b.graph.zig_exe});
1547    try printSteps(b, w);
1548
1549    try w.writeAll(
1550        \\
1551        \\General Options:
1552        \\  -p, --prefix [path]          Where to install files (default: zig-out)
1553        \\  --prefix-lib-dir [path]      Where to install libraries
1554        \\  --prefix-exe-dir [path]      Where to install executables
1555        \\  --prefix-include-dir [path]  Where to install C header files
1556        \\
1557        \\  --release[=mode]             Request release mode, optionally specifying a
1558        \\                               preferred optimization mode: fast, safe, small
1559        \\
1560        \\  -fdarling,  -fno-darling     Integration with system-installed Darling to
1561        \\                               execute macOS programs on Linux hosts
1562        \\                               (default: no)
1563        \\  -fqemu,     -fno-qemu        Integration with system-installed QEMU to execute
1564        \\                               foreign-architecture programs on Linux hosts
1565        \\                               (default: no)
1566        \\  --libc-runtimes [path]       Enhances QEMU integration by providing dynamic libc
1567        \\                               (e.g. glibc or musl) built for multiple foreign
1568        \\                               architectures, allowing execution of non-native
1569        \\                               programs that link with libc.
1570        \\  -frosetta,  -fno-rosetta     Rely on Rosetta to execute x86_64 programs on
1571        \\                               ARM64 macOS hosts. (default: no)
1572        \\  -fwasmtime, -fno-wasmtime    Integration with system-installed wasmtime to
1573        \\                               execute WASI binaries. (default: no)
1574        \\  -fwine,     -fno-wine        Integration with system-installed Wine to execute
1575        \\                               Windows programs on Linux hosts. (default: no)
1576        \\
1577        \\  -h, --help                   Print this help and exit
1578        \\  -l, --list-steps             Print available steps
1579        \\  --verbose                    Print commands before executing them
1580        \\  --color [auto|off|on]        Enable or disable colored error messages
1581        \\  --error-style [style]        Control how build errors are printed
1582        \\    verbose                    (Default) Report errors with full context
1583        \\    minimal                    Report errors after summary, excluding context like command lines
1584        \\    verbose_clear              Like 'verbose', but clear the terminal at the start of each update
1585        \\    minimal_clear              Like 'minimal', but clear the terminal at the start of each update
1586        \\  --multiline-errors [style]   Control how multi-line error messages are printed
1587        \\    indent                     (Default) Indent non-initial lines to align with initial line
1588        \\    newline                    Include a leading newline so that the error message is on its own lines
1589        \\    none                       Print as usual so the first line is misaligned
1590        \\  --summary [mode]             Control the printing of the build summary
1591        \\    all                        Print the build summary in its entirety
1592        \\    new                        Omit cached steps
1593        \\    failures                   (Default if short-lived) Only print failed steps
1594        \\    line                       (Default if long-lived) Only print the single-line summary
1595        \\    none                       Do not print the build summary
1596        \\  -j<N>                        Limit concurrent jobs (default is to use all CPU cores)
1597        \\  --maxrss <bytes>             Limit memory usage (default is to use available memory)
1598        \\  --skip-oom-steps             Instead of failing, skip steps that would exceed --maxrss
1599        \\  --test-timeout <timeout>     Limit execution time of unit tests, terminating if exceeded.
1600        \\                               The timeout must include a unit: ns, us, ms, s, m, h
1601        \\  --fetch[=mode]               Fetch dependency tree (optionally choose laziness) and exit
1602        \\    needed                     (Default) Lazy dependencies are fetched as needed
1603        \\    all                        Lazy dependencies are always fetched
1604        \\  --watch                      Continuously rebuild when source files are modified
1605        \\  --debounce <ms>              Delay before rebuilding after changed file detected
1606        \\  --webui[=ip]                 Enable the web interface on the given IP address
1607        \\  --fuzz[=limit]               Continuously search for unit test failures with an optional 
1608        \\                               limit to the max number of iterations. The argument supports
1609        \\                               an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies
1610        \\                               '--webui' when no limit is specified.
1611        \\  --time-report                Force full rebuild and provide detailed information on
1612        \\                               compilation time of Zig source code (implies '--webui')
1613        \\     -fincremental             Enable incremental compilation
1614        \\  -fno-incremental             Disable incremental compilation
1615        \\
1616        \\Project-Specific Options:
1617        \\
1618    );
1619
1620    const arena = b.graph.arena;
1621    if (b.available_options_list.items.len == 0) {
1622        try w.print("  (none)\n", .{});
1623    } else {
1624        for (b.available_options_list.items) |option| {
1625            const name = try fmt.allocPrint(arena, "  -D{s}=[{s}]", .{
1626                option.name,
1627                @tagName(option.type_id),
1628            });
1629            try w.print("{s:<30} {s}\n", .{ name, option.description });
1630            if (option.enum_options) |enum_options| {
1631                const padding = " " ** 33;
1632                try w.writeAll(padding ++ "Supported Values:\n");
1633                for (enum_options) |enum_option| {
1634                    try w.print(padding ++ "  {s}\n", .{enum_option});
1635                }
1636            }
1637        }
1638    }
1639
1640    try w.writeAll(
1641        \\
1642        \\System Integration Options:
1643        \\  --search-prefix [path]       Add a path to look for binaries, libraries, headers
1644        \\  --sysroot [path]             Set the system root directory (usually /)
1645        \\  --libc [file]                Provide a file which specifies libc paths
1646        \\
1647        \\  --system [pkgdir]            Disable package fetching; enable all integrations
1648        \\  -fsys=[name]                 Enable a system integration
1649        \\  -fno-sys=[name]              Disable a system integration
1650        \\
1651        \\  Available System Integrations:                Enabled:
1652        \\
1653    );
1654    if (b.graph.system_library_options.entries.len == 0) {
1655        try w.writeAll("  (none)                                        -\n");
1656    } else {
1657        for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1658            const status = switch (v) {
1659                .declared_enabled => "yes",
1660                .declared_disabled => "no",
1661                .user_enabled, .user_disabled => unreachable, // already emitted error
1662            };
1663            try w.print("    {s:<43} {s}\n", .{ k, status });
1664        }
1665    }
1666
1667    try w.writeAll(
1668        \\
1669        \\Advanced Options:
1670        \\  -freference-trace[=num]      How many lines of reference trace should be shown per compile error
1671        \\  -fno-reference-trace         Disable reference trace
1672        \\  -fallow-so-scripts           Allows .so files to be GNU ld scripts
1673        \\  -fno-allow-so-scripts        (default) .so files must be ELF files
1674        \\  --build-file [file]          Override path to build.zig
1675        \\  --cache-dir [path]           Override path to local Zig cache directory
1676        \\  --global-cache-dir [path]    Override path to global Zig cache directory
1677        \\  --zig-lib-dir [arg]          Override path to Zig lib directory
1678        \\  --build-runner [file]        Override path to build runner
1679        \\  --seed [integer]             For shuffling dependency traversal order (default: random)
1680        \\  --build-id[=style]           At a minor link-time expense, embeds a build ID in binaries
1681        \\      fast                     8-byte non-cryptographic hash (COFF, ELF, WASM)
1682        \\      sha1, tree               20-byte cryptographic hash (ELF, WASM)
1683        \\      md5                      16-byte cryptographic hash (ELF)
1684        \\      uuid                     16-byte random UUID (ELF, WASM)
1685        \\      0x[hexstring]            Constant ID, maximum 32 bytes (ELF, WASM)
1686        \\      none                     (default) No build ID
1687        \\  --debug-log [scope]          Enable debugging the compiler
1688        \\  --debug-pkg-config           Fail if unknown pkg-config flags encountered
1689        \\  --debug-rt                   Debug compiler runtime libraries
1690        \\  --verbose-link               Enable compiler debug output for linking
1691        \\  --verbose-air                Enable compiler debug output for Zig AIR
1692        \\  --verbose-llvm-ir[=file]     Enable compiler debug output for LLVM IR
1693        \\  --verbose-llvm-bc=[file]     Enable compiler debug output for LLVM BC
1694        \\  --verbose-cimport            Enable compiler debug output for C imports
1695        \\  --verbose-cc                 Enable compiler debug output for C compilation
1696        \\  --verbose-llvm-cpu-features  Enable compiler debug output for LLVM CPU features
1697        \\
1698    );
1699}
1700
1701fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 {
1702    if (idx.* >= args.len) return null;
1703    defer idx.* += 1;
1704    return args[idx.*];
1705}
1706
1707fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 {
1708    return nextArg(args, idx) orelse {
1709        std.debug.print("expected argument after '{s}'\n  access the help menu with 'zig build -h'\n", .{args[idx.* - 1]});
1710        process.exit(1);
1711    };
1712}
1713
1714fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 {
1715    if (idx >= args.len) return null;
1716    return args[idx..];
1717}
1718
1719const Color = std.zig.Color;
1720const ErrorStyle = enum {
1721    verbose,
1722    minimal,
1723    verbose_clear,
1724    minimal_clear,
1725    fn verboseContext(s: ErrorStyle) bool {
1726        return switch (s) {
1727            .verbose, .verbose_clear => true,
1728            .minimal, .minimal_clear => false,
1729        };
1730    }
1731    fn clearOnUpdate(s: ErrorStyle) bool {
1732        return switch (s) {
1733            .verbose, .minimal => false,
1734            .verbose_clear, .minimal_clear => true,
1735        };
1736    }
1737};
1738const MultilineErrors = enum { indent, newline, none };
1739const Summary = enum { all, new, failures, line, none };
1740
1741fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1742    std.debug.print(f ++ "\n  access the help menu with 'zig build -h'\n", args);
1743    process.exit(1);
1744}
1745
1746fn validateSystemLibraryOptions(b: *std.Build) void {
1747    var bad = false;
1748    for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1749        switch (v) {
1750            .user_disabled, .user_enabled => {
1751                // The user tried to enable or disable a system library integration, but
1752                // the build script did not recognize that option.
1753                std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
1754                bad = true;
1755            },
1756            .declared_disabled, .declared_enabled => {},
1757        }
1758    }
1759    if (bad) {
1760        std.debug.print("  access the help menu with 'zig build -h'\n", .{});
1761        process.exit(1);
1762    }
1763}
1764
1765/// Starting from all top-level steps in `b`, traverses the entire step graph
1766/// and adds all step dependencies implied by module graphs.
1767fn createModuleDependencies(b: *std.Build) Allocator.Error!void {
1768    const arena = b.graph.arena;
1769
1770    var all_steps: std.AutoArrayHashMapUnmanaged(*Step, void) = .empty;
1771    var next_step_idx: usize = 0;
1772
1773    try all_steps.ensureUnusedCapacity(arena, b.top_level_steps.count());
1774    for (b.top_level_steps.values()) |tls| {
1775        all_steps.putAssumeCapacityNoClobber(&tls.step, {});
1776    }
1777
1778    while (next_step_idx < all_steps.count()) {
1779        const step = all_steps.keys()[next_step_idx];
1780        next_step_idx += 1;
1781
1782        // Set up any implied dependencies for this step. It's important that we do this first, so
1783        // that the loop below discovers steps implied by the module graph.
1784        try createModuleDependenciesForStep(step);
1785
1786        try all_steps.ensureUnusedCapacity(arena, step.dependencies.items.len);
1787        for (step.dependencies.items) |other_step| {
1788            all_steps.putAssumeCapacity(other_step, {});
1789        }
1790    }
1791}
1792
1793/// If the given `Step` is a `Step.Compile`, adds any dependencies for that step which
1794/// are implied by the module graph rooted at `step.cast(Step.Compile).?.root_module`.
1795fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {
1796    const root_module = if (step.cast(Step.Compile)) |cs| root: {
1797        break :root cs.root_module;
1798    } else return; // not a compile step so no module dependencies
1799
1800    // Starting from `root_module`, discover all modules in this graph.
1801    const modules = root_module.getGraph().modules;
1802
1803    // For each of those modules, set up the implied step dependencies.
1804    for (modules) |mod| {
1805        if (mod.root_source_file) |lp| lp.addStepDependencies(step);
1806        for (mod.include_dirs.items) |include_dir| switch (include_dir) {
1807            .path,
1808            .path_system,
1809            .path_after,
1810            .framework_path,
1811            .framework_path_system,
1812            .embed_path,
1813            => |lp| lp.addStepDependencies(step),
1814
1815            .other_step => |other| {
1816                other.getEmittedIncludeTree().addStepDependencies(step);
1817                step.dependOn(&other.step);
1818            },
1819
1820            .config_header_step => |other| step.dependOn(&other.step),
1821        };
1822        for (mod.lib_paths.items) |lp| lp.addStepDependencies(step);
1823        for (mod.rpaths.items) |rpath| switch (rpath) {
1824            .lazy_path => |lp| lp.addStepDependencies(step),
1825            .special => {},
1826        };
1827        for (mod.link_objects.items) |link_object| switch (link_object) {
1828            .static_path,
1829            .assembly_file,
1830            => |lp| lp.addStepDependencies(step),
1831            .other_step => |other| step.dependOn(&other.step),
1832            .system_lib => {},
1833            .c_source_file => |source| source.file.addStepDependencies(step),
1834            .c_source_files => |source_files| source_files.root.addStepDependencies(step),
1835            .win32_resource_file => |rc_source| {
1836                rc_source.file.addStepDependencies(step);
1837                for (rc_source.include_paths) |lp| lp.addStepDependencies(step);
1838            },
1839        };
1840    }
1841}
1842
1843var stdio_buffer_allocation: [256]u8 = undefined;
1844var stdout_writer_allocation: std.fs.File.Writer = undefined;
1845
1846fn initStdoutWriter() *Writer {
1847    stdout_writer_allocation = std.fs.File.stdout().writerStreaming(&stdio_buffer_allocation);
1848    return &stdout_writer_allocation.interface;
1849}