zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

build_runner.zig (66312B) - Raw


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