authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-09 23:15:22-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-12 00:14:08-07:00
log7bccef3e4e4d48c2e1d34ec28754d8c33902f2bb
treecc708ebeb8f8885c3ca4cfaa273a1eaee5101f87
parent26bdc836d2d9b2654f7f95fec34c6276070f2a59

std.Build.Watch: introduce special file "." to watch entire dir

And use it to implement InstallDir Step watch integration. I'm not seeing any events triggered when I run `mkdir` in the watched directory, however, and I have not yet figured out why.

3 files changed, 75 insertions(+), 21 deletions(-)

lib/std/Build/Step.zig+39-2
......@@ -160,6 +160,7 @@ pub const Inputs = struct {
160160 };
161161
162162 pub const Table = std.ArrayHashMapUnmanaged(Build.Cache.Path, Files, Build.Cache.Path.TableAdapter, false);
163 /// The special file name "." means any changes inside the directory.
163164 pub const Files = std.ArrayListUnmanaged([]const u8);
164165
165166 pub fn populated(inputs: *Inputs) bool {
......@@ -611,8 +612,9 @@ pub fn clearWatchInputs(step: *Step) void {
611612 step.inputs.clear(gpa);
612613}
613614
614pub fn addWatchInput(step: *Step, lazy_path: Build.LazyPath) Allocator.Error!void {
615 switch (lazy_path) {
615/// Places a *file* dependency on the path.
616pub fn addWatchInput(step: *Step, lazy_file: Build.LazyPath) Allocator.Error!void {
617 switch (lazy_file) {
616618 .src_path => |src_path| try addWatchInputFromBuilder(step, src_path.owner, src_path.sub_path),
617619 .dependency => |d| try addWatchInputFromBuilder(step, d.dependency.builder, d.sub_path),
618620 .cwd_relative => |path_string| {
......@@ -629,6 +631,34 @@ pub fn addWatchInput(step: *Step, lazy_path: Build.LazyPath) Allocator.Error!voi
629631 }
630632}
631633
634/// Any changes inside the directory will trigger invalidation.
635///
636/// See also `addDirectoryWatchInputFromPath` which takes a `Build.Cache.Path` instead.
637pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Allocator.Error!void {
638 switch (lazy_directory) {
639 .src_path => |src_path| try addDirectoryWatchInputFromBuilder(step, src_path.owner, src_path.sub_path),
640 .dependency => |d| try addDirectoryWatchInputFromBuilder(step, d.dependency.builder, d.sub_path),
641 .cwd_relative => |path_string| {
642 try addDirectoryWatchInputFromPath(step, .{
643 .root_dir = .{
644 .path = null,
645 .handle = std.fs.cwd(),
646 },
647 .sub_path = path_string,
648 });
649 },
650 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
651 .generated => {},
652 }
653}
654
655/// Any changes inside the directory will trigger invalidation.
656///
657/// See also `addDirectoryWatchInput` which takes a `Build.LazyPath` instead.
658pub fn addDirectoryWatchInputFromPath(step: *Step, path: Build.Cache.Path) !void {
659 return addWatchInputFromPath(step, path, ".");
660}
661
632662fn addWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8) !void {
633663 return addWatchInputFromPath(step, .{
634664 .root_dir = builder.build_root,
......@@ -636,6 +666,13 @@ fn addWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8)
636666 }, std.fs.path.basename(sub_path));
637667}
638668
669fn addDirectoryWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8) !void {
670 return addDirectoryWatchInputFromPath(step, .{
671 .root_dir = builder.build_root,
672 .sub_path = sub_path,
673 });
674}
675
639676fn addWatchInputFromPath(step: *Step, path: Build.Cache.Path, basename: []const u8) !void {
640677 const gpa = step.owner.allocator;
641678 const gop = try step.inputs.table.getOrPut(gpa, path);
lib/std/Build/Step/InstallDir.zig+16-10
......@@ -59,12 +59,14 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
5959 _ = prog_node;
6060 const b = step.owner;
6161 const install_dir: *InstallDir = @fieldParentPtr("step", step);
62 step.clearWatchInputs();
6263 const arena = b.allocator;
6364 const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir);
64 const src_dir_path = install_dir.options.source_dir.getPath2(b, step);
65 var src_dir = b.build_root.handle.openDir(src_dir_path, .{ .iterate = true }) catch |err| {
66 return step.fail("unable to open source directory '{}{s}': {s}", .{
67 b.build_root, src_dir_path, @errorName(err),
65 const src_dir_path = install_dir.options.source_dir.getPath3(b, step);
66 try step.addDirectoryWatchInput(install_dir.options.source_dir);
67 var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOpt() orelse ".", .{ .iterate = true }) catch |err| {
68 return step.fail("unable to open source directory '{}': {s}", .{
69 src_dir_path, @errorName(err),
6870 });
6971 };
7072 defer src_dir.close();
......@@ -88,12 +90,16 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
8890 }
8991
9092 // relative to src build root
91 const src_sub_path = b.pathJoin(&.{ src_dir_path, entry.path });
93 const src_sub_path = try src_dir_path.join(arena, entry.path);
9294 const dest_path = b.pathJoin(&.{ dest_prefix, entry.path });
9395 const cwd = fs.cwd();
9496
9597 switch (entry.kind) {
96 .directory => try cwd.makePath(dest_path),
98 .directory => {
99 const subdir_path = try src_dir_path.join(arena, entry.path);
100 try step.addDirectoryWatchInputFromPath(subdir_path);
101 try cwd.makePath(dest_path);
102 },
97103 .file => {
98104 for (install_dir.options.blank_extensions) |ext| {
99105 if (mem.endsWith(u8, entry.path, ext)) {
......@@ -103,14 +109,14 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
103109 }
104110
105111 const prev_status = fs.Dir.updateFile(
106 b.build_root.handle,
107 src_sub_path,
112 src_sub_path.root_dir.handle,
113 src_sub_path.sub_path,
108114 cwd,
109115 dest_path,
110116 .{},
111117 ) catch |err| {
112 return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{
113 b.build_root, src_sub_path, dest_path, @errorName(err),
118 return step.fail("unable to update file from '{}' to '{s}': {s}", .{
119 src_sub_path, dest_path, @errorName(err),
114120 });
115121 };
116122 all_cached = all_cached and prev_status == .fresh;
lib/std/Build/Watch.zig+20-9
......@@ -12,10 +12,13 @@ generation: Generation,
1212
1313pub const fan_mask: std.os.linux.fanotify.MarkMask = .{
1414 .CLOSE_WRITE = true,
15 .CREATE = true,
1516 .DELETE = true,
17 .DELETE_SELF = true,
18 .EVENT_ON_CHILD = true,
1619 .MOVED_FROM = true,
1720 .MOVED_TO = true,
18 .EVENT_ON_CHILD = true,
21 .MOVE_SELF = true,
1922};
2023
2124pub const init: Watch = .{
......@@ -32,6 +35,7 @@ pub const init: Watch = .{
3235const DirTable = std.ArrayHashMapUnmanaged(Cache.Path, void, Cache.Path.TableAdapter, false);
3336
3437const HandleTable = std.ArrayHashMapUnmanaged(LinuxFileHandle, ReactionSet, LinuxFileHandle.Adapter, false);
38/// Special key of "." means any changes in this directory trigger the steps.
3539const ReactionSet = std.StringArrayHashMapUnmanaged(StepSet);
3640const StepSet = std.AutoArrayHashMapUnmanaged(*Step, Generation);
3741
......@@ -149,14 +153,10 @@ pub fn markDirtySteps(w: *Watch, gpa: Allocator) !bool {
149153 const file_name = std.mem.span(file_name_z);
150154 const lfh: Watch.LinuxFileHandle = .{ .handle = file_handle };
151155 if (w.handle_table.getPtr(lfh)) |reaction_set| {
152 if (reaction_set.getPtr(file_name)) |step_set| {
153 for (step_set.keys()) |step| {
154 if (step.state != .precheck_done) {
155 step.recursiveReset(gpa);
156 any_dirty = true;
157 }
158 }
159 }
156 if (reaction_set.getPtr(".")) |glob_set|
157 any_dirty = markStepSetDirty(gpa, glob_set, any_dirty);
158 if (reaction_set.getPtr(file_name)) |step_set|
159 any_dirty = markStepSetDirty(gpa, step_set, any_dirty);
160160 }
161161 },
162162 else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}),
......@@ -187,3 +187,14 @@ fn markAllFilesDirty(w: *Watch, gpa: Allocator) void {
187187 }
188188 }
189189}
190
191fn markStepSetDirty(gpa: Allocator, step_set: *StepSet, any_dirty: bool) bool {
192 var this_any_dirty = false;
193 for (step_set.keys()) |step| {
194 if (step.state != .precheck_done) {
195 step.recursiveReset(gpa);
196 this_any_dirty = true;
197 }
198 }
199 return any_dirty or this_any_dirty;
200}