authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-23 14:10:40-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 18:54:36-07:00
logb069a2eb21d4bdcbc87c566be013fa30f26d3a1e
tree1858085897c9c7363b23170898ed58584b0e0bd7
parent05fbeb4ea7acef3e9cfcce650343b1ef73e12e29

Maker: update macos file watching code to new api


3 files changed, 52 insertions(+), 40 deletions(-)

lib/compiler/Maker.zig+1-1
...@@ -623,7 +623,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -623,7 +623,7 @@ pub fn main(init: process.Init.Minimal) !void {
623 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.623 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
624 if (!Watch.have_impl) unreachable;624 if (!Watch.have_impl) unreachable;
625625
626 try w.update(gpa, maker.step_stack.keys());626 try w.update(maker.step_stack.keys());
627627
628 // Wait until a file system notification arrives. Read all such events628 // Wait until a file system notification arrives. Read all such events
629 // until the buffer is empty. Then wait for a debounce interval, resetting629 // until the buffer is empty. Then wait for a debounce interval, resetting
lib/compiler/Maker/Watch.zig+19-16
...@@ -177,8 +177,9 @@ const Os = switch (builtin.os.tag) {...@@ -177,8 +177,9 @@ const Os = switch (builtin.os.tag) {
177 }177 }
178 }178 }
179179
180 fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {180 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
181 const maker = w.maker;181 const maker = w.maker;
182 const gpa = maker.gpa;
182183
183 // Add missing marks and note persisted ones.184 // Add missing marks and note persisted ones.
184 for (steps) |step_index| {185 for (steps) |step_index| {
...@@ -465,8 +466,7 @@ const Os = switch (builtin.os.tag) {...@@ -465,8 +466,7 @@ const Os = switch (builtin.os.tag) {
465 };466 };
466 };467 };
467468
468 fn init(cwd_path: []const u8) !Watch {469 fn init(maker: *Maker) !Watch {
469 _ = cwd_path;
470 return .{470 return .{
471 .dir_table = .{},471 .dir_table = .{},
472 .dir_count = 0,472 .dir_count = 0,
...@@ -478,6 +478,7 @@ const Os = switch (builtin.os.tag) {...@@ -478,6 +478,7 @@ const Os = switch (builtin.os.tag) {
478 else => {},478 else => {},
479 },479 },
480 .generation = 0,480 .generation = 0,
481 .maker = maker,
481 };482 };
482 }483 }
483484
...@@ -546,7 +547,8 @@ const Os = switch (builtin.os.tag) {...@@ -546,7 +547,8 @@ const Os = switch (builtin.os.tag) {
546 return any_dirty;547 return any_dirty;
547 }548 }
548549
549 fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {550 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
551 const gpa = w.maker.gpa;
550 // Add missing marks and note persisted ones.552 // Add missing marks and note persisted ones.
551 for (steps) |step| {553 for (steps) |step| {
552 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {554 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
...@@ -677,8 +679,7 @@ const Os = switch (builtin.os.tag) {...@@ -677,8 +679,7 @@ const Os = switch (builtin.os.tag) {
677 const EV = std.c.EV;679 const EV = std.c.EV;
678 const NOTE = std.c.NOTE;680 const NOTE = std.c.NOTE;
679681
680 fn init(cwd_path: []const u8) !Watch {682 fn init(maker: *Maker) !Watch {
681 _ = cwd_path;
682 return .{683 return .{
683 .dir_table = .{},684 .dir_table = .{},
684 .dir_count = 0,685 .dir_count = 0,
...@@ -687,10 +688,12 @@ const Os = switch (builtin.os.tag) {...@@ -687,10 +688,12 @@ const Os = switch (builtin.os.tag) {
687 .handles = .empty,688 .handles = .empty,
688 },689 },
689 .generation = 0,690 .generation = 0,
691 .maker = maker,
690 };692 };
691 }693 }
692694
693 fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {695 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
696 const gpa = w.maker.gpa;
694 const handles = &w.os.handles;697 const handles = &w.os.handles;
695 for (steps) |step| {698 for (steps) |step| {
696 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {699 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
...@@ -860,21 +863,21 @@ const Os = switch (builtin.os.tag) {...@@ -860,21 +863,21 @@ const Os = switch (builtin.os.tag) {
860 .macos => struct {863 .macos => struct {
861 fse: FsEvents,864 fse: FsEvents,
862865
863 fn init(cwd_path: []const u8) !Watch {866 fn init(maker: *Maker) !Watch {
864 return .{867 return .{
865 .os = .{ .fse = try .init(cwd_path) },868 .os = .{ .fse = try .init(maker.graph.cache.cwd) },
866 .dir_count = 0,869 .dir_count = 0,
867 .dir_table = undefined,870 .dir_table = undefined,
868 .generation = undefined,871 .generation = undefined,
872 .maker = maker,
869 };873 };
870 }874 }
871 fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {875 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
872 try w.os.fse.setPaths(gpa, steps);876 try w.os.fse.setPaths(w.maker, steps);
873 w.dir_count = w.os.fse.watch_roots.len;877 w.dir_count = w.os.fse.watch_roots.len;
874 }878 }
875 fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult {879 fn wait(w: *Watch, timeout: Timeout) !WaitResult {
876 _ = io;880 return w.os.fse.wait(w.maker, switch (timeout) {
877 return w.os.fse.wait(gpa, switch (timeout) {
878 .none => null,881 .none => null,
879 .ms => |ms| @as(u64, ms) * std.time.ns_per_ms,882 .ms => |ms| @as(u64, ms) * std.time.ns_per_ms,
880 });883 });
...@@ -938,8 +941,8 @@ fn markStepSetDirty(maker: *Maker, step_set: *StepSet, any_dirty: bool) bool {...@@ -938,8 +941,8 @@ fn markStepSetDirty(maker: *Maker, step_set: *StepSet, any_dirty: bool) bool {
938 return any_dirty or this_any_dirty;941 return any_dirty or this_any_dirty;
939}942}
940943
941pub fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {944pub fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
942 return Os.update(w, gpa, steps);945 return Os.update(w, steps);
943}946}
944947
945pub const Timeout = union(enum) {948pub const Timeout = union(enum) {
lib/compiler/Maker/Watch/FsEvents.zig+32-23
...@@ -17,6 +17,7 @@...@@ -17,6 +17,7 @@
17//! the logic that would avoid them is currently disabled, because the build system kind17//! the logic that would avoid them is currently disabled, because the build system kind
18//! of relies on them at the time of writing to avoid redundant work -- see the comment at18//! of relies on them at the time of writing to avoid redundant work -- see the comment at
19//! the top of `wait` for details.19//! the top of `wait` for details.
20const FsEvents = @This();
2021
21const enable_debug_logs = false;22const enable_debug_logs = false;
2223
...@@ -30,7 +31,7 @@ paths_arena: std.heap.ArenaAllocator.State,...@@ -30,7 +31,7 @@ paths_arena: std.heap.ArenaAllocator.State,
30watch_roots: [][:0]const u8,31watch_roots: [][:0]const u8,
31/// All of the paths being watched. Value is the set of steps which depend on the file/directory.32/// All of the paths being watched. Value is the set of steps which depend on the file/directory.
32/// Keys and values are in `paths_arena`, but this map is allocated into the GPA.33/// Keys and values are in `paths_arena`, but this map is allocated into the GPA.
33watch_paths: std.StringArrayHashMapUnmanaged([]const *std.Build.Step),34watch_paths: std.array_hash_map.String([]const std.Build.Configuration.Step.Index),
3435
35/// The semaphore we use to block the thread calling `wait` until the callback determines a relevant36/// The semaphore we use to block the thread calling `wait` until the callback determines a relevant
36/// event has occurred. This is retained across `wait` calls for simplicity and efficiency.37/// event has occurred. This is retained across `wait` calls for simplicity and efficiency.
...@@ -118,19 +119,22 @@ pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {...@@ -118,19 +119,22 @@ pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {
118 }119 }
119}120}
120121
121pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step) !void {122pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configuration.Step.Index) !void {
123 const gpa = maker.gpa;
124
122 var paths_arena_instance = fse.paths_arena.promote(gpa);125 var paths_arena_instance = fse.paths_arena.promote(gpa);
123 defer fse.paths_arena = paths_arena_instance.state;126 defer fse.paths_arena = paths_arena_instance.state;
124 const paths_arena = paths_arena_instance.allocator();127 const paths_arena = paths_arena_instance.allocator();
125128
126 var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty;129 var need_dirs: std.array_hash_map.String(void) = .empty;
127 defer need_dirs.deinit(gpa);130 defer need_dirs.deinit(gpa);
128131
129 fse.watch_paths.clearRetainingCapacity();132 fse.watch_paths.clearRetainingCapacity();
130133
131 // We take `step` by pointer for a slight memory optimization in a moment.134 // We take `step_index` by pointer for a slight memory optimization in a moment.
132 for (steps) |*step| {135 for (steps) |*step_index| {
133 for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| {136 const step = maker.stepByIndex(step_index.*);
137 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
134 const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{138 const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{
135 fse.cwd_path, path.root_dir.path orelse ".", path.sub_path,139 fse.cwd_path, path.root_dir.path orelse ".", path.sub_path,
136 });140 });
...@@ -143,14 +147,14 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)...@@ -143,14 +147,14 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
143 const gop = try fse.watch_paths.getOrPut(gpa, watch_path);147 const gop = try fse.watch_paths.getOrPut(gpa, watch_path);
144 if (gop.found_existing) {148 if (gop.found_existing) {
145 const old_steps = gop.value_ptr.*;149 const old_steps = gop.value_ptr.*;
146 const new_steps = try paths_arena.alloc(*std.Build.Step, old_steps.len + 1);150 const new_steps = try paths_arena.alloc(std.Build.Configuration.Step.Index, old_steps.len + 1);
147 @memcpy(new_steps[0..old_steps.len], old_steps);151 @memcpy(new_steps[0..old_steps.len], old_steps);
148 new_steps[old_steps.len] = step.*;152 new_steps[old_steps.len] = step_index.*;
149 gop.value_ptr.* = new_steps;153 gop.value_ptr.* = new_steps;
150 } else {154 } else {
151 // This is why we captured `step` by pointer! We can avoid allocating a slice of one155 // This is why we captured `step` by pointer! We can avoid allocating a slice of one
152 // step in the arena in the common case where a file is referenced by only one step.156 // step in the arena in the common case where a file is referenced by only one step.
153 gop.value_ptr.* = step[0..1];157 gop.value_ptr.* = step_index[0..1];
154 }158 }
155 }159 }
156 }160 }
...@@ -206,8 +210,9 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)...@@ -206,8 +210,9 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
206 }210 }
207}211}
208212
209pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!std.Build.Watch.WaitResult {213pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!Watch.WaitResult {
210 if (fse.watch_roots.len == 0) @panic("nothing to watch");214 if (fse.watch_roots.len == 0) @panic("nothing to watch");
215 const gpa = maker.gpa;
211216
212 const rs = fse.resolved_symbols;217 const rs = fse.resolved_symbols;
213218
...@@ -253,7 +258,7 @@ pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory...@@ -253,7 +258,7 @@ pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory
253258
254 const callback_ctx: EventCallbackCtx = .{259 const callback_ctx: EventCallbackCtx = .{
255 .fse = fse,260 .fse = fse,
256 .gpa = gpa,261 .maker = maker,
257 };262 };
258 const event_stream = rs.FSEventStreamCreate(263 const event_stream = rs.FSEventStreamCreate(
259 null,264 null,
...@@ -321,7 +326,7 @@ const cf_alloc_callbacks = struct {...@@ -321,7 +326,7 @@ const cf_alloc_callbacks = struct {
321326
322const EventCallbackCtx = struct {327const EventCallbackCtx = struct {
323 fse: *FsEvents,328 fse: *FsEvents,
324 gpa: Allocator,329 maker: *Maker,
325};330};
326331
327fn eventCallback(332fn eventCallback(
...@@ -333,8 +338,8 @@ fn eventCallback(...@@ -333,8 +338,8 @@ fn eventCallback(
333 events_ids_ptr: [*]const FSEventStreamEventId,338 events_ids_ptr: [*]const FSEventStreamEventId,
334) callconv(.c) void {339) callconv(.c) void {
335 const ctx: *const EventCallbackCtx = @ptrCast(@alignCast(client_callback_info));340 const ctx: *const EventCallbackCtx = @ptrCast(@alignCast(client_callback_info));
341 const maker = ctx.maker;
336 const fse = ctx.fse;342 const fse = ctx.fse;
337 const gpa = ctx.gpa;
338 const rs = fse.resolved_symbols;343 const rs = fse.resolved_symbols;
339 const events_paths_ptr_casted: [*]const [*:0]const u8 = @ptrCast(@alignCast(events_paths_ptr));344 const events_paths_ptr_casted: [*]const [*:0]const u8 = @ptrCast(@alignCast(events_paths_ptr));
340 const events_paths = events_paths_ptr_casted[0..num_events];345 const events_paths = events_paths_ptr_casted[0..num_events];
...@@ -349,17 +354,13 @@ fn eventCallback(...@@ -349,17 +354,13 @@ fn eventCallback(
349 false => {354 false => {
350 if (fse.watch_paths.get(event_path)) |steps| {355 if (fse.watch_paths.get(event_path)) |steps| {
351 assert(steps.len > 0);356 assert(steps.len > 0);
352 for (steps) |s| {357 if (invalidateSteps(maker, steps)) any_dirty = true;
353 if (s.invalidateResult(gpa)) any_dirty = true;
354 }
355 }358 }
356 if (std.fs.path.dirname(event_path)) |event_dirname| {359 if (std.fs.path.dirname(event_path)) |event_dirname| {
357 // Modifying '/foo/bar' triggers the watch on '/foo'.360 // Modifying '/foo/bar' triggers the watch on '/foo'.
358 if (fse.watch_paths.get(event_dirname)) |steps| {361 if (fse.watch_paths.get(event_dirname)) |steps| {
359 assert(steps.len > 0);362 assert(steps.len > 0);
360 for (steps) |s| {363 if (invalidateSteps(maker, steps)) any_dirty = true;
361 if (s.invalidateResult(gpa)) any_dirty = true;
362 }
363 }364 }
364 }365 }
365 },366 },
...@@ -372,9 +373,7 @@ fn eventCallback(...@@ -372,9 +373,7 @@ fn eventCallback(
372 const changed_path = std.fs.path.dirname(event_path) orelse event_path;373 const changed_path = std.fs.path.dirname(event_path) orelse event_path;
373 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {374 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {
374 if (dirStartsWith(watching_path, changed_path)) {375 if (dirStartsWith(watching_path, changed_path)) {
375 for (steps) |s| {376 if (invalidateSteps(maker, steps)) any_dirty = true;
376 if (s.invalidateResult(gpa)) any_dirty = true;
377 }
378 }377 }
379 }378 }
380 },379 },
...@@ -392,6 +391,15 @@ fn dirStartsWith(path: []const u8, prefix: []const u8) bool {...@@ -392,6 +391,15 @@ fn dirStartsWith(path: []const u8, prefix: []const u8) bool {
392 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`391 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`
393}392}
394393
394fn invalidateSteps(maker: *Maker, steps: []const std.Build.Configuration.Step.Index) bool {
395 var any_dirty = false;
396 for (steps) |step_index| {
397 const step = maker.stepByIndex(step_index);
398 if (maker.invalidateResult(step)) any_dirty = true;
399 }
400 return any_dirty;
401}
402
395const CFAllocatorRef = ?*const opaque {};403const CFAllocatorRef = ?*const opaque {};
396const CFArrayRef = *const opaque {};404const CFArrayRef = *const opaque {};
397const CFStringRef = *const opaque {};405const CFStringRef = *const opaque {};
...@@ -476,4 +484,5 @@ const Io = std.Io;...@@ -476,4 +484,5 @@ const Io = std.Io;
476const assert = std.debug.assert;484const assert = std.debug.assert;
477const Allocator = std.mem.Allocator;485const Allocator = std.mem.Allocator;
478const watch_log = std.log.scoped(.watch);486const watch_log = std.log.scoped(.watch);
479const FsEvents = @This();487const Maker = @import("../../Maker.zig");
488const Watch = @import("../Watch.zig");