diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index f7bbe5686b..5e5cb96df7 100644 --- 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 index b59f616d99..99502df188 100644 --- 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 index 0a56ce1822..04833b2644 100644 --- 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");