diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index cf883a5811..ea1cfda266 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -500,9 +500,10 @@ pub fn main() !void { const events_len = try std.posix.poll(&poll_fds, timeout); if (events_len == 0) { debouncing_node.end(); + Watch.markFailedStepsDirty(gpa, run.step_stack.keys()); continue :rebuild; } - if (try markDirtySteps(&w)) { + if (try w.markDirtySteps(gpa)) { if (!debouncing) { debouncing = true; debouncing_node.end(); @@ -513,44 +514,6 @@ pub fn main() !void { } } -fn markDirtySteps(w: *Watch) !bool { - const fanotify = std.os.linux.fanotify; - const M = fanotify.event_metadata; - var events_buf: [256 + 4096]u8 = undefined; - var any_dirty = false; - while (true) { - var len = std.posix.read(w.fan_fd, &events_buf) catch |err| switch (err) { - error.WouldBlock => return any_dirty, - else => |e| return e, - }; - var meta: [*]align(1) M = @ptrCast(&events_buf); - while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({ - len -= meta[0].event_len; - meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len); - }) { - assert(meta[0].vers == M.VERSION); - const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1); - switch (fid.hdr.info_type) { - .DFID_NAME => { - const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); - const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes); - const file_name = mem.span(file_name_z); - const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; - if (w.handle_table.getPtr(lfh)) |reaction_set| { - if (reaction_set.getPtr(file_name)) |step_set| { - for (step_set.keys()) |step| { - step.state = .precheck_done; - any_dirty = true; - } - } - } - }, - else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}), - } - } - } -} - const Run = struct { max_rss: u64, max_rss_is_default: bool, @@ -1319,7 +1282,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void { \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss \\ --fetch Exit after fetching dependency tree \\ --watch Continuously rebuild when source files are modified - \\ --debounce Delay before rebuilding after watched file detection + \\ --debounce Delay before rebuilding after changed file detected \\ \\Project-Specific Options: \\ diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index fcab831f67..4b958be284 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -637,6 +637,31 @@ fn addWatchInputFromPath(step: *Step, path: Build.Cache.Path, basename: []const try gop.value_ptr.append(gpa, basename); } +fn reset(step: *Step, gpa: Allocator) void { + assert(step.state == .precheck_done); + + step.result_error_msgs.clearRetainingCapacity(); + step.result_stderr = ""; + step.result_cached = false; + step.result_duration_ns = null; + step.result_peak_rss = 0; + step.test_results = .{}; + + step.result_error_bundle.deinit(gpa); + step.result_error_bundle = std.zig.ErrorBundle.empty; +} + +/// Implementation detail of file watching. Prepares the step for being re-evaluated. +pub fn recursiveReset(step: *Step, gpa: Allocator) void { + assert(step.state != .precheck_done); + step.state = .precheck_done; + step.reset(gpa); + for (step.dependants.items) |dep| { + if (dep.state == .precheck_done) continue; + dep.recursiveReset(gpa); + } +} + test { _ = CheckFile; _ = CheckObject; diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index b04bfcb475..b58e524cab 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -2,6 +2,7 @@ const std = @import("../std.zig"); const Watch = @This(); const Step = std.Build.Step; const Allocator = std.mem.Allocator; +const assert = std.debug.assert; dir_table: DirTable, /// 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 const stack_lfh: LinuxFileHandle = .{ .handle = stack_ptr }; return stack_lfh.clone(gpa); } + +pub fn markDirtySteps(w: *Watch, gpa: Allocator) !bool { + const fanotify = std.os.linux.fanotify; + const M = fanotify.event_metadata; + var events_buf: [256 + 4096]u8 = undefined; + var any_dirty = false; + while (true) { + var len = std.posix.read(w.fan_fd, &events_buf) catch |err| switch (err) { + error.WouldBlock => return any_dirty, + else => |e| return e, + }; + var meta: [*]align(1) M = @ptrCast(&events_buf); + while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({ + len -= meta[0].event_len; + meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len); + }) { + assert(meta[0].vers == M.VERSION); + const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1); + switch (fid.hdr.info_type) { + .DFID_NAME => { + const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); + const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes); + const file_name = std.mem.span(file_name_z); + const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; + if (w.handle_table.getPtr(lfh)) |reaction_set| { + if (reaction_set.getPtr(file_name)) |step_set| { + for (step_set.keys()) |step| { + if (step.state != .precheck_done) { + step.recursiveReset(gpa); + any_dirty = true; + } + } + } + } + }, + else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}), + } + } + } +} + +pub fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void { + for (all_steps) |step| switch (step.state) { + .dependency_failure, .failure, .skipped => step.recursiveReset(gpa), + else => continue, + }; + // Now that all dirty steps have been found, the remaining steps that + // succeeded from last run shall be marked "cached". + for (all_steps) |step| switch (step.state) { + .success => step.result_cached = true, + else => continue, + }; +}