| ... | @@ -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 squish | 15 | // 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(); |
| 19 | | 24 | |
| 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); |
| 23 | | 27 | |
| ... | @@ -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); |
| 247 | | 251 | |
| 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 | } |
| 260 | | 259 | |
| 261 | fn make(b: *std.Build, step_names: []const []const u8) !void { | 260 | fn 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(); |
| 264 | | 263 | |
| 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 | } |
| 276 | | 277 | |
| 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 | } |
| 283 | | 285 | |
| 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 | } | | |
| 288 | | 304 | |
| 289 | fn 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; | | |
| 295 | | 319 | |
| 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 | |
| | 325 | fn 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 | |
| | 355 | fn workerMakeOneStep(wg: *std.Thread.WaitGroup, b: *std.Build, s: *std.Build.Step) void { |
| | 356 | defer wg.finish(); |
| | 357 | |
| | 358 | _ = b; |
| 304 | | 359 | |
| 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 | } |
| 307 | | 368 | |
| 308 | fn makeOneStep(b: *std.Build, s: *std.Build.Step) anyerror!void { | 369 | fn 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 | } |
| 315 | | 375 | |