| ... | @@ -17,6 +17,7 @@ | ... | @@ -17,6 +17,7 @@ |
| 17 | //! the logic that would avoid them is currently disabled, because the build system kind | 17 | //! 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 at | 18 | //! 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. |
| | 20 | const FsEvents = @This(); |
| 20 | | 21 | |
| 21 | const enable_debug_logs = false; | 22 | const enable_debug_logs = false; |
| 22 | | 23 | |
| ... | @@ -30,7 +31,7 @@ paths_arena: std.heap.ArenaAllocator.State, | ... | @@ -30,7 +31,7 @@ paths_arena: std.heap.ArenaAllocator.State, |
| 30 | watch_roots: [][:0]const u8, | 31 | watch_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. |
| 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 | /// The semaphore we use to block the thread calling `wait` until the callback determines a relevant | 36 | /// 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 | } |
| 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 | 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(); |
| 125 | | 128 | |
| 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); |
| 128 | | 131 | |
| 129 | fse.watch_paths.clearRetainingCapacity(); | 132 | fse.watch_paths.clearRetainingCapacity(); |
| 130 | | 133 | |
| 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 one | 155 | // 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 | } |
| 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 | 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; |
| 211 | | 216 | |
| 212 | const rs = fse.resolved_symbols; | 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,7 +258,7 @@ pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory |
| 253 | | 258 | |
| 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 { |
| 321 | | 326 | |
| 322 | const EventCallbackCtx = struct { | 327 | const EventCallbackCtx = struct { |
| 323 | fse: *FsEvents, | 328 | fse: *FsEvents, |
| 324 | gpa: Allocator, | 329 | maker: *Maker, |
| 325 | }; | 330 | }; |
| 326 | | 331 | |
| 327 | fn eventCallback( | 332 | fn 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 | } |
| 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 | const CFAllocatorRef = ?*const opaque {}; | 403 | const CFAllocatorRef = ?*const opaque {}; |
| 396 | const CFArrayRef = *const opaque {}; | 404 | const CFArrayRef = *const opaque {}; |
| 397 | const CFStringRef = *const opaque {}; | 405 | const CFStringRef = *const opaque {}; |
| ... | @@ -476,4 +484,5 @@ const Io = std.Io; | ... | @@ -476,4 +484,5 @@ const Io = std.Io; |
| 476 | const assert = std.debug.assert; | 484 | const assert = std.debug.assert; |
| 477 | const Allocator = std.mem.Allocator; | 485 | const Allocator = std.mem.Allocator; |
| 478 | const watch_log = std.log.scoped(.watch); | 486 | const watch_log = std.log.scoped(.watch); |
| 479 | const FsEvents = @This(); | 487 | const Maker = @import("../../Maker.zig"); |
| | 488 | const Watch = @import("../Watch.zig"); |