| ... | ... | @@ -17,6 +17,7 @@ |
| 17 | 17 | //! the logic that would avoid them is currently disabled, because the build system kind |
| 18 | 18 | //! of relies on them at the time of writing to avoid redundant work -- see the comment at |
| 19 | 19 | //! the top of `wait` for details. |
| 20 | const FsEvents = @This(); |
| 20 | 21 | |
| 21 | 22 | const enable_debug_logs = false; |
| 22 | 23 | |
| ... | ... | @@ -30,7 +31,7 @@ paths_arena: std.heap.ArenaAllocator.State, |
| 30 | 31 | watch_roots: [][:0]const u8, |
| 31 | 32 | /// All of the paths being watched. Value is the set of steps which depend on the file/directory. |
| 32 | 33 | /// Keys and values are in `paths_arena`, but this map is allocated into the GPA. |
| 33 | | watch_paths: std.StringArrayHashMapUnmanaged([]const *std.Build.Step), |
| 34 | watch_paths: std.array_hash_map.String([]const std.Build.Configuration.Step.Index), |
| 34 | 35 | |
| 35 | 36 | /// The semaphore we use to block the thread calling `wait` until the callback determines a relevant |
| 36 | 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 | 119 | } |
| 119 | 120 | } |
| 120 | 121 | |
| 121 | | pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step) !void { |
| 122 | pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configuration.Step.Index) !void { |
| 123 | const gpa = maker.gpa; |
| 124 | |
| 122 | 125 | var paths_arena_instance = fse.paths_arena.promote(gpa); |
| 123 | 126 | defer fse.paths_arena = paths_arena_instance.state; |
| 124 | 127 | const paths_arena = paths_arena_instance.allocator(); |
| 125 | 128 | |
| 126 | | var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty; |
| 129 | var need_dirs: std.array_hash_map.String(void) = .empty; |
| 127 | 130 | defer need_dirs.deinit(gpa); |
| 128 | 131 | |
| 129 | 132 | fse.watch_paths.clearRetainingCapacity(); |
| 130 | 133 | |
| 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| { |
| 134 | 138 | const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{ |
| 135 | 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 | 147 | const gop = try fse.watch_paths.getOrPut(gpa, watch_path); |
| 144 | 148 | if (gop.found_existing) { |
| 145 | 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 | 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 | 153 | gop.value_ptr.* = new_steps; |
| 150 | 154 | } else { |
| 151 | 155 | // This is why we captured `step` by pointer! We can avoid allocating a slice of one |
| 152 | 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 | 210 | } |
| 207 | 211 | } |
| 208 | 212 | |
| 209 | | pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!std.Build.Watch.WaitResult { |
| 213 | pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!Watch.WaitResult { |
| 210 | 214 | if (fse.watch_roots.len == 0) @panic("nothing to watch"); |
| 215 | const gpa = maker.gpa; |
| 211 | 216 | |
| 212 | 217 | const rs = fse.resolved_symbols; |
| 213 | 218 | |
| ... | ... | @@ -253,7 +258,7 @@ pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory |
| 253 | 258 | |
| 254 | 259 | const callback_ctx: EventCallbackCtx = .{ |
| 255 | 260 | .fse = fse, |
| 256 | | .gpa = gpa, |
| 261 | .maker = maker, |
| 257 | 262 | }; |
| 258 | 263 | const event_stream = rs.FSEventStreamCreate( |
| 259 | 264 | null, |
| ... | ... | @@ -321,7 +326,7 @@ const cf_alloc_callbacks = struct { |
| 321 | 326 | |
| 322 | 327 | const EventCallbackCtx = struct { |
| 323 | 328 | fse: *FsEvents, |
| 324 | | gpa: Allocator, |
| 329 | maker: *Maker, |
| 325 | 330 | }; |
| 326 | 331 | |
| 327 | 332 | fn eventCallback( |
| ... | ... | @@ -333,8 +338,8 @@ fn eventCallback( |
| 333 | 338 | events_ids_ptr: [*]const FSEventStreamEventId, |
| 334 | 339 | ) callconv(.c) void { |
| 335 | 340 | const ctx: *const EventCallbackCtx = @ptrCast(@alignCast(client_callback_info)); |
| 341 | const maker = ctx.maker; |
| 336 | 342 | const fse = ctx.fse; |
| 337 | | const gpa = ctx.gpa; |
| 338 | 343 | const rs = fse.resolved_symbols; |
| 339 | 344 | const events_paths_ptr_casted: [*]const [*:0]const u8 = @ptrCast(@alignCast(events_paths_ptr)); |
| 340 | 345 | const events_paths = events_paths_ptr_casted[0..num_events]; |
| ... | ... | @@ -349,17 +354,13 @@ fn eventCallback( |
| 349 | 354 | false => { |
| 350 | 355 | if (fse.watch_paths.get(event_path)) |steps| { |
| 351 | 356 | 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; |
| 355 | 358 | } |
| 356 | 359 | if (std.fs.path.dirname(event_path)) |event_dirname| { |
| 357 | 360 | // Modifying '/foo/bar' triggers the watch on '/foo'. |
| 358 | 361 | if (fse.watch_paths.get(event_dirname)) |steps| { |
| 359 | 362 | 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; |
| 363 | 364 | } |
| 364 | 365 | } |
| 365 | 366 | }, |
| ... | ... | @@ -372,9 +373,7 @@ fn eventCallback( |
| 372 | 373 | const changed_path = std.fs.path.dirname(event_path) orelse event_path; |
| 373 | 374 | for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| { |
| 374 | 375 | 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; |
| 378 | 377 | } |
| 379 | 378 | } |
| 380 | 379 | }, |
| ... | ... | @@ -392,6 +391,15 @@ fn dirStartsWith(path: []const u8, prefix: []const u8) bool { |
| 392 | 391 | return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar` |
| 393 | 392 | } |
| 394 | 393 | |
| 394 | fn 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 | |
| 395 | 403 | const CFAllocatorRef = ?*const opaque {}; |
| 396 | 404 | const CFArrayRef = *const opaque {}; |
| 397 | 405 | const CFStringRef = *const opaque {}; |
| ... | ... | @@ -476,4 +484,5 @@ const Io = std.Io; |
| 476 | 484 | const assert = std.debug.assert; |
| 477 | 485 | const Allocator = std.mem.Allocator; |
| 478 | 486 | const watch_log = std.log.scoped(.watch); |
| 479 | | const FsEvents = @This(); |
| 487 | const Maker = @import("../../Maker.zig"); |
| 488 | const Watch = @import("../Watch.zig"); |