| ... | @@ -1,41 +1,21 @@ | ... | @@ -1,41 +1,21 @@ |
| | 1 | const builtin = @import("builtin"); |
| 1 | const std = @import("../std.zig"); | 2 | const std = @import("../std.zig"); |
| 2 | const Watch = @This(); | 3 | const Watch = @This(); |
| 3 | const Step = std.Build.Step; | 4 | const Step = std.Build.Step; |
| 4 | const Allocator = std.mem.Allocator; | 5 | const Allocator = std.mem.Allocator; |
| 5 | const assert = std.debug.assert; | 6 | const assert = std.debug.assert; |
| | 7 | const fatal = std.zig.fatal; |
| 6 | | 8 | |
| 7 | dir_table: DirTable, | 9 | dir_table: DirTable, |
| 8 | /// Keyed differently but indexes correspond 1:1 with `dir_table`. | 10 | os: Os, |
| 9 | handle_table: HandleTable, | | |
| 10 | fan_fd: std.posix.fd_t, | | |
| 11 | generation: Generation, | 11 | generation: Generation, |
| 12 | | 12 | |
| 13 | pub const fan_mask: std.os.linux.fanotify.MarkMask = .{ | | |
| 14 | .CLOSE_WRITE = true, | | |
| 15 | .CREATE = true, | | |
| 16 | .DELETE = true, | | |
| 17 | .DELETE_SELF = true, | | |
| 18 | .EVENT_ON_CHILD = true, | | |
| 19 | .MOVED_FROM = true, | | |
| 20 | .MOVED_TO = true, | | |
| 21 | .MOVE_SELF = true, | | |
| 22 | .ONDIR = true, | | |
| 23 | }; | | |
| 24 | | | |
| 25 | pub const init: Watch = .{ | | |
| 26 | .dir_table = .{}, | | |
| 27 | .handle_table = .{}, | | |
| 28 | .fan_fd = -1, | | |
| 29 | .generation = 0, | | |
| 30 | }; | | |
| 31 | | | |
| 32 | /// Key is the directory to watch which contains one or more files we are | 13 | /// Key is the directory to watch which contains one or more files we are |
| 33 | /// interested in noticing changes to. | 14 | /// interested in noticing changes to. |
| 34 | /// | 15 | /// |
| 35 | /// Value is generation. | 16 | /// Value is generation. |
| 36 | const DirTable = std.ArrayHashMapUnmanaged(Cache.Path, void, Cache.Path.TableAdapter, false); | 17 | const DirTable = std.ArrayHashMapUnmanaged(Cache.Path, void, Cache.Path.TableAdapter, false); |
| 37 | | 18 | |
| 38 | const HandleTable = std.ArrayHashMapUnmanaged(LinuxFileHandle, ReactionSet, LinuxFileHandle.Adapter, false); | | |
| 39 | /// Special key of "." means any changes in this directory trigger the steps. | 19 | /// Special key of "." means any changes in this directory trigger the steps. |
| 40 | const ReactionSet = std.StringArrayHashMapUnmanaged(StepSet); | 20 | const ReactionSet = std.StringArrayHashMapUnmanaged(StepSet); |
| 41 | const StepSet = std.AutoArrayHashMapUnmanaged(*Step, Generation); | 21 | const StepSet = std.AutoArrayHashMapUnmanaged(*Step, Generation); |
| ... | @@ -45,6 +25,255 @@ const Generation = u8; | ... | @@ -45,6 +25,255 @@ const Generation = u8; |
| 45 | const Hash = std.hash.Wyhash; | 25 | const Hash = std.hash.Wyhash; |
| 46 | const Cache = std.Build.Cache; | 26 | const Cache = std.Build.Cache; |
| 47 | | 27 | |
| | 28 | const Os = switch (builtin.os.tag) { |
| | 29 | .linux => struct { |
| | 30 | const posix = std.posix; |
| | 31 | |
| | 32 | /// Keyed differently but indexes correspond 1:1 with `dir_table`. |
| | 33 | handle_table: HandleTable, |
| | 34 | poll_fds: [1]posix.pollfd, |
| | 35 | |
| | 36 | const HandleTable = std.ArrayHashMapUnmanaged(FileHandle, ReactionSet, FileHandle.Adapter, false); |
| | 37 | |
| | 38 | const fan_mask: std.os.linux.fanotify.MarkMask = .{ |
| | 39 | .CLOSE_WRITE = true, |
| | 40 | .CREATE = true, |
| | 41 | .DELETE = true, |
| | 42 | .DELETE_SELF = true, |
| | 43 | .EVENT_ON_CHILD = true, |
| | 44 | .MOVED_FROM = true, |
| | 45 | .MOVED_TO = true, |
| | 46 | .MOVE_SELF = true, |
| | 47 | .ONDIR = true, |
| | 48 | }; |
| | 49 | |
| | 50 | const FileHandle = struct { |
| | 51 | handle: *align(1) std.os.linux.file_handle, |
| | 52 | |
| | 53 | fn clone(lfh: FileHandle, gpa: Allocator) Allocator.Error!FileHandle { |
| | 54 | const bytes = lfh.slice(); |
| | 55 | const new_ptr = try gpa.alignedAlloc( |
| | 56 | u8, |
| | 57 | @alignOf(std.os.linux.file_handle), |
| | 58 | @sizeOf(std.os.linux.file_handle) + bytes.len, |
| | 59 | ); |
| | 60 | const new_header: *std.os.linux.file_handle = @ptrCast(new_ptr); |
| | 61 | new_header.* = lfh.handle.*; |
| | 62 | const new: FileHandle = .{ .handle = new_header }; |
| | 63 | @memcpy(new.slice(), lfh.slice()); |
| | 64 | return new; |
| | 65 | } |
| | 66 | |
| | 67 | fn destroy(lfh: FileHandle, gpa: Allocator) void { |
| | 68 | const ptr: [*]u8 = @ptrCast(lfh.handle); |
| | 69 | const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes]; |
| | 70 | return gpa.free(allocated_slice); |
| | 71 | } |
| | 72 | |
| | 73 | fn slice(lfh: FileHandle) []u8 { |
| | 74 | const ptr: [*]u8 = &lfh.handle.f_handle; |
| | 75 | return ptr[0..lfh.handle.handle_bytes]; |
| | 76 | } |
| | 77 | |
| | 78 | const Adapter = struct { |
| | 79 | pub fn hash(self: Adapter, a: FileHandle) u32 { |
| | 80 | _ = self; |
| | 81 | const unsigned_type: u32 = @bitCast(a.handle.handle_type); |
| | 82 | return @truncate(Hash.hash(unsigned_type, a.slice())); |
| | 83 | } |
| | 84 | pub fn eql(self: Adapter, a: FileHandle, b: FileHandle, b_index: usize) bool { |
| | 85 | _ = self; |
| | 86 | _ = b_index; |
| | 87 | return a.handle.handle_type == b.handle.handle_type and std.mem.eql(u8, a.slice(), b.slice()); |
| | 88 | } |
| | 89 | }; |
| | 90 | }; |
| | 91 | |
| | 92 | fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path) !FileHandle { |
| | 93 | var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined; |
| | 94 | var mount_id: i32 = undefined; |
| | 95 | var buf: [std.fs.max_path_bytes]u8 = undefined; |
| | 96 | const adjusted_path = if (path.sub_path.len == 0) "./" else std.fmt.bufPrint(&buf, "{s}/", .{ |
| | 97 | path.sub_path, |
| | 98 | }) catch return error.NameTooLong; |
| | 99 | const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer); |
| | 100 | stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle); |
| | 101 | try posix.name_to_handle_at(path.root_dir.handle.fd, adjusted_path, stack_ptr, &mount_id, std.os.linux.AT.HANDLE_FID); |
| | 102 | const stack_lfh: FileHandle = .{ .handle = stack_ptr }; |
| | 103 | return stack_lfh.clone(gpa); |
| | 104 | } |
| | 105 | |
| | 106 | fn markDirtySteps(w: *Watch, gpa: Allocator) !bool { |
| | 107 | const fan_fd = w.os.getFanFd(); |
| | 108 | const fanotify = std.os.linux.fanotify; |
| | 109 | const M = fanotify.event_metadata; |
| | 110 | var events_buf: [256 + 4096]u8 = undefined; |
| | 111 | var any_dirty = false; |
| | 112 | while (true) { |
| | 113 | var len = posix.read(fan_fd, &events_buf) catch |err| switch (err) { |
| | 114 | error.WouldBlock => return any_dirty, |
| | 115 | else => |e| return e, |
| | 116 | }; |
| | 117 | var meta: [*]align(1) M = @ptrCast(&events_buf); |
| | 118 | while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({ |
| | 119 | len -= meta[0].event_len; |
| | 120 | meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len); |
| | 121 | }) { |
| | 122 | assert(meta[0].vers == M.VERSION); |
| | 123 | if (meta[0].mask.Q_OVERFLOW) { |
| | 124 | any_dirty = true; |
| | 125 | std.log.warn("file system watch queue overflowed; falling back to fstat", .{}); |
| | 126 | markAllFilesDirty(w, gpa); |
| | 127 | return true; |
| | 128 | } |
| | 129 | const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1); |
| | 130 | switch (fid.hdr.info_type) { |
| | 131 | .DFID_NAME => { |
| | 132 | const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); |
| | 133 | const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes); |
| | 134 | const file_name = std.mem.span(file_name_z); |
| | 135 | const lfh: FileHandle = .{ .handle = file_handle }; |
| | 136 | if (w.os.handle_table.getPtr(lfh)) |reaction_set| { |
| | 137 | if (reaction_set.getPtr(".")) |glob_set| |
| | 138 | any_dirty = markStepSetDirty(gpa, glob_set, any_dirty); |
| | 139 | if (reaction_set.getPtr(file_name)) |step_set| |
| | 140 | any_dirty = markStepSetDirty(gpa, step_set, any_dirty); |
| | 141 | } |
| | 142 | }, |
| | 143 | else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}), |
| | 144 | } |
| | 145 | } |
| | 146 | } |
| | 147 | } |
| | 148 | |
| | 149 | fn getFanFd(os: *const @This()) posix.fd_t { |
| | 150 | return os.poll_fds[0].fd; |
| | 151 | } |
| | 152 | |
| | 153 | fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { |
| | 154 | const fan_fd = w.os.getFanFd(); |
| | 155 | // Add missing marks and note persisted ones. |
| | 156 | for (steps) |step| { |
| | 157 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { |
| | 158 | const reaction_set = rs: { |
| | 159 | const gop = try w.dir_table.getOrPut(gpa, path); |
| | 160 | if (!gop.found_existing) { |
| | 161 | const dir_handle = try Os.getDirHandle(gpa, path); |
| | 162 | // `dir_handle` may already be present in the table in |
| | 163 | // the case that we have multiple Cache.Path instances |
| | 164 | // that compare inequal but ultimately point to the same |
| | 165 | // directory on the file system. |
| | 166 | // In such case, we must revert adding this directory, but keep |
| | 167 | // the additions to the step set. |
| | 168 | const dh_gop = try w.os.handle_table.getOrPut(gpa, dir_handle); |
| | 169 | if (dh_gop.found_existing) { |
| | 170 | _ = w.dir_table.pop(); |
| | 171 | } else { |
| | 172 | assert(dh_gop.index == gop.index); |
| | 173 | dh_gop.value_ptr.* = .{}; |
| | 174 | posix.fanotify_mark(fan_fd, .{ |
| | 175 | .ADD = true, |
| | 176 | .ONLYDIR = true, |
| | 177 | }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| { |
| | 178 | fatal("unable to watch {}: {s}", .{ path, @errorName(err) }); |
| | 179 | }; |
| | 180 | } |
| | 181 | break :rs dh_gop.value_ptr; |
| | 182 | } |
| | 183 | break :rs &w.os.handle_table.values()[gop.index]; |
| | 184 | }; |
| | 185 | for (files.items) |basename| { |
| | 186 | const gop = try reaction_set.getOrPut(gpa, basename); |
| | 187 | if (!gop.found_existing) gop.value_ptr.* = .{}; |
| | 188 | try gop.value_ptr.put(gpa, step, w.generation); |
| | 189 | } |
| | 190 | } |
| | 191 | } |
| | 192 | |
| | 193 | { |
| | 194 | // Remove marks for files that are no longer inputs. |
| | 195 | var i: usize = 0; |
| | 196 | while (i < w.os.handle_table.entries.len) { |
| | 197 | { |
| | 198 | const reaction_set = &w.os.handle_table.values()[i]; |
| | 199 | var step_set_i: usize = 0; |
| | 200 | while (step_set_i < reaction_set.entries.len) { |
| | 201 | const step_set = &reaction_set.values()[step_set_i]; |
| | 202 | var dirent_i: usize = 0; |
| | 203 | while (dirent_i < step_set.entries.len) { |
| | 204 | const generations = step_set.values(); |
| | 205 | if (generations[dirent_i] == w.generation) { |
| | 206 | dirent_i += 1; |
| | 207 | continue; |
| | 208 | } |
| | 209 | step_set.swapRemoveAt(dirent_i); |
| | 210 | } |
| | 211 | if (step_set.entries.len > 0) { |
| | 212 | step_set_i += 1; |
| | 213 | continue; |
| | 214 | } |
| | 215 | reaction_set.swapRemoveAt(step_set_i); |
| | 216 | } |
| | 217 | if (reaction_set.entries.len > 0) { |
| | 218 | i += 1; |
| | 219 | continue; |
| | 220 | } |
| | 221 | } |
| | 222 | |
| | 223 | const path = w.dir_table.keys()[i]; |
| | 224 | |
| | 225 | posix.fanotify_mark(fan_fd, .{ |
| | 226 | .REMOVE = true, |
| | 227 | .ONLYDIR = true, |
| | 228 | }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| switch (err) { |
| | 229 | error.FileNotFound => {}, // Expected, harmless. |
| | 230 | else => |e| std.log.warn("unable to unwatch '{}': {s}", .{ path, @errorName(e) }), |
| | 231 | }; |
| | 232 | |
| | 233 | w.dir_table.swapRemoveAt(i); |
| | 234 | w.os.handle_table.swapRemoveAt(i); |
| | 235 | } |
| | 236 | w.generation +%= 1; |
| | 237 | } |
| | 238 | } |
| | 239 | }, |
| | 240 | else => void, |
| | 241 | }; |
| | 242 | |
| | 243 | pub fn init() !Watch { |
| | 244 | switch (builtin.os.tag) { |
| | 245 | .linux => { |
| | 246 | const fan_fd = try std.posix.fanotify_init(.{ |
| | 247 | .CLASS = .NOTIF, |
| | 248 | .CLOEXEC = true, |
| | 249 | .NONBLOCK = true, |
| | 250 | .REPORT_NAME = true, |
| | 251 | .REPORT_DIR_FID = true, |
| | 252 | .REPORT_FID = true, |
| | 253 | .REPORT_TARGET_FID = true, |
| | 254 | }, 0); |
| | 255 | return .{ |
| | 256 | .dir_table = .{}, |
| | 257 | .os = switch (builtin.os.tag) { |
| | 258 | .linux => .{ |
| | 259 | .handle_table = .{}, |
| | 260 | .poll_fds = .{ |
| | 261 | .{ |
| | 262 | .fd = fan_fd, |
| | 263 | .events = std.posix.POLL.IN, |
| | 264 | .revents = undefined, |
| | 265 | }, |
| | 266 | }, |
| | 267 | }, |
| | 268 | else => {}, |
| | 269 | }, |
| | 270 | .generation = 0, |
| | 271 | }; |
| | 272 | }, |
| | 273 | else => @panic("unimplemented"), |
| | 274 | } |
| | 275 | } |
| | 276 | |
| 48 | pub const Match = struct { | 277 | pub const Match = struct { |
| 49 | /// Relative to the watched directory, the file path that triggers this | 278 | /// Relative to the watched directory, the file path that triggers this |
| 50 | /// match. | 279 | /// match. |
| ... | @@ -68,119 +297,8 @@ pub const Match = struct { | ... | @@ -68,119 +297,8 @@ pub const Match = struct { |
| 68 | }; | 297 | }; |
| 69 | }; | 298 | }; |
| 70 | | 299 | |
| 71 | pub const LinuxFileHandle = struct { | | |
| 72 | handle: *align(1) std.os.linux.file_handle, | | |
| 73 | | | |
| 74 | pub fn clone(lfh: LinuxFileHandle, gpa: Allocator) Allocator.Error!LinuxFileHandle { | | |
| 75 | const bytes = lfh.slice(); | | |
| 76 | const new_ptr = try gpa.alignedAlloc( | | |
| 77 | u8, | | |
| 78 | @alignOf(std.os.linux.file_handle), | | |
| 79 | @sizeOf(std.os.linux.file_handle) + bytes.len, | | |
| 80 | ); | | |
| 81 | const new_header: *std.os.linux.file_handle = @ptrCast(new_ptr); | | |
| 82 | new_header.* = lfh.handle.*; | | |
| 83 | const new: LinuxFileHandle = .{ .handle = new_header }; | | |
| 84 | @memcpy(new.slice(), lfh.slice()); | | |
| 85 | return new; | | |
| 86 | } | | |
| 87 | | | |
| 88 | pub fn destroy(lfh: LinuxFileHandle, gpa: Allocator) void { | | |
| 89 | const ptr: [*]u8 = @ptrCast(lfh.handle); | | |
| 90 | const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes]; | | |
| 91 | return gpa.free(allocated_slice); | | |
| 92 | } | | |
| 93 | | | |
| 94 | pub fn slice(lfh: LinuxFileHandle) []u8 { | | |
| 95 | const ptr: [*]u8 = &lfh.handle.f_handle; | | |
| 96 | return ptr[0..lfh.handle.handle_bytes]; | | |
| 97 | } | | |
| 98 | | | |
| 99 | pub const Adapter = struct { | | |
| 100 | pub fn hash(self: Adapter, a: LinuxFileHandle) u32 { | | |
| 101 | _ = self; | | |
| 102 | const unsigned_type: u32 = @bitCast(a.handle.handle_type); | | |
| 103 | return @truncate(Hash.hash(unsigned_type, a.slice())); | | |
| 104 | } | | |
| 105 | pub fn eql(self: Adapter, a: LinuxFileHandle, b: LinuxFileHandle, b_index: usize) bool { | | |
| 106 | _ = self; | | |
| 107 | _ = b_index; | | |
| 108 | return a.handle.handle_type == b.handle.handle_type and std.mem.eql(u8, a.slice(), b.slice()); | | |
| 109 | } | | |
| 110 | }; | | |
| 111 | }; | | |
| 112 | | | |
| 113 | pub fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path) !LinuxFileHandle { | | |
| 114 | var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined; | | |
| 115 | var mount_id: i32 = undefined; | | |
| 116 | var buf: [std.fs.max_path_bytes]u8 = undefined; | | |
| 117 | const adjusted_path = if (path.sub_path.len == 0) "./" else std.fmt.bufPrint(&buf, "{s}/", .{ | | |
| 118 | path.sub_path, | | |
| 119 | }) catch return error.NameTooLong; | | |
| 120 | const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer); | | |
| 121 | stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle); | | |
| 122 | try std.posix.name_to_handle_at(path.root_dir.handle.fd, adjusted_path, stack_ptr, &mount_id, std.os.linux.AT.HANDLE_FID); | | |
| 123 | const stack_lfh: LinuxFileHandle = .{ .handle = stack_ptr }; | | |
| 124 | return stack_lfh.clone(gpa); | | |
| 125 | } | | |
| 126 | | | |
| 127 | pub fn markDirtySteps(w: *Watch, gpa: Allocator) !bool { | | |
| 128 | const fanotify = std.os.linux.fanotify; | | |
| 129 | const M = fanotify.event_metadata; | | |
| 130 | var events_buf: [256 + 4096]u8 = undefined; | | |
| 131 | var any_dirty = false; | | |
| 132 | while (true) { | | |
| 133 | var len = std.posix.read(w.fan_fd, &events_buf) catch |err| switch (err) { | | |
| 134 | error.WouldBlock => return any_dirty, | | |
| 135 | else => |e| return e, | | |
| 136 | }; | | |
| 137 | var meta: [*]align(1) M = @ptrCast(&events_buf); | | |
| 138 | while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({ | | |
| 139 | len -= meta[0].event_len; | | |
| 140 | meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len); | | |
| 141 | }) { | | |
| 142 | assert(meta[0].vers == M.VERSION); | | |
| 143 | if (meta[0].mask.Q_OVERFLOW) { | | |
| 144 | any_dirty = true; | | |
| 145 | std.log.warn("file system watch queue overflowed; falling back to fstat", .{}); | | |
| 146 | markAllFilesDirty(w, gpa); | | |
| 147 | return true; | | |
| 148 | } | | |
| 149 | const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1); | | |
| 150 | switch (fid.hdr.info_type) { | | |
| 151 | .DFID_NAME => { | | |
| 152 | const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); | | |
| 153 | const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes); | | |
| 154 | const file_name = std.mem.span(file_name_z); | | |
| 155 | const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle }; | | |
| 156 | if (w.handle_table.getPtr(lfh)) |reaction_set| { | | |
| 157 | if (reaction_set.getPtr(".")) |glob_set| | | |
| 158 | any_dirty = markStepSetDirty(gpa, glob_set, any_dirty); | | |
| 159 | if (reaction_set.getPtr(file_name)) |step_set| | | |
| 160 | any_dirty = markStepSetDirty(gpa, step_set, any_dirty); | | |
| 161 | } | | |
| 162 | }, | | |
| 163 | else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}), | | |
| 164 | } | | |
| 165 | } | | |
| 166 | } | | |
| 167 | } | | |
| 168 | | | |
| 169 | pub fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void { | | |
| 170 | for (all_steps) |step| switch (step.state) { | | |
| 171 | .dependency_failure, .failure, .skipped => step.recursiveReset(gpa), | | |
| 172 | else => continue, | | |
| 173 | }; | | |
| 174 | // Now that all dirty steps have been found, the remaining steps that | | |
| 175 | // succeeded from last run shall be marked "cached". | | |
| 176 | for (all_steps) |step| switch (step.state) { | | |
| 177 | .success => step.result_cached = true, | | |
| 178 | else => continue, | | |
| 179 | }; | | |
| 180 | } | | |
| 181 | | | |
| 182 | fn markAllFilesDirty(w: *Watch, gpa: Allocator) void { | 300 | fn markAllFilesDirty(w: *Watch, gpa: Allocator) void { |
| 183 | for (w.handle_table.values()) |reaction_set| { | 301 | for (w.os.handle_table.values()) |reaction_set| { |
| 184 | for (reaction_set.values()) |step_set| { | 302 | for (reaction_set.values()) |step_set| { |
| 185 | for (step_set.keys()) |step| { | 303 | for (step_set.keys()) |step| { |
| 186 | step.recursiveReset(gpa); | 304 | step.recursiveReset(gpa); |
| ... | @@ -199,3 +317,47 @@ fn markStepSetDirty(gpa: Allocator, step_set: *StepSet, any_dirty: bool) bool { | ... | @@ -199,3 +317,47 @@ fn markStepSetDirty(gpa: Allocator, step_set: *StepSet, any_dirty: bool) bool { |
| 199 | } | 317 | } |
| 200 | return any_dirty or this_any_dirty; | 318 | return any_dirty or this_any_dirty; |
| 201 | } | 319 | } |
| | 320 | |
| | 321 | pub fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { |
| | 322 | switch (builtin.os.tag) { |
| | 323 | .linux => return Os.update(w, gpa, steps), |
| | 324 | else => @compileError("unimplemented"), |
| | 325 | } |
| | 326 | } |
| | 327 | |
| | 328 | pub const Timeout = union(enum) { |
| | 329 | none, |
| | 330 | ms: u16, |
| | 331 | |
| | 332 | pub fn to_i32_ms(t: Timeout) i32 { |
| | 333 | return switch (t) { |
| | 334 | .none => -1, |
| | 335 | .ms => |ms| ms, |
| | 336 | }; |
| | 337 | } |
| | 338 | }; |
| | 339 | |
| | 340 | pub const WaitResult = enum { |
| | 341 | timeout, |
| | 342 | /// File system watching triggered on files that were marked as inputs to at least one Step. |
| | 343 | /// Relevant steps have been marked dirty. |
| | 344 | dirty, |
| | 345 | /// File system watching triggered but none of the events were relevant to |
| | 346 | /// what we are listening to. There is nothing to do. |
| | 347 | clean, |
| | 348 | }; |
| | 349 | |
| | 350 | pub fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult { |
| | 351 | switch (builtin.os.tag) { |
| | 352 | .linux => { |
| | 353 | const events_len = try std.posix.poll(&w.os.poll_fds, timeout.to_i32_ms()); |
| | 354 | return if (events_len == 0) |
| | 355 | .timeout |
| | 356 | else if (try Os.markDirtySteps(w, gpa)) |
| | 357 | .dirty |
| | 358 | else |
| | 359 | .clean; |
| | 360 | }, |
| | 361 | else => @compileError("unimplemented"), |
| | 362 | } |
| | 363 | } |