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) {...@@ -728,13 +728,13 @@ const Os = switch (builtin.os.tag) {
728 fn deinit(w: *Watch) void {728 fn deinit(w: *Watch) void {
729 const gpa = w.maker.gpa;729 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| {
732 rs.deinit(gpa);732 rs.deinit(gpa);
733 Os.Threaded.closeFd(dir_fd);733 Io.Threaded.closeFd(dir_fd);
734 }734 }
735 w.os.handles.deinit(gpa);735 w.os.handles.deinit(gpa);
736736
737 Os.Threaded.closeFd(w.os.kq_fd);737 Io.Threaded.closeFd(w.os.kq_fd);
738738
739 w.dir_table.deinit(gpa);739 w.dir_table.deinit(gpa);
740 w.* = undefined;740 w.* = undefined;
...@@ -875,12 +875,12 @@ const Os = switch (builtin.os.tag) {...@@ -875,12 +875,12 @@ const Os = switch (builtin.os.tag) {
875 var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer));875 var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer));
876 if (n == 0) return .timeout;876 if (n == 0) return .timeout;
877 const reaction_sets = w.os.handles.items(.rs);877 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);
879 timespec_buffer = .{ .sec = 0, .nsec = 0 };879 timespec_buffer = .{ .sec = 0, .nsec = 0 };
880 while (n == event_buffer.len) {880 while (n == event_buffer.len) {
881 n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, &timespec_buffer);881 n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, &timespec_buffer);
882 if (n == 0) break;882 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);
884 }884 }
885 return if (any_dirty) .dirty else .clean;885 return if (any_dirty) .dirty else .clean;
886 }886 }
...@@ -890,7 +890,7 @@ const Os = switch (builtin.os.tag) {...@@ -890,7 +890,7 @@ const Os = switch (builtin.os.tag) {
890 reaction_sets: []ReactionSet,890 reaction_sets: []ReactionSet,
891 events: []const std.c.Kevent,891 events: []const std.c.Kevent,
892 start_any_dirty: bool,892 start_any_dirty: bool,
893 ) bool {893 ) !bool {
894 var any_dirty = start_any_dirty;894 var any_dirty = start_any_dirty;
895 for (events) |event| {895 for (events) |event| {
896 const index: usize = @intCast(event.udata);896 const index: usize = @intCast(event.udata);
...@@ -924,9 +924,8 @@ const Os = switch (builtin.os.tag) {...@@ -924,9 +924,8 @@ const Os = switch (builtin.os.tag) {
924 }924 }
925 fn deinit(w: *Watch) void {925 fn deinit(w: *Watch) void {
926 const gpa = w.maker.gpa;926 const gpa = w.maker.gpa;
927 const io = w.maker.io;927 const io = w.maker.graph.io;
928 w.os.fse.deinit(gpa, io);928 w.os.fse.deinit(gpa, io);
929 w.dir_table.deinit(gpa);
930 w.* = undefined;929 w.* = undefined;
931 }930 }
932 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {931 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,...@@ -46,6 +46,8 @@ since_event: FSEventStreamEventId,
4646
47cwd_path: []const u8,47cwd_path: []const u8,
4848
49must_reconfigure: bool,
50
49/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols51/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols
50/// is not present, `init` will close the framework and return an error.52/// is not present, `init` will close the framework and return an error.
51const ResolvedSymbols = struct {53const ResolvedSymbols = struct {
...@@ -104,13 +106,15 @@ pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreService...@@ -104,13 +106,15 @@ pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreService
104 // to notice any changes which happened during said work.106 // to notice any changes which happened during said work.
105 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),107 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),
106 .cwd_path = cwd_path,108 .cwd_path = cwd_path,
109 .must_reconfigure = false,
107 };110 };
108}111}
109112
110pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {113pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {
114 _ = io;
111 fse.waiting_semaphore.as_object().release();115 fse.waiting_semaphore.as_object().release();
112 fse.dispatch_queue.as_object().release();116 fse.dispatch_queue.as_object().release();
113 fse.core_services.close(io);117 fse.core_services.close();
114118
115 gpa.free(fse.watch_roots);119 gpa.free(fse.watch_roots);
116 fse.watch_paths.deinit(gpa);120 fse.watch_paths.deinit(gpa);
...@@ -211,7 +215,7 @@ pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configur...@@ -211,7 +215,7 @@ pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configur
211 }215 }
212}216}
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 {
215 if (fse.watch_roots.len == 0) @panic("nothing to watch");219 if (fse.watch_roots.len == 0) @panic("nothing to watch");
216 const gpa = maker.gpa;220 const gpa = maker.gpa;
217221
...@@ -285,6 +289,7 @@ pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory,...@@ -285,6 +289,7 @@ pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory,
285 const ns = timeout_ns orelse break :timeout .FOREVER;289 const ns = timeout_ns orelse break :timeout .FOREVER;
286 break :timeout .time(.NOW, @intCast(ns));290 break :timeout .time(.NOW, @intCast(ns));
287 });291 });
292 if (fse.must_reconfigure) return error.MustReconfigure;
288 return switch (result) {293 return switch (result) {
289 0 => .dirty,294 0 => .dirty,
290 else => .timeout,295 else => .timeout,
...@@ -355,13 +360,23 @@ fn eventCallback(...@@ -355,13 +360,23 @@ fn eventCallback(
355 false => {360 false => {
356 if (fse.watch_paths.get(event_path)) |steps| {361 if (fse.watch_paths.get(event_path)) |steps| {
357 assert(steps.len > 0);362 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;
359 }369 }
360 if (std.fs.path.dirname(event_path)) |event_dirname| {370 if (std.fs.path.dirname(event_path)) |event_dirname| {
361 // Modifying '/foo/bar' triggers the watch on '/foo'.371 // Modifying '/foo/bar' triggers the watch on '/foo'.
362 if (fse.watch_paths.get(event_dirname)) |steps| {372 if (fse.watch_paths.get(event_dirname)) |steps| {
363 assert(steps.len > 0);373 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;
365 }380 }
366 }381 }
367 },382 },
...@@ -374,13 +389,18 @@ fn eventCallback(...@@ -374,13 +389,18 @@ fn eventCallback(
374 const changed_path = std.fs.path.dirname(event_path) orelse event_path;389 const changed_path = std.fs.path.dirname(event_path) orelse event_path;
375 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {390 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {
376 if (dirStartsWith(watching_path, changed_path)) {391 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;
378 }398 }
379 }399 }
380 },400 },
381 }401 }
382 }402 }
383 if (any_dirty) {403 if (any_dirty or fse.must_reconfigure) {
384 fse.since_event = rs.FSEventStreamGetLatestEventId(stream);404 fse.since_event = rs.FSEventStreamGetLatestEventId(stream);
385 _ = fse.waiting_semaphore.signal();405 _ = fse.waiting_semaphore.signal();
386 }406 }
...@@ -392,11 +412,11 @@ fn dirStartsWith(path: []const u8, prefix: []const u8) bool {...@@ -392,11 +412,11 @@ fn dirStartsWith(path: []const u8, prefix: []const u8) bool {
392 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`412 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`
393}413}
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 {
396 var any_dirty = false;416 var any_dirty = false;
397 for (steps) |step_index| {417 for (steps) |step_index| {
398 const step = maker.stepByIndex(step_index);418 const step = maker.stepByIndex(step_index);
399 if (maker.invalidateResult(step)) any_dirty = true;419 if (try maker.invalidateResult(step)) any_dirty = true;
400 }420 }
401 return any_dirty;421 return any_dirty;
402}422}