aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorSahnvour <sahnvour@pm.me>2023-08-12 13:15:05 +0200
committerAndrew Kelley <andrew@ziglang.org>2023-10-19 14:24:35 -0400
commitb87353a17f4fe297b83d2f75cbf1c737ebb71c74 (patch)
tree51c461fec1a46d80df5339ad5984cc6ff8bfd748 /lib
parent530dc0405c9f48fa3b241f078cf815164e4448ab (diff)
downloadzig-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.zig48
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