feature. See also
. The project being documented here (as the example) is the Zig library itself.
FsEvents.setPaths
pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configuration.Step.Index) !void
File
Code
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.array_hash_map.String(void) = .empty;
defer need_dirs.deinit(gpa);
fse.watch_paths.clearRetainingCapacity();
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,
});
try need_dirs.put(gpa, resolved_dir, {});
for (files.items) |file_name| {
const watch_path = if (std.mem.eql(u8, file_name, "."))
resolved_dir
else
try std.fs.path.join(paths_arena, &.{ resolved_dir, file_name });
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.Configuration.Step.Index, old_steps.len + 1);
@memcpy(new_steps[0..old_steps.len], old_steps);
new_steps[old_steps.len] = step_index.*;
gop.value_ptr.* = new_steps;
} else {
// step in the arena in the common case where a file is referenced by only one step.
gop.value_ptr.* = step_index[0..1];
}
}
}
}
{
// To eliminate these, we'll re-add directories in order of path length with a redundancy check.
const old_dirs = try gpa.dupe([]const u8, need_dirs.keys());
defer gpa.free(old_dirs);
std.mem.sort([]const u8, old_dirs, {}, struct {
fn lessThan(ctx: void, a: []const u8, b: []const u8) bool {
ctx;
return std.mem.lessThan(u8, a, b);
}
}.lessThan);
need_dirs.clearRetainingCapacity();
for (old_dirs) |dir_path| {
var it: std.fs.path.ComponentIterator(.posix, u8) = .init(dir_path);
while (it.next()) |component| {
if (need_dirs.contains(component.path)) {
break;
}
} else {
need_dirs.putAssumeCapacityNoClobber(dir_path, {});
}
}
}
// likely to have reduced it to a quite small set (e.g. it'll typically coalesce a full `src/`
// directory into one entry). However, the FSEventStream API has a fairly low undocumented limit
// on total watches (supposedly 4096), so we should handle the case where we exceed it. To be
// safe, because this API can be a little unpredictable, we'll cap ourselves a little *below*
// that known limit.
if (need_dirs.count() > 2048) {
if (enable_debug_logs) watch_log.debug("too many dirs; recursively watching root", .{});
fse.watch_roots = try gpa.realloc(fse.watch_roots, 1);
fse.watch_roots[0] = "/";
} else {
fse.watch_roots = try gpa.realloc(fse.watch_roots, need_dirs.count());
for (fse.watch_roots, need_dirs.keys()) |*out, in| {
out.* = try paths_arena.dupeSentinel(u8, in, 0);
}
}
if (enable_debug_logs) {
watch_log.debug("watching {d} paths using {d} recursive watches:", .{ fse.watch_paths.count(), fse.watch_roots.len });
for (fse.watch_roots) |dir_path| {
watch_log.debug("- '{s}'", .{dir_path});
}
}
}