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 {
623623 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
624624 if (!Watch.have_impl) unreachable;
625625
626 try w.update(gpa, maker.step_stack.keys());
626 try w.update(maker.step_stack.keys());
627627
628628 // Wait until a file system notification arrives. Read all such events
629629 // 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) {
177177 }
178178 }
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 {
181181 const maker = w.maker;
182 const gpa = maker.gpa;
182183
183184 // Add missing marks and note persisted ones.
184185 for (steps) |step_index| {
......@@ -465,8 +466,7 @@ const Os = switch (builtin.os.tag) {
465466 };
466467 };
467468
468 fn init(cwd_path: []const u8) !Watch {
469 _ = cwd_path;
469 fn init(maker: *Maker) !Watch {
470470 return .{
471471 .dir_table = .{},
472472 .dir_count = 0,
......@@ -478,6 +478,7 @@ const Os = switch (builtin.os.tag) {
478478 else => {},
479479 },
480480 .generation = 0,
481 .maker = maker,
481482 };
482483 }
483484
......@@ -546,7 +547,8 @@ const Os = switch (builtin.os.tag) {
546547 return any_dirty;
547548 }
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;
550552 // Add missing marks and note persisted ones.
551553 for (steps) |step| {
552554 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
......@@ -677,8 +679,7 @@ const Os = switch (builtin.os.tag) {
677679 const EV = std.c.EV;
678680 const NOTE = std.c.NOTE;
679681
680 fn init(cwd_path: []const u8) !Watch {
681 _ = cwd_path;
682 fn init(maker: *Maker) !Watch {
682683 return .{
683684 .dir_table = .{},
684685 .dir_count = 0,
......@@ -687,10 +688,12 @@ const Os = switch (builtin.os.tag) {
687688 .handles = .empty,
688689 },
689690 .generation = 0,
691 .maker = maker,
690692 };
691693 }
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;
694697 const handles = &w.os.handles;
695698 for (steps) |step| {
696699 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
......@@ -860,21 +863,21 @@ const Os = switch (builtin.os.tag) {
860863 .macos => struct {
861864 fse: FsEvents,
862865
863 fn init(cwd_path: []const u8) !Watch {
866 fn init(maker: *Maker) !Watch {
864867 return .{
865 .os = .{ .fse = try .init(cwd_path) },
868 .os = .{ .fse = try .init(maker.graph.cache.cwd) },
866869 .dir_count = 0,
867870 .dir_table = undefined,
868871 .generation = undefined,
872 .maker = maker,
869873 };
870874 }
871 fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {
872 try w.os.fse.setPaths(gpa, steps);
875 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
876 try w.os.fse.setPaths(w.maker, steps);
873877 w.dir_count = w.os.fse.watch_roots.len;
874878 }
875 fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult {
876 _ = io;
877 return w.os.fse.wait(gpa, switch (timeout) {
879 fn wait(w: *Watch, timeout: Timeout) !WaitResult {
880 return w.os.fse.wait(w.maker, switch (timeout) {
878881 .none => null,
879882 .ms => |ms| @as(u64, ms) * std.time.ns_per_ms,
880883 });
......@@ -938,8 +941,8 @@ fn markStepSetDirty(maker: *Maker, step_set: *StepSet, any_dirty: bool) bool {
938941 return any_dirty or this_any_dirty;
939942}
940943
941pub fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {
942 return Os.update(w, gpa, steps);
944pub fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
945 return Os.update(w, steps);
943946}
944947
945948pub const Timeout = union(enum) {
lib/compiler/Maker/Watch/FsEvents.zig+32-23
......@@ -17,6 +17,7 @@
1717//! the logic that would avoid them is currently disabled, because the build system kind
1818//! of relies on them at the time of writing to avoid redundant work -- see the comment at
1919//! the top of `wait` for details.
20const FsEvents = @This();
2021
2122const enable_debug_logs = false;
2223
......@@ -30,7 +31,7 @@ paths_arena: std.heap.ArenaAllocator.State,
3031watch_roots: [][:0]const u8,
3132/// All of the paths being watched. Value is the set of steps which depend on the file/directory.
3233/// 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
3536/// The semaphore we use to block the thread calling `wait` until the callback determines a relevant
3637/// 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 {
118119 }
119120}
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
122125 var paths_arena_instance = fse.paths_arena.promote(gpa);
123126 defer fse.paths_arena = paths_arena_instance.state;
124127 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;
127130 defer need_dirs.deinit(gpa);
128131
129132 fse.watch_paths.clearRetainingCapacity();
130133
131 // We take `step` by pointer for a slight memory optimization in a moment.
132 for (steps) |*step| {
133 for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| {
134 // We take `step_index` by pointer for a slight memory optimization in a moment.
135 for (steps) |*step_index| {
136 const step = maker.stepByIndex(step_index.*);
137 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
134138 const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{
135139 fse.cwd_path, path.root_dir.path orelse ".", path.sub_path,
136140 });
......@@ -143,14 +147,14 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
143147 const gop = try fse.watch_paths.getOrPut(gpa, watch_path);
144148 if (gop.found_existing) {
145149 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);
147151 @memcpy(new_steps[0..old_steps.len], old_steps);
148 new_steps[old_steps.len] = step.*;
152 new_steps[old_steps.len] = step_index.*;
149153 gop.value_ptr.* = new_steps;
150154 } else {
151155 // This is why we captured `step` by pointer! We can avoid allocating a slice of one
152156 // 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];
154158 }
155159 }
156160 }
......@@ -206,8 +210,9 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
206210 }
207211}
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 {
210214 if (fse.watch_roots.len == 0) @panic("nothing to watch");
215 const gpa = maker.gpa;
211216
212217 const rs = fse.resolved_symbols;
213218
......@@ -253,7 +258,7 @@ pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory
253258
254259 const callback_ctx: EventCallbackCtx = .{
255260 .fse = fse,
256 .gpa = gpa,
261 .maker = maker,
257262 };
258263 const event_stream = rs.FSEventStreamCreate(
259264 null,
......@@ -321,7 +326,7 @@ const cf_alloc_callbacks = struct {
321326
322327const EventCallbackCtx = struct {
323328 fse: *FsEvents,
324 gpa: Allocator,
329 maker: *Maker,
325330};
326331
327332fn eventCallback(
......@@ -333,8 +338,8 @@ fn eventCallback(
333338 events_ids_ptr: [*]const FSEventStreamEventId,
334339) callconv(.c) void {
335340 const ctx: *const EventCallbackCtx = @ptrCast(@alignCast(client_callback_info));
341 const maker = ctx.maker;
336342 const fse = ctx.fse;
337 const gpa = ctx.gpa;
338343 const rs = fse.resolved_symbols;
339344 const events_paths_ptr_casted: [*]const [*:0]const u8 = @ptrCast(@alignCast(events_paths_ptr));
340345 const events_paths = events_paths_ptr_casted[0..num_events];
......@@ -349,17 +354,13 @@ fn eventCallback(
349354 false => {
350355 if (fse.watch_paths.get(event_path)) |steps| {
351356 assert(steps.len > 0);
352 for (steps) |s| {
353 if (s.invalidateResult(gpa)) any_dirty = true;
354 }
357 if (invalidateSteps(maker, steps)) any_dirty = true;
355358 }
356359 if (std.fs.path.dirname(event_path)) |event_dirname| {
357360 // Modifying '/foo/bar' triggers the watch on '/foo'.
358361 if (fse.watch_paths.get(event_dirname)) |steps| {
359362 assert(steps.len > 0);
360 for (steps) |s| {
361 if (s.invalidateResult(gpa)) any_dirty = true;
362 }
363 if (invalidateSteps(maker, steps)) any_dirty = true;
363364 }
364365 }
365366 },
......@@ -372,9 +373,7 @@ fn eventCallback(
372373 const changed_path = std.fs.path.dirname(event_path) orelse event_path;
373374 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {
374375 if (dirStartsWith(watching_path, changed_path)) {
375 for (steps) |s| {
376 if (s.invalidateResult(gpa)) any_dirty = true;
377 }
376 if (invalidateSteps(maker, steps)) any_dirty = true;
378377 }
379378 }
380379 },
......@@ -392,6 +391,15 @@ fn dirStartsWith(path: []const u8, prefix: []const u8) bool {
392391 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`
393392}
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
395403const CFAllocatorRef = ?*const opaque {};
396404const CFArrayRef = *const opaque {};
397405const CFStringRef = *const opaque {};
......@@ -476,4 +484,5 @@ const Io = std.Io;
476484const assert = std.debug.assert;
477485const Allocator = std.mem.Allocator;
478486const watch_log = std.log.scoped(.watch);
479const FsEvents = @This();
487const Maker = @import("../../Maker.zig");
488const Watch = @import("../Watch.zig");