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 }