Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

setPaths

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();

    // 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,
            });
            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 {
                    // 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_index[0..1];
                }
            }
        }
    }

    {
        // There's no point looking at directories inside other ones (e.g. "/foo" and "/foo/bar").
        // 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)) {
                    // this path is '/foo/bar/qux', but '/foo' or '/foo/bar' was already added
                    break;
                }
            } else {
                need_dirs.putAssumeCapacityNoClobber(dir_path, {});
            }
        }
    }

    // `need_dirs` is now a set of directories to watch with no redundancy. In practice, this is very
    // 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) {
        // Fallback: watch the whole filesystem. This is excessive, but... it *works* :P
        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});
        }
    }
}