authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-13 15:14:49-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-15 10:48:12-07:00
logcff86cf7a17e038db44fa1f72ee5919eea6a6cae
tree185b4c8a3daae10afcfc0e5d9aee5143878ee384
parent658de75500871f28015aa2ff14872eed0410dddf

build_runner now executes the step graph in parallel


2 files changed, 103 insertions(+), 38 deletions(-)

lib/build_runner.zig+93-33
...@@ -14,10 +14,14 @@ pub fn main() !void {...@@ -14,10 +14,14 @@ pub fn main() !void {
14 // Here we use an ArenaAllocator backed by a DirectAllocator because a build is a short-lived,14 // Here we use an ArenaAllocator backed by a DirectAllocator because a build is a short-lived,
15 // one shot program. We don't need to waste time freeing memory and finding places to squish15 // one shot program. We don't need to waste time freeing memory and finding places to squish
16 // bytes into. So we free everything all at once at the very end.16 // bytes into. So we free everything all at once at the very end.
17 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);17 var single_threaded_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
18 defer arena.deinit();18 defer single_threaded_arena.deinit();
19
20 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
21 .child_allocator = single_threaded_arena.allocator(),
22 };
23 const allocator = thread_safe_arena.allocator();
1924
20 const allocator = arena.allocator();
21 var args = try process.argsAlloc(allocator);25 var args = try process.argsAlloc(allocator);
22 defer process.argsFree(allocator, args);26 defer process.argsFree(allocator, args);
2327
...@@ -245,71 +249,127 @@ pub fn main() !void {...@@ -245,71 +249,127 @@ pub fn main() !void {
245 if (builder.validateUserInputDidItFail())249 if (builder.validateUserInputDidItFail())
246 usageAndErr(builder, true, stderr_stream);250 usageAndErr(builder, true, stderr_stream);
247251
248 make(builder, targets.items) catch |err| {252 runStepNames(builder, targets.items) catch |err| {
249 switch (err) {253 switch (err) {
250 error.UncleanExit => process.exit(1),254 error.UncleanExit => process.exit(1),
251 // This error is intended to indicate that the step has already
252 // logged an error message and so printing the error return trace
253 // here would be unwanted extra information, unless the user opts
254 // into it with a debug flag.
255 error.StepFailed => process.exit(1),
256 else => return err,255 else => return err,
257 }256 }
258 };257 };
259}258}
260259
261fn make(b: *std.Build, step_names: []const []const u8) !void {260fn runStepNames(b: *std.Build, step_names: []const []const u8) !void {
262 var wanted_steps = ArrayList(*std.Build.Step).init(b.allocator);261 var step_stack = ArrayList(*std.Build.Step).init(b.allocator);
263 defer wanted_steps.deinit();262 defer step_stack.deinit();
264263
265 if (step_names.len == 0) {264 if (step_names.len == 0) {
266 try wanted_steps.append(b.default_step);265 try step_stack.append(b.default_step);
267 } else {266 } else {
268 for (step_names) |step_name| {267 try step_stack.resize(step_names.len);
268
269 for (step_names) |step_name, i| {
269 const s = b.top_level_steps.get(step_name) orelse {270 const s = b.top_level_steps.get(step_name) orelse {
270 std.debug.print("no step named '{s}'. Access the help menu with 'zig build -h'\n", .{step_name});271 std.debug.print("no step named '{s}'. Access the help menu with 'zig build -h'\n", .{step_name});
271 process.exit(1);272 process.exit(1);
272 };273 };
273 try wanted_steps.append(&s.step);274 step_stack.items[step_names.len - i - 1] = &s.step;
274 }275 }
275 }276 }
276277
277 for (wanted_steps.items) |s| {278 const starting_steps = step_stack.items;
278 checkForDependencyLoop(b, s) catch |err| switch (err) {279 for (starting_steps) |s| {
280 checkForDependencyLoop(b, s, &step_stack) catch |err| switch (err) {
279 error.DependencyLoopDetected => return error.UncleanExit,281 error.DependencyLoopDetected => return error.UncleanExit,
280 else => |e| return e,282 else => |e| return e,
281 };283 };
282 }284 }
283285
284 for (wanted_steps.items) |s| {286 var thread_pool: std.Thread.Pool = undefined;
285 try makeOneStep(b, s);287 try thread_pool.init(b.allocator);
288 defer thread_pool.deinit();
289
290 {
291 var wait_group: std.Thread.WaitGroup = .{};
292 defer wait_group.wait();
293 var i = step_stack.items.len;
294
295 while (i > 0) {
296 i -= 1;
297 const step = step_stack.items[i];
298
299 wait_group.start();
300 thread_pool.spawn(workerMakeOneStep, .{ &wait_group, b, step }) catch
301 @panic("unhandled error");
302 }
286 }303 }
287}
288304
289fn checkForDependencyLoop(b: *std.Build, s: *std.Build.Step) !void {305 var any_failed = false;
290 if (s.loop_tag == .started) {306
291 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});307 for (step_stack.items) |s| {
292 return error.DependencyLoopDetected;308 switch (s.result) {
309 .not_done => unreachable,
310 .success => continue,
311 .failure => |f| {
312 any_failed = true;
313 std.debug.print("{s}: {s}\n", .{
314 s.name, @errorName(f.err_code),
315 });
316 },
317 }
293 }318 }
294 s.loop_tag = .started;
295319
296 for (s.dependencies.items) |dep| {320 if (any_failed) {
297 checkForDependencyLoop(b, dep) catch |err| {321 process.exit(1);
298 if (err == error.DependencyLoopDetected) {322 }
299 std.debug.print(" {s}\n", .{s.name});323}
324
325fn checkForDependencyLoop(
326 b: *std.Build,
327 s: *std.Build.Step,
328 step_stack: *ArrayList(*std.Build.Step),
329) !void {
330 switch (s.loop_tag) {
331 .started => {
332 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
333 return error.DependencyLoopDetected;
334 },
335 .unstarted => {
336 s.loop_tag = .started;
337
338 try step_stack.append(s);
339
340 for (s.dependencies.items) |dep| {
341 checkForDependencyLoop(b, dep, step_stack) catch |err| {
342 if (err == error.DependencyLoopDetected) {
343 std.debug.print(" {s}\n", .{s.name});
344 }
345 return err;
346 };
300 }347 }
301 return err;348
302 };349 s.loop_tag = .done;
350 },
351 .done => {},
303 }352 }
353}
354
355fn workerMakeOneStep(wg: *std.Thread.WaitGroup, b: *std.Build, s: *std.Build.Step) void {
356 defer wg.finish();
357
358 _ = b;
304359
305 s.loop_tag = .done;360 if (s.make()) |_| {
361 s.result = .success;
362 } else |err| {
363 s.result = .{ .failure = .{
364 .err_code = err,
365 } };
366 }
306}367}
307368
308fn makeOneStep(b: *std.Build, s: *std.Build.Step) anyerror!void {369fn makeOneStep(b: *std.Build, s: *std.Build.Step) anyerror!void {
309 for (s.dependencies.items) |dep| {370 for (s.dependencies.items) |dep| {
310 try makeOneStep(b, dep);371 try makeOneStep(b, dep);
311 }372 }
312
313 try s.make();373 try s.make();
314}374}
315375
lib/std/Build/Step.zig+10-5
...@@ -4,7 +4,13 @@ makeFn: *const fn (self: *Step) anyerror!void,...@@ -4,7 +4,13 @@ makeFn: *const fn (self: *Step) anyerror!void,
4dependencies: std.ArrayList(*Step),4dependencies: std.ArrayList(*Step),
5/// Used only during a pre-check for dependency loops.5/// Used only during a pre-check for dependency loops.
6loop_tag: enum { unstarted, started, done },6loop_tag: enum { unstarted, started, done },
7done_flag: bool,7result: union(enum) {
8 not_done,
9 success,
10 failure: struct {
11 err_code: anyerror,
12 },
13},
814
9pub const Id = enum {15pub const Id = enum {
10 top_level,16 top_level,
...@@ -62,7 +68,7 @@ pub fn init(...@@ -62,7 +68,7 @@ pub fn init(
62 .makeFn = makeFn,68 .makeFn = makeFn,
63 .dependencies = std.ArrayList(*Step).init(allocator),69 .dependencies = std.ArrayList(*Step).init(allocator),
64 .loop_tag = .unstarted,70 .loop_tag = .unstarted,
65 .done_flag = false,71 .result = .not_done,
66 };72 };
67}73}
6874
...@@ -71,10 +77,8 @@ pub fn initNoOp(id: Id, name: []const u8, allocator: Allocator) Step {...@@ -71,10 +77,8 @@ pub fn initNoOp(id: Id, name: []const u8, allocator: Allocator) Step {
71}77}
7278
73pub fn make(self: *Step) !void {79pub fn make(self: *Step) !void {
74 if (self.done_flag) return;80 assert(self.result == .not_done);
75
76 try self.makeFn(self);81 try self.makeFn(self);
77 self.done_flag = true;
78}82}
7983
80pub fn dependOn(self: *Step, other: *Step) void {84pub fn dependOn(self: *Step, other: *Step) void {
...@@ -96,3 +100,4 @@ const Step = @This();...@@ -96,3 +100,4 @@ const Step = @This();
96const std = @import("../std.zig");100const std = @import("../std.zig");
97const Build = std.Build;101const Build = std.Build;
98const Allocator = std.mem.Allocator;102const Allocator = std.mem.Allocator;
103const assert = std.debug.assert;