commit b069a2eb21d4bdcbc87c566be013fa30f26d3a1e (tree)
parent 05fbeb4ea7acef3e9cfcce650343b1ef73e12e29
Author: Andrew Kelley <andrew@ziglang.org>
Date: Sat, 23 May 2026 14:10:40 -0700
Maker: update macos file watching code to new api
Diffstat:
3 files changed, 52 insertions(+), 40 deletions(-)
diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig
@@ -623,7 +623,7 @@ pub fn main(init: process.Init.Minimal) !void {
// Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
if (!Watch.have_impl) unreachable;
- try w.update(gpa, maker.step_stack.keys());
+ try w.update(maker.step_stack.keys());
// Wait until a file system notification arrives. Read all such events
// until the buffer is empty. Then wait for a debounce interval, resetting
diff --git a/lib/compiler/Maker/Watch.zig b/lib/compiler/Maker/Watch.zig
@@ -177,8 +177,9 @@ const Os = switch (builtin.os.tag) {
}
}
- fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {
+ fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
const maker = w.maker;
+ const gpa = maker.gpa;
// Add missing marks and note persisted ones.
for (steps) |step_index| {
@@ -465,8 +466,7 @@ const Os = switch (builtin.os.tag) {
};
};
- fn init(cwd_path: []const u8) !Watch {
- _ = cwd_path;
+ fn init(maker: *Maker) !Watch {
return .{
.dir_table = .{},
.dir_count = 0,
@@ -478,6 +478,7 @@ const Os = switch (builtin.os.tag) {
else => {},
},
.generation = 0,
+ .maker = maker,
};
}
@@ -546,7 +547,8 @@ const Os = switch (builtin.os.tag) {
return any_dirty;
}
- fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {
+ fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
+ const gpa = w.maker.gpa;
// Add missing marks and note persisted ones.
for (steps) |step| {
for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
@@ -677,8 +679,7 @@ const Os = switch (builtin.os.tag) {
const EV = std.c.EV;
const NOTE = std.c.NOTE;
- fn init(cwd_path: []const u8) !Watch {
- _ = cwd_path;
+ fn init(maker: *Maker) !Watch {
return .{
.dir_table = .{},
.dir_count = 0,
@@ -687,10 +688,12 @@ const Os = switch (builtin.os.tag) {
.handles = .empty,
},
.generation = 0,
+ .maker = maker,
};
}
- fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {
+ fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
+ const gpa = w.maker.gpa;
const handles = &w.os.handles;
for (steps) |step| {
for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
@@ -860,21 +863,21 @@ const Os = switch (builtin.os.tag) {
.macos => struct {
fse: FsEvents,
- fn init(cwd_path: []const u8) !Watch {
+ fn init(maker: *Maker) !Watch {
return .{
- .os = .{ .fse = try .init(cwd_path) },
+ .os = .{ .fse = try .init(maker.graph.cache.cwd) },
.dir_count = 0,
.dir_table = undefined,
.generation = undefined,
+ .maker = maker,
};
}
- fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {
- try w.os.fse.setPaths(gpa, steps);
+ fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
+ try w.os.fse.setPaths(w.maker, steps);
w.dir_count = w.os.fse.watch_roots.len;
}
- fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult {
- _ = io;
- return w.os.fse.wait(gpa, switch (timeout) {
+ fn wait(w: *Watch, timeout: Timeout) !WaitResult {
+ return w.os.fse.wait(w.maker, switch (timeout) {
.none => null,
.ms => |ms| @as(u64, ms) * std.time.ns_per_ms,
});
@@ -938,8 +941,8 @@ fn markStepSetDirty(maker: *Maker, step_set: *StepSet, any_dirty: bool) bool {
return any_dirty or this_any_dirty;
}
-pub fn update(w: *Watch, gpa: Allocator, steps: []const Configuration.Step.Index) !void {
- return Os.update(w, gpa, steps);
+pub fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
+ return Os.update(w, steps);
}
pub const Timeout = union(enum) {
diff --git a/lib/compiler/Maker/Watch/FsEvents.zig b/lib/compiler/Maker/Watch/FsEvents.zig
@@ -17,6 +17,7 @@
//! the logic that would avoid them is currently disabled, because the build system kind
//! of relies on them at the time of writing to avoid redundant work -- see the comment at
//! the top of `wait` for details.
+const FsEvents = @This();
const enable_debug_logs = false;
@@ -30,7 +31,7 @@ paths_arena: std.heap.ArenaAllocator.State,
watch_roots: [][:0]const u8,
/// All of the paths being watched. Value is the set of steps which depend on the file/directory.
/// Keys and values are in `paths_arena`, but this map is allocated into the GPA.
-watch_paths: std.StringArrayHashMapUnmanaged([]const *std.Build.Step),
+watch_paths: std.array_hash_map.String([]const std.Build.Configuration.Step.Index),
/// The semaphore we use to block the thread calling `wait` until the callback determines a relevant
/// 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 {
}
}
-pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step) !void {
+pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configuration.Step.Index) !void {
+ const gpa = maker.gpa;
+
var paths_arena_instance = fse.paths_arena.promote(gpa);
defer fse.paths_arena = paths_arena_instance.state;
const paths_arena = paths_arena_instance.allocator();
- var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty;
+ var need_dirs: std.array_hash_map.String(void) = .empty;
defer need_dirs.deinit(gpa);
fse.watch_paths.clearRetainingCapacity();
- // We take `step` by pointer for a slight memory optimization in a moment.
- for (steps) |*step| {
- for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| {
+ // We take `step_index` by pointer for a slight memory optimization in a moment.
+ for (steps) |*step_index| {
+ const step = maker.stepByIndex(step_index.*);
+ for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{
fse.cwd_path, path.root_dir.path orelse ".", path.sub_path,
});
@@ -143,14 +147,14 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
const gop = try fse.watch_paths.getOrPut(gpa, watch_path);
if (gop.found_existing) {
const old_steps = gop.value_ptr.*;
- const new_steps = try paths_arena.alloc(*std.Build.Step, old_steps.len + 1);
+ const new_steps = try paths_arena.alloc(std.Build.Configuration.Step.Index, old_steps.len + 1);
@memcpy(new_steps[0..old_steps.len], old_steps);
- new_steps[old_steps.len] = step.*;
+ new_steps[old_steps.len] = step_index.*;
gop.value_ptr.* = new_steps;
} else {
// This is why we captured `step` by pointer! We can avoid allocating a slice of one
// step in the arena in the common case where a file is referenced by only one step.
- gop.value_ptr.* = step[0..1];
+ gop.value_ptr.* = step_index[0..1];
}
}
}
@@ -206,8 +210,9 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
}
}
-pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!std.Build.Watch.WaitResult {
+pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!Watch.WaitResult {
if (fse.watch_roots.len == 0) @panic("nothing to watch");
+ const gpa = maker.gpa;
const rs = fse.resolved_symbols;
@@ -253,7 +258,7 @@ pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory
const callback_ctx: EventCallbackCtx = .{
.fse = fse,
- .gpa = gpa,
+ .maker = maker,
};
const event_stream = rs.FSEventStreamCreate(
null,
@@ -321,7 +326,7 @@ const cf_alloc_callbacks = struct {
const EventCallbackCtx = struct {
fse: *FsEvents,
- gpa: Allocator,
+ maker: *Maker,
};
fn eventCallback(
@@ -333,8 +338,8 @@ fn eventCallback(
events_ids_ptr: [*]const FSEventStreamEventId,
) callconv(.c) void {
const ctx: *const EventCallbackCtx = @ptrCast(@alignCast(client_callback_info));
+ const maker = ctx.maker;
const fse = ctx.fse;
- const gpa = ctx.gpa;
const rs = fse.resolved_symbols;
const events_paths_ptr_casted: [*]const [*:0]const u8 = @ptrCast(@alignCast(events_paths_ptr));
const events_paths = events_paths_ptr_casted[0..num_events];
@@ -349,17 +354,13 @@ fn eventCallback(
false => {
if (fse.watch_paths.get(event_path)) |steps| {
assert(steps.len > 0);
- for (steps) |s| {
- if (s.invalidateResult(gpa)) any_dirty = true;
- }
+ if (invalidateSteps(maker, steps)) any_dirty = true;
}
if (std.fs.path.dirname(event_path)) |event_dirname| {
// Modifying '/foo/bar' triggers the watch on '/foo'.
if (fse.watch_paths.get(event_dirname)) |steps| {
assert(steps.len > 0);
- for (steps) |s| {
- if (s.invalidateResult(gpa)) any_dirty = true;
- }
+ if (invalidateSteps(maker, steps)) any_dirty = true;
}
}
},
@@ -372,9 +373,7 @@ fn eventCallback(
const changed_path = std.fs.path.dirname(event_path) orelse event_path;
for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {
if (dirStartsWith(watching_path, changed_path)) {
- for (steps) |s| {
- if (s.invalidateResult(gpa)) any_dirty = true;
- }
+ if (invalidateSteps(maker, steps)) any_dirty = true;
}
}
},
@@ -392,6 +391,15 @@ fn dirStartsWith(path: []const u8, prefix: []const u8) bool {
return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`
}
+fn invalidateSteps(maker: *Maker, steps: []const std.Build.Configuration.Step.Index) bool {
+ var any_dirty = false;
+ for (steps) |step_index| {
+ const step = maker.stepByIndex(step_index);
+ if (maker.invalidateResult(step)) any_dirty = true;
+ }
+ return any_dirty;
+}
+
const CFAllocatorRef = ?*const opaque {};
const CFArrayRef = *const opaque {};
const CFStringRef = *const opaque {};
@@ -476,4 +484,5 @@ const Io = std.Io;
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const watch_log = std.log.scoped(.watch);
-const FsEvents = @This();
+const Maker = @import("../../Maker.zig");
+const Watch = @import("../Watch.zig");