Commit 001ff7b3b2
Changed files (3)
lib
compiler
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 <ms> Delay before rebuilding after watched file detection
+ \\ --debounce <ms> Delay before rebuilding after changed file detected
\\
\\Project-Specific Options:
\\
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;
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,
+ };
+}