| ... | ... | @@ -7,6 +7,7 @@ const mem = std.mem; |
| 7 | 7 | const process = std.process; |
| 8 | 8 | const ArrayList = std.ArrayList; |
| 9 | 9 | const File = std.fs.File; |
| 10 | const Step = std.Build.Step; |
| 10 | 11 | |
| 11 | 12 | pub const dependencies = @import("@dependencies"); |
| 12 | 13 | |
| ... | ... | @@ -258,7 +259,7 @@ pub fn main() !void { |
| 258 | 259 | } |
| 259 | 260 | |
| 260 | 261 | fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { |
| 261 | | var step_stack = ArrayList(*std.Build.Step).init(b.allocator); |
| 262 | var step_stack = ArrayList(*Step).init(b.allocator); |
| 262 | 263 | defer step_stack.deinit(); |
| 263 | 264 | |
| 264 | 265 | if (step_names.len == 0) { |
| ... | ... | @@ -290,28 +291,35 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { |
| 290 | 291 | { |
| 291 | 292 | var wait_group: std.Thread.WaitGroup = .{}; |
| 292 | 293 | defer wait_group.wait(); |
| 293 | | var i = step_stack.items.len; |
| 294 | 294 | |
| 295 | // Here we spawn the initial set of tasks with a nice heuristic - |
| 296 | // dependency order. Each worker when it finishes a step will then |
| 297 | // check whether it should run any dependants. |
| 298 | var i = step_stack.items.len; |
| 295 | 299 | while (i > 0) { |
| 296 | 300 | i -= 1; |
| 297 | 301 | const step = step_stack.items[i]; |
| 298 | 302 | |
| 299 | 303 | wait_group.start(); |
| 300 | | thread_pool.spawn(workerMakeOneStep, .{ &wait_group, b, step }) catch |
| 301 | | @panic("unhandled error"); |
| 304 | thread_pool.spawn(workerMakeOneStep, .{ &wait_group, &thread_pool, b, step }) catch |
| 305 | @panic("OOM"); |
| 302 | 306 | } |
| 303 | 307 | } |
| 304 | 308 | |
| 305 | 309 | var any_failed = false; |
| 306 | 310 | |
| 307 | 311 | for (step_stack.items) |s| { |
| 308 | | switch (s.result) { |
| 309 | | .not_done => unreachable, |
| 312 | switch (s.state) { |
| 313 | .precheck_unstarted => unreachable, |
| 314 | .precheck_started => unreachable, |
| 315 | .precheck_done => unreachable, |
| 316 | .running => unreachable, |
| 317 | .dependency_failure => continue, |
| 310 | 318 | .success => continue, |
| 311 | | .failure => |f| { |
| 319 | .failure => { |
| 312 | 320 | any_failed = true; |
| 313 | 321 | std.debug.print("{s}: {s}\n", .{ |
| 314 | | s.name, @errorName(f.err_code), |
| 322 | s.name, @errorName(s.result.err_code), |
| 315 | 323 | }); |
| 316 | 324 | }, |
| 317 | 325 | } |
| ... | ... | @@ -324,20 +332,20 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { |
| 324 | 332 | |
| 325 | 333 | fn checkForDependencyLoop( |
| 326 | 334 | b: *std.Build, |
| 327 | | s: *std.Build.Step, |
| 328 | | step_stack: *ArrayList(*std.Build.Step), |
| 335 | s: *Step, |
| 336 | step_stack: *ArrayList(*Step), |
| 329 | 337 | ) !void { |
| 330 | | switch (s.loop_tag) { |
| 331 | | .started => { |
| 338 | switch (s.state) { |
| 339 | .precheck_started => { |
| 332 | 340 | std.debug.print("dependency loop detected:\n {s}\n", .{s.name}); |
| 333 | 341 | return error.DependencyLoopDetected; |
| 334 | 342 | }, |
| 335 | | .unstarted => { |
| 336 | | s.loop_tag = .started; |
| 337 | | |
| 338 | | try step_stack.append(s); |
| 343 | .precheck_unstarted => { |
| 344 | s.state = .precheck_started; |
| 339 | 345 | |
| 340 | 346 | for (s.dependencies.items) |dep| { |
| 347 | try step_stack.append(dep); |
| 348 | try dep.dependants.append(b.allocator, s); |
| 341 | 349 | checkForDependencyLoop(b, dep, step_stack) catch |err| { |
| 342 | 350 | if (err == error.DependencyLoopDetected) { |
| 343 | 351 | std.debug.print(" {s}\n", .{s.name}); |
| ... | ... | @@ -346,31 +354,70 @@ fn checkForDependencyLoop( |
| 346 | 354 | }; |
| 347 | 355 | } |
| 348 | 356 | |
| 349 | | s.loop_tag = .done; |
| 357 | s.state = .precheck_done; |
| 350 | 358 | }, |
| 351 | | .done => {}, |
| 359 | .precheck_done => {}, |
| 360 | |
| 361 | // These don't happen until we actually run the step graph. |
| 362 | .dependency_failure => unreachable, |
| 363 | .running => unreachable, |
| 364 | .success => unreachable, |
| 365 | .failure => unreachable, |
| 352 | 366 | } |
| 353 | 367 | } |
| 354 | 368 | |
| 355 | | fn workerMakeOneStep(wg: *std.Thread.WaitGroup, b: *std.Build, s: *std.Build.Step) void { |
| 369 | fn workerMakeOneStep( |
| 370 | wg: *std.Thread.WaitGroup, |
| 371 | thread_pool: *std.Thread.Pool, |
| 372 | b: *std.Build, |
| 373 | s: *Step, |
| 374 | ) void { |
| 356 | 375 | defer wg.finish(); |
| 357 | 376 | |
| 358 | | _ = b; |
| 377 | // First, check the conditions for running this step. If they are not met, |
| 378 | // then we return without doing the step, relying on another worker to |
| 379 | // queue this step up again when dependencies are met. |
| 380 | for (s.dependencies.items) |dep| { |
| 381 | switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) { |
| 382 | .success => continue, |
| 383 | .failure, .dependency_failure => { |
| 384 | @atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst); |
| 385 | return; |
| 386 | }, |
| 387 | .precheck_done, .running => { |
| 388 | // dependency is not finished yet. |
| 389 | return; |
| 390 | }, |
| 391 | .precheck_unstarted => unreachable, |
| 392 | .precheck_started => unreachable, |
| 393 | } |
| 394 | } |
| 359 | 395 | |
| 360 | | if (s.make()) |_| { |
| 361 | | s.result = .success; |
| 362 | | } else |err| { |
| 363 | | s.result = .{ .failure = .{ |
| 364 | | .err_code = err, |
| 365 | | } }; |
| 396 | // Avoid running steps twice. |
| 397 | if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .SeqCst, .SeqCst) != null) { |
| 398 | // Another worker got the job. |
| 399 | return; |
| 366 | 400 | } |
| 367 | | } |
| 368 | 401 | |
| 369 | | fn makeOneStep(b: *std.Build, s: *std.Build.Step) anyerror!void { |
| 370 | | for (s.dependencies.items) |dep| { |
| 371 | | try makeOneStep(b, dep); |
| 402 | // I suspect we will want to pass `b` to make() in a future modification. |
| 403 | // For example, CompileStep does some sus things with modifying the saved |
| 404 | // *Build object in install header steps that might be able to be removed |
| 405 | // by passing the *Build object through the make() functions. |
| 406 | s.make() catch |err| { |
| 407 | s.result = .{ |
| 408 | .err_code = err, |
| 409 | }; |
| 410 | @atomicStore(Step.State, &s.state, .failure, .SeqCst); |
| 411 | return; |
| 412 | }; |
| 413 | |
| 414 | @atomicStore(Step.State, &s.state, .success, .SeqCst); |
| 415 | |
| 416 | // Successful completion of a step, so we queue up its dependants as well. |
| 417 | for (s.dependants.items) |dep| { |
| 418 | wg.start(); |
| 419 | thread_pool.spawn(workerMakeOneStep, .{ wg, thread_pool, b, dep }) catch @panic("OOM"); |
| 372 | 420 | } |
| 373 | | try s.make(); |
| 374 | 421 | } |
| 375 | 422 | |
| 376 | 423 | fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void { |