authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-13 00:13:37-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-13 16:34:30-07:00
logbc1f280a77ce118300d3f486932c5768911b0e6f
tree80e4b93f3f7159780bba5bbe9fd13b0544ebc46c
parent99bfd07854b4e14e01e32e83302707cacde15cb5

Maker: fix watching on macos and bsds


2 files changed, 35 insertions(+), 16 deletions(-)

lib/compiler/Maker/Watch.zig+7-8
......@@ -728,13 +728,13 @@ const Os = switch (builtin.os.tag) {
728728 fn deinit(w: *Watch) void {
729729 const gpa = w.maker.gpa;
730730
731 for (w.os.handles.items(.rs), w.os.handles.items(.dir_fd)) |rs, dir_fd| {
731 for (w.os.handles.items(.rs), w.os.handles.items(.dir_fd)) |*rs, dir_fd| {
732732 rs.deinit(gpa);
733 Os.Threaded.closeFd(dir_fd);
733 Io.Threaded.closeFd(dir_fd);
734734 }
735735 w.os.handles.deinit(gpa);
736736
737 Os.Threaded.closeFd(w.os.kq_fd);
737 Io.Threaded.closeFd(w.os.kq_fd);
738738
739739 w.dir_table.deinit(gpa);
740740 w.* = undefined;
......@@ -875,12 +875,12 @@ const Os = switch (builtin.os.tag) {
875875 var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer));
876876 if (n == 0) return .timeout;
877877 const reaction_sets = w.os.handles.items(.rs);
878 var any_dirty = markDirtySteps(maker, reaction_sets, event_buffer[0..n], false);
878 var any_dirty = try markDirtySteps(maker, reaction_sets, event_buffer[0..n], false);
879879 timespec_buffer = .{ .sec = 0, .nsec = 0 };
880880 while (n == event_buffer.len) {
881881 n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, &timespec_buffer);
882882 if (n == 0) break;
883 any_dirty = markDirtySteps(maker, reaction_sets, event_buffer[0..n], any_dirty);
883 any_dirty = try markDirtySteps(maker, reaction_sets, event_buffer[0..n], any_dirty);
884884 }
885885 return if (any_dirty) .dirty else .clean;
886886 }
......@@ -890,7 +890,7 @@ const Os = switch (builtin.os.tag) {
890890 reaction_sets: []ReactionSet,
891891 events: []const std.c.Kevent,
892892 start_any_dirty: bool,
893 ) bool {
893 ) !bool {
894894 var any_dirty = start_any_dirty;
895895 for (events) |event| {
896896 const index: usize = @intCast(event.udata);
......@@ -924,9 +924,8 @@ const Os = switch (builtin.os.tag) {
924924 }
925925 fn deinit(w: *Watch) void {
926926 const gpa = w.maker.gpa;
927 const io = w.maker.io;
927 const io = w.maker.graph.io;
928928 w.os.fse.deinit(gpa, io);
929 w.dir_table.deinit(gpa);
930929 w.* = undefined;
931930 }
932931 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
lib/compiler/Maker/Watch/FsEvents.zig+28-8
......@@ -46,6 +46,8 @@ since_event: FSEventStreamEventId,
4646
4747cwd_path: []const u8,
4848
49must_reconfigure: bool,
50
4951/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols
5052/// is not present, `init` will close the framework and return an error.
5153const ResolvedSymbols = struct {
......@@ -104,13 +106,15 @@ pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreService
104106 // to notice any changes which happened during said work.
105107 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),
106108 .cwd_path = cwd_path,
109 .must_reconfigure = false,
107110 };
108111}
109112
110113pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {
114 _ = io;
111115 fse.waiting_semaphore.as_object().release();
112116 fse.dispatch_queue.as_object().release();
113 fse.core_services.close(io);
117 fse.core_services.close();
114118
115119 gpa.free(fse.watch_roots);
116120 fse.watch_paths.deinit(gpa);
......@@ -211,7 +215,7 @@ pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configur
211215 }
212216}
213217
214pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!Watch.WaitResult {
218pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory, StartFailed, MustReconfigure }!Watch.WaitResult {
215219 if (fse.watch_roots.len == 0) @panic("nothing to watch");
216220 const gpa = maker.gpa;
217221
......@@ -285,6 +289,7 @@ pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory,
285289 const ns = timeout_ns orelse break :timeout .FOREVER;
286290 break :timeout .time(.NOW, @intCast(ns));
287291 });
292 if (fse.must_reconfigure) return error.MustReconfigure;
288293 return switch (result) {
289294 0 => .dirty,
290295 else => .timeout,
......@@ -355,13 +360,23 @@ fn eventCallback(
355360 false => {
356361 if (fse.watch_paths.get(event_path)) |steps| {
357362 assert(steps.len > 0);
358 if (invalidateSteps(maker, steps)) any_dirty = true;
363 if (invalidateSteps(maker, steps) catch |err| switch (err) {
364 error.MustReconfigure => {
365 fse.must_reconfigure = true;
366 break;
367 },
368 }) any_dirty = true;
359369 }
360370 if (std.fs.path.dirname(event_path)) |event_dirname| {
361371 // Modifying '/foo/bar' triggers the watch on '/foo'.
362372 if (fse.watch_paths.get(event_dirname)) |steps| {
363373 assert(steps.len > 0);
364 if (invalidateSteps(maker, steps)) any_dirty = true;
374 if (invalidateSteps(maker, steps) catch |err| switch (err) {
375 error.MustReconfigure => {
376 fse.must_reconfigure = true;
377 break;
378 },
379 }) any_dirty = true;
365380 }
366381 }
367382 },
......@@ -374,13 +389,18 @@ fn eventCallback(
374389 const changed_path = std.fs.path.dirname(event_path) orelse event_path;
375390 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {
376391 if (dirStartsWith(watching_path, changed_path)) {
377 if (invalidateSteps(maker, steps)) any_dirty = true;
392 if (invalidateSteps(maker, steps) catch |err| switch (err) {
393 error.MustReconfigure => {
394 fse.must_reconfigure = true;
395 break;
396 },
397 }) any_dirty = true;
378398 }
379399 }
380400 },
381401 }
382402 }
383 if (any_dirty) {
403 if (any_dirty or fse.must_reconfigure) {
384404 fse.since_event = rs.FSEventStreamGetLatestEventId(stream);
385405 _ = fse.waiting_semaphore.signal();
386406 }
......@@ -392,11 +412,11 @@ fn dirStartsWith(path: []const u8, prefix: []const u8) bool {
392412 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`
393413}
394414
395fn invalidateSteps(maker: *Maker, steps: []const std.Build.Configuration.Step.Index) bool {
415fn invalidateSteps(maker: *Maker, steps: []const std.Build.Configuration.Step.Index) !bool {
396416 var any_dirty = false;
397417 for (steps) |step_index| {
398418 const step = maker.stepByIndex(step_index);
399 if (maker.invalidateResult(step)) any_dirty = true;
419 if (try maker.invalidateResult(step)) any_dirty = true;
400420 }
401421 return any_dirty;
402422}