| 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,9 +500,10 @@ pub fn main() !void { |
| 500 | const events_len = try std.posix.poll(&poll_fds, timeout); | 500 | const events_len = try std.posix.poll(&poll_fds, timeout); |
| 501 | if (events_len == 0) { | 501 | if (events_len == 0) { |
| 502 | debouncing_node.end(); | 502 | debouncing_node.end(); |
| 503 | Watch.markFailedStepsDirty(gpa, run.step_stack.keys()); | ||
| 503 | continue :rebuild; | 504 | continue :rebuild; |
| 504 | } | 505 | } |
| 505 | if (try markDirtySteps(&w)) { | 506 | if (try w.markDirtySteps(gpa)) { |
| 506 | if (!debouncing) { | 507 | if (!debouncing) { |
| 507 | debouncing = true; | 508 | debouncing = true; |
| 508 | debouncing_node.end(); | 509 | debouncing_node.end(); |
| ... | @@ -513,44 +514,6 @@ pub fn main() !void { | ... | @@ -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 | const Run = struct { | 517 | const Run = struct { |
| 555 | max_rss: u64, | 518 | max_rss: u64, |
| 556 | max_rss_is_default: bool, | 519 | max_rss_is_default: bool, |
| ... | @@ -1319,7 +1282,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void { | ... | @@ -1319,7 +1282,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void { |
| 1319 | \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss | 1282 | \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss |
| 1320 | \\ --fetch Exit after fetching dependency tree | 1283 | \\ --fetch Exit after fetching dependency tree |
| 1321 | \\ --watch Continuously rebuild when source files are modified | 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 | \\Project-Specific Options: | 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,6 +637,31 @@ fn addWatchInputFromPath(step: *Step, path: Build.Cache.Path, basename: []const |
| 637 | try gop.value_ptr.append(gpa, basename); | 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 | test { | 665 | test { |
| 641 | _ = CheckFile; | 666 | _ = CheckFile; |
| 642 | _ = CheckObject; | 667 | _ = CheckObject; |
lib/std/Build/Watch.zig+54| ... | @@ -2,6 +2,7 @@ const std = @import("../std.zig"); | ... | @@ -2,6 +2,7 @@ const std = @import("../std.zig"); |
| 2 | const Watch = @This(); | 2 | const Watch = @This(); |
| 3 | const Step = std.Build.Step; | 3 | const Step = std.Build.Step; |
| 4 | const Allocator = std.mem.Allocator; | 4 | const Allocator = std.mem.Allocator; |
| 5 | const assert = std.debug.assert; | ||
| 5 | 6 | ||
| 6 | dir_table: DirTable, | 7 | dir_table: DirTable, |
| 7 | /// Keyed differently but indexes correspond 1:1 with `dir_table`. | 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,3 +118,56 @@ pub fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path) !LinuxFileHandle |
| 117 | const stack_lfh: LinuxFileHandle = .{ .handle = stack_ptr }; | 118 | const stack_lfh: LinuxFileHandle = .{ .handle = stack_ptr }; |
| 118 | return stack_lfh.clone(gpa); | 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 | } |