Commit 2e42969786

Andrew Kelley <andrew@ziglang.org>
2024-07-11 02:14:54
std.Build.Step.Run: integrate with --watch
1 parent 6fcb189
Changed files (2)
lib
std
Build
lib/std/Build/Step/Run.zig
@@ -615,7 +615,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
                     // On Windows we don't have rpaths so we have to add .dll search paths to PATH
                     run.addPathForDynLibs(artifact);
                 }
-                const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?; // the path is guaranteed to be set
+                const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?;
 
                 try argv_list.append(file_path);
 
@@ -665,7 +665,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
         _ = try man.addFile(lazy_path.getPath2(b, step), null);
     }
 
-    if (!has_side_effects and try step.cacheHit(&man)) {
+    if (!has_side_effects and try step.cacheHitAndWatch(&man)) {
         // cache hit, skip running command
         const digest = man.final();
 
@@ -719,7 +719,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
         }
 
         try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node);
-        if (!has_side_effects) try step.writeManifest(&man);
+        if (!has_side_effects) try step.writeManifestAndWatch(&man);
         return;
     };
 
@@ -795,7 +795,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
         };
     }
 
-    if (!has_side_effects) try step.writeManifest(&man);
+    if (!has_side_effects) try step.writeManifestAndWatch(&man);
 
     try populateGeneratedPaths(
         arena,
lib/std/Build/Step.zig
@@ -582,11 +582,26 @@ pub fn allocPrintCmd2(
     return buf.toOwnedSlice(arena);
 }
 
+/// Prefer `cacheHitAndWatch` unless you already added watch inputs
+/// separately from using the cache system.
 pub fn cacheHit(s: *Step, man: *Build.Cache.Manifest) !bool {
     s.result_cached = man.hit() catch |err| return failWithCacheError(s, man, err);
     return s.result_cached;
 }
 
+/// Clears previous watch inputs, if any, and then populates watch inputs from
+/// the full set of files picked up by the cache manifest.
+///
+/// Must be accompanied with `writeManifestAndWatch`.
+pub fn cacheHitAndWatch(s: *Step, man: *Build.Cache.Manifest) !bool {
+    const is_hit = man.hit() catch |err| return failWithCacheError(s, man, err);
+    s.result_cached = is_hit;
+    // The above call to hit() populates the manifest with files, so in case of
+    // a hit, we need to populate watch inputs.
+    if (is_hit) try setWatchInputsFromManifest(s, man);
+    return is_hit;
+}
+
 fn failWithCacheError(s: *Step, man: *const Build.Cache.Manifest, err: anyerror) anyerror {
     const i = man.failed_file_index orelse return err;
     const pp = man.files.keys()[i].prefixed_path;
@@ -594,6 +609,8 @@ fn failWithCacheError(s: *Step, man: *const Build.Cache.Manifest, err: anyerror)
     return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path });
 }
 
+/// Prefer `writeManifestAndWatch` unless you already added watch inputs
+/// separately from using the cache system.
 pub fn writeManifest(s: *Step, man: *Build.Cache.Manifest) !void {
     if (s.test_results.isSuccess()) {
         man.writeManifest() catch |err| {
@@ -602,6 +619,29 @@ pub fn writeManifest(s: *Step, man: *Build.Cache.Manifest) !void {
     }
 }
 
+/// Clears previous watch inputs, if any, and then populates watch inputs from
+/// the full set of files picked up by the cache manifest.
+///
+/// Must be accompanied with `cacheHitAndWatch`.
+pub fn writeManifestAndWatch(s: *Step, man: *Build.Cache.Manifest) !void {
+    try writeManifest(s, man);
+    try setWatchInputsFromManifest(s, man);
+}
+
+fn setWatchInputsFromManifest(s: *Step, man: *Build.Cache.Manifest) !void {
+    const arena = s.owner.allocator;
+    const prefixes = man.cache.prefixes();
+    clearWatchInputs(s);
+    for (man.files.keys()) |file| {
+        // The file path data is freed when the cache manifest is cleaned up at the end of `make`.
+        const sub_path = try arena.dupe(u8, file.prefixed_path.sub_path);
+        try addWatchInputFromPath(s, .{
+            .root_dir = prefixes[file.prefixed_path.prefix],
+            .sub_path = std.fs.path.dirname(sub_path) orelse "",
+        }, std.fs.path.basename(sub_path));
+    }
+}
+
 /// For steps that have a single input that never changes when re-running `make`.
 pub fn singleUnchangingWatchInput(step: *Step, lazy_path: Build.LazyPath) Allocator.Error!void {
     if (!step.inputs.populated()) try step.addWatchInput(lazy_path);