diff options
| author | Sahnvour <sahnvour@pm.me> | 2023-08-12 13:15:05 +0200 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2023-10-19 14:24:35 -0400 |
| commit | b87353a17f4fe297b83d2f75cbf1c737ebb71c74 (patch) | |
| tree | 51c461fec1a46d80df5339ad5984cc6ff8bfd748 /lib | |
| parent | 530dc0405c9f48fa3b241f078cf815164e4448ab (diff) | |
| download | zig-b87353a17f4fe297b83d2f75cbf1c737ebb71c74.tar.gz zig-b87353a17f4fe297b83d2f75cbf1c737ebb71c74.zip | |
std.Build: add --seed argument to randomize step dependencies spawning
help detect possibly hidden dependencies on the running order of steps,
especially in -j1 mode
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/build_runner.zig | 48 |
1 files changed, 43 insertions, 5 deletions
diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 48c70646b1..3a27eab663 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -95,6 +95,7 @@ pub fn main() !void { var max_rss: usize = 0; var skip_oom_steps: bool = false; var color: Color = .auto; + var seed: u32 = 0; const stderr_stream = io.getStdErr().writer(); const stdout_stream = io.getStdOut().writer(); @@ -196,6 +197,15 @@ pub fn main() !void { std.debug.print("Expected argument after {s}\n\n", .{arg}); usageAndErr(builder, false, stderr_stream); } }; + } else if (mem.eql(u8, arg, "--seed")) { + const next_arg = nextArg(args, &arg_idx) orelse { + std.debug.print("Expected u32 after {s}\n\n", .{arg}); + usageAndErr(builder, false, stderr_stream); + }; + seed = std.fmt.parseUnsigned(u32, next_arg, 10) catch |err| { + std.debug.print("unable to parse seed '{s}' as u32: {s}", .{ next_arg, @errorName(err) }); + process.exit(1); + }; } else if (mem.eql(u8, arg, "--debug-log")) { const next_arg = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after {s}\n\n", .{arg}); @@ -329,6 +339,7 @@ pub fn main() !void { main_progress_node, thread_pool_options, &run, + seed, ) catch |err| switch (err) { error.UncleanExit => process.exit(1), else => return err, @@ -355,6 +366,7 @@ fn runStepNames( parent_prog_node: *std.Progress.Node, thread_pool_options: std.Thread.Pool.Options, run: *Run, + seed: u32, ) !void { const gpa = b.allocator; var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{}; @@ -375,8 +387,13 @@ fn runStepNames( } const starting_steps = try arena.dupe(*Step, step_stack.keys()); + + var rng = std.rand.DefaultPrng.init(seed); + const rand = rng.random(); + rand.shuffle(*Step, starting_steps); + for (starting_steps) |s| { - checkForDependencyLoop(b, s, &step_stack) catch |err| switch (err) { + constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) { error.DependencyLoopDetected => return error.UncleanExit, else => |e| return e, }; @@ -509,7 +526,9 @@ fn runStepNames( stderr.writeAll(" (disable with --summary none)") catch {}; ttyconf.setColor(stderr, .reset) catch {}; } - stderr.writeAll("\n") catch {}; + ttyconf.setColor(stderr, .dim) catch {}; + stderr.writer().print("\nseed is {}\n", .{seed}) catch {}; + ttyconf.setColor(stderr, .reset) catch {}; const failures_only = run.summary != Summary.all; // Print a fancy tree with build results. @@ -748,10 +767,22 @@ fn printTreeStep( } } -fn checkForDependencyLoop( +/// Traverse the dependency graph depth-first and make it undirected by having +/// steps know their dependants (they only know dependencies at start). +/// Along the way, check that there is no dependency loop, and record the steps +/// in traversal order in `step_stack`. +/// Each step has its dependencies traversed in random order, this accomplishes +/// two things: +/// - `step_stack` will be in randomized-depth-first order, so the build runner +/// spawns steps in a random (but optimized) order +/// - each step's `dependants` list is also filled in a random order, so that +/// when it finishes executing in `workerMakeOneStep`, it spawns next steps +/// to run in random order +fn constructGraphAndCheckForDependencyLoop( b: *std.Build, s: *Step, step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), + rand: std.rand.Random, ) !void { switch (s.state) { .precheck_started => { @@ -762,10 +793,16 @@ fn checkForDependencyLoop( s.state = .precheck_started; try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len); - for (s.dependencies.items) |dep| { + + // We dupe to avoid shuffling the steps in the summary, it depends + // on s.dependencies' order. + const deps = b.allocator.dupe(*Step, s.dependencies.items) catch @panic("OOM"); + rand.shuffle(*Step, deps); + + for (deps) |dep| { try step_stack.put(b.allocator, dep, {}); try dep.dependants.append(b.allocator, s); - checkForDependencyLoop(b, dep, step_stack) catch |err| { + constructGraphAndCheckForDependencyLoop(b, dep, step_stack, rand) catch |err| { if (err == error.DependencyLoopDetected) { std.debug.print(" {s}\n", .{s.name}); } @@ -1034,6 +1071,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi \\ --global-cache-dir [path] Override path to global Zig cache directory \\ --zig-lib-dir [arg] Override path to Zig lib directory \\ --build-runner [file] Override path to build runner + \\ --seed [integer] For shuffling dependency traversal order (default: random) \\ --debug-log [scope] Enable debugging the compiler \\ --debug-pkg-config Fail if unknown pkg-config flags encountered \\ --verbose-link Enable compiler debug output for linking |
