authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-09 20:05:37-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-12 00:14:07-07:00
log001ff7b3b2176467a9169069938396dcca75e93c
tree74540b2d929440a75c0b3b4241fb1b5893f6aceb
parent6f89824c22b57e14951870c8f589df33e180f552

std.Build.Watch: make dirty steps invalidate each other

and make failed steps always be invalidated and make steps that don't need to be reevaluated marked as cached

3 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}
515516
516fn 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
554const Run = struct {517const 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 --maxrss1282 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1320 \\ --fetch Exit after fetching dependency tree1283 \\ --fetch Exit after fetching dependency tree
1321 \\ --watch Continuously rebuild when source files are modified1284 \\ --watch Continuously rebuild when source files are modified
1322 \\ --debounce <ms> Delay before rebuilding after watched file detection1285 \\ --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}
639639
640fn 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.
655pub 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
640test {665test {
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");
2const Watch = @This();2const Watch = @This();
3const Step = std.Build.Step;3const Step = std.Build.Step;
4const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
5const assert = std.debug.assert;
56
6dir_table: DirTable,7dir_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
122pub 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
162pub 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}