| author | |
| committer | |
| log | 001ff7b3b2176467a9169069938396dcca75e93c |
| tree | 74540b2d929440a75c0b3b4241fb1b5893f6aceb |
| parent | 6f89824c22b57e14951870c8f589df33e180f552 |
and make failed steps always be invalidated
and make steps that don't need to be reevaluated marked as cached3 files changed, 82 insertions(+), 40 deletions(-)
lib/compiler/build_runner.zig+3-40| ... | ... | @@ -500,9 +500,10 @@ pub fn main() !void { |
| 500 | 500 | const events_len = try std.posix.poll(&poll_fds, timeout); |
| 501 | 501 | if (events_len == 0) { |
| 502 | 502 | debouncing_node.end(); |
| 503 | Watch.markFailedStepsDirty(gpa, run.step_stack.keys()); | |
| 503 | 504 | continue :rebuild; |
| 504 | 505 | } |
| 505 | if (try markDirtySteps(&w)) { | |
| 506 | if (try w.markDirtySteps(gpa)) { | |
| 506 | 507 | if (!debouncing) { |
| 507 | 508 | debouncing = true; |
| 508 | 509 | debouncing_node.end(); |
| ... | ... | @@ -513,44 +514,6 @@ pub fn main() !void { |
| 513 | 514 | } |
| 514 | 515 | } |
| 515 | 516 | |
| 516 | fn markDirtySteps(w: *Watch) !bool { | |
| 517 | const fanotify = std.os.linux.fanotify; | |
| 518 | const M = fanotify.event_metadata; | |
| 519 | var events_buf: [256 + 4096]u8 = undefined; | |
| 520 | var any_dirty = false; | |
| 521 | while (true) { | |
| 522 | var len = std.posix.read(w.fan_fd, &events_buf) catch |err| switch (err) { | |
| 523 | error.WouldBlock => return any_dirty, | |
| 524 | else => |e| return e, | |
| 525 | }; | |
| 526 | var meta: [*]align(1) M = @ptrCast(&events_buf); | |
| 527 | while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({ | |
| 528 | len -= meta[0].event_len; | |
| 529 | meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len); | |
| 530 | }) { | |
| 531 | assert(meta[0].vers == M.VERSION); | |
| 532 | const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1); | |
| 533 | switch (fid.hdr.info_type) { | |
| 534 | .DFID_NAME => { | |
| 535 | const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); | |
| 536 | const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes); | |
| 537 | const file_name = mem.span(file_name_z); | |
| 538 | const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; | |
| 539 | if (w.handle_table.getPtr(lfh)) |reaction_set| { | |
| 540 | if (reaction_set.getPtr(file_name)) |step_set| { | |
| 541 | for (step_set.keys()) |step| { | |
| 542 | step.state = .precheck_done; | |
| 543 | any_dirty = true; | |
| 544 | } | |
| 545 | } | |
| 546 | } | |
| 547 | }, | |
| 548 | else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}), | |
| 549 | } | |
| 550 | } | |
| 551 | } | |
| 552 | } | |
| 553 | ||
| 554 | 517 | const Run = struct { |
| 555 | 518 | max_rss: u64, |
| 556 | 519 | max_rss_is_default: bool, |
| ... | ... | @@ -1319,7 +1282,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void { |
| 1319 | 1282 | \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss |
| 1320 | 1283 | \\ --fetch Exit after fetching dependency tree |
| 1321 | 1284 | \\ --watch Continuously rebuild when source files are modified |
| 1322 | \\ --debounce <ms> Delay before rebuilding after watched file detection | |
| 1285 | \\ --debounce <ms> Delay before rebuilding after changed file detected | |
| 1323 | 1286 | \\ |
| 1324 | 1287 | \\Project-Specific Options: |
| 1325 | 1288 | \\ |
lib/std/Build/Step.zig+25| ... | ... | @@ -637,6 +637,31 @@ fn addWatchInputFromPath(step: *Step, path: Build.Cache.Path, basename: []const |
| 637 | 637 | try gop.value_ptr.append(gpa, basename); |
| 638 | 638 | } |
| 639 | 639 | |
| 640 | fn reset(step: *Step, gpa: Allocator) void { | |
| 641 | assert(step.state == .precheck_done); | |
| 642 | ||
| 643 | step.result_error_msgs.clearRetainingCapacity(); | |
| 644 | step.result_stderr = ""; | |
| 645 | step.result_cached = false; | |
| 646 | step.result_duration_ns = null; | |
| 647 | step.result_peak_rss = 0; | |
| 648 | step.test_results = .{}; | |
| 649 | ||
| 650 | step.result_error_bundle.deinit(gpa); | |
| 651 | step.result_error_bundle = std.zig.ErrorBundle.empty; | |
| 652 | } | |
| 653 | ||
| 654 | /// Implementation detail of file watching. Prepares the step for being re-evaluated. | |
| 655 | pub fn recursiveReset(step: *Step, gpa: Allocator) void { | |
| 656 | assert(step.state != .precheck_done); | |
| 657 | step.state = .precheck_done; | |
| 658 | step.reset(gpa); | |
| 659 | for (step.dependants.items) |dep| { | |
| 660 | if (dep.state == .precheck_done) continue; | |
| 661 | dep.recursiveReset(gpa); | |
| 662 | } | |
| 663 | } | |
| 664 | ||
| 640 | 665 | test { |
| 641 | 666 | _ = CheckFile; |
| 642 | 667 | _ = CheckObject; |
lib/std/Build/Watch.zig+54| ... | ... | @@ -2,6 +2,7 @@ const std = @import("../std.zig"); |
| 2 | 2 | const Watch = @This(); |
| 3 | 3 | const Step = std.Build.Step; |
| 4 | 4 | const Allocator = std.mem.Allocator; |
| 5 | const assert = std.debug.assert; | |
| 5 | 6 | |
| 6 | 7 | dir_table: DirTable, |
| 7 | 8 | /// Keyed differently but indexes correspond 1:1 with `dir_table`. |
| ... | ... | @@ -117,3 +118,56 @@ pub fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path) !LinuxFileHandle |
| 117 | 118 | const stack_lfh: LinuxFileHandle = .{ .handle = stack_ptr }; |
| 118 | 119 | return stack_lfh.clone(gpa); |
| 119 | 120 | } |
| 121 | ||
| 122 | pub fn markDirtySteps(w: *Watch, gpa: Allocator) !bool { | |
| 123 | const fanotify = std.os.linux.fanotify; | |
| 124 | const M = fanotify.event_metadata; | |
| 125 | var events_buf: [256 + 4096]u8 = undefined; | |
| 126 | var any_dirty = false; | |
| 127 | while (true) { | |
| 128 | var len = std.posix.read(w.fan_fd, &events_buf) catch |err| switch (err) { | |
| 129 | error.WouldBlock => return any_dirty, | |
| 130 | else => |e| return e, | |
| 131 | }; | |
| 132 | var meta: [*]align(1) M = @ptrCast(&events_buf); | |
| 133 | while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({ | |
| 134 | len -= meta[0].event_len; | |
| 135 | meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len); | |
| 136 | }) { | |
| 137 | assert(meta[0].vers == M.VERSION); | |
| 138 | const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1); | |
| 139 | switch (fid.hdr.info_type) { | |
| 140 | .DFID_NAME => { | |
| 141 | const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); | |
| 142 | const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes); | |
| 143 | const file_name = std.mem.span(file_name_z); | |
| 144 | const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; | |
| 145 | if (w.handle_table.getPtr(lfh)) |reaction_set| { | |
| 146 | if (reaction_set.getPtr(file_name)) |step_set| { | |
| 147 | for (step_set.keys()) |step| { | |
| 148 | if (step.state != .precheck_done) { | |
| 149 | step.recursiveReset(gpa); | |
| 150 | any_dirty = true; | |
| 151 | } | |
| 152 | } | |
| 153 | } | |
| 154 | } | |
| 155 | }, | |
| 156 | else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}), | |
| 157 | } | |
| 158 | } | |
| 159 | } | |
| 160 | } | |
| 161 | ||
| 162 | pub fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void { | |
| 163 | for (all_steps) |step| switch (step.state) { | |
| 164 | .dependency_failure, .failure, .skipped => step.recursiveReset(gpa), | |
| 165 | else => continue, | |
| 166 | }; | |
| 167 | // Now that all dirty steps have been found, the remaining steps that | |
| 168 | // succeeded from last run shall be marked "cached". | |
| 169 | for (all_steps) |step| switch (step.state) { | |
| 170 | .success => step.result_cached = true, | |
| 171 | else => continue, | |
| 172 | }; | |
| 173 | } |