Commit 2654d0c668

Andrew Kelley <andrew@ziglang.org>
2023-02-06 00:45:51
std.Build.RunStep: introduce addOutputFileArg API
This provides file path as a command line argument to the command being run, and returns a FileSource which can be used as inputs to other APIs throughout the build system. Unfortunately, it is implemented by pooping a ton of temporary files into zig-cache/tmp for the time being. I think one of the very next improvements to the build system should be moving the compiler's cache system to the standard library and using it in the build system. I had a look at the dependencies and it is already pretty untangled.
1 parent b061cc9
Changed files (1)
lib
std
lib/std/Build/RunStep.zig
@@ -39,6 +39,10 @@ expected_exit_code: ?u8 = 0,
 
 /// Print the command before running it
 print: bool,
+/// Controls whether execution is skipped if the output file is up-to-date.
+/// The default is to always run if there is no output file, and to skip
+/// running if all output files are up-to-date.
+condition: enum { output_outdated, always } = .output_outdated,
 
 pub const StdIoAction = union(enum) {
     inherit,
@@ -51,6 +55,12 @@ pub const Arg = union(enum) {
     artifact: *CompileStep,
     file_source: std.Build.FileSource,
     bytes: []u8,
+    output: Output,
+
+    pub const Output = struct {
+        generated_file: *std.Build.GeneratedFile,
+        basename: []const u8,
+    };
 };
 
 pub fn create(builder: *std.Build, name: []const u8) *RunStep {
@@ -71,6 +81,20 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {
     self.step.dependOn(&artifact.step);
 }
 
+/// This provides file path as a command line argument to the command being
+/// run, and returns a FileSource which can be used as inputs to other APIs
+/// throughout the build system.
+pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource {
+    const generated_file = rs.builder.allocator.create(std.Build.GeneratedFile) catch @panic("OOM");
+    generated_file.* = .{ .step = &rs.step };
+    rs.argv.append(.{ .output = .{
+        .generated_file = generated_file,
+        .basename = rs.builder.dupe(basename),
+    } }) catch @panic("OOM");
+
+    return .{ .generated = generated_file };
+}
+
 pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void {
     self.argv.append(Arg{
         .file_source = file_source.dupe(self.builder),
@@ -159,10 +183,24 @@ fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo {
     };
 }
 
+fn needOutputCheck(self: RunStep) bool {
+    switch (self.condition) {
+        .always => return false,
+        .output_outdated => {
+            for (self.argv.items) |arg| switch (arg) {
+                .output => return true,
+                else => continue,
+            };
+            return false;
+        },
+    }
+}
+
 fn make(step: *Step) !void {
     const self = @fieldParentPtr(RunStep, "step", step);
 
     var argv_list = ArrayList([]const u8).init(self.builder.allocator);
+
     for (self.argv.items) |arg| {
         switch (arg) {
             .bytes => |bytes| try argv_list.append(bytes),
@@ -172,9 +210,34 @@ fn make(step: *Step) !void {
                     // On Windows we don't have rpaths so we have to add .dll search paths to PATH
                     self.addPathForDynLibs(artifact);
                 }
-                const executable_path = artifact.installed_path orelse artifact.getOutputSource().getPath(self.builder);
+                const executable_path = artifact.installed_path orelse
+                    artifact.getOutputSource().getPath(self.builder);
                 try argv_list.append(executable_path);
             },
+            .output => |output| {
+                // TODO: until the cache system is brought into the build system,
+                // we use a temporary directory here for each run.
+                var digest: [16]u8 = undefined;
+                std.crypto.random.bytes(&digest);
+                var hash_basename: [digest.len * 2]u8 = undefined;
+                _ = std.fmt.bufPrint(
+                    &hash_basename,
+                    "{s}",
+                    .{std.fmt.fmtSliceHexLower(&digest)},
+                ) catch unreachable;
+
+                const output_path = try fs.path.join(self.builder.allocator, &[_][]const u8{
+                    self.builder.cache_root, "tmp", &hash_basename, output.basename,
+                });
+                const output_dir = fs.path.dirname(output_path).?;
+                fs.cwd().makePath(output_dir) catch |err| {
+                    std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) });
+                    return err;
+                };
+
+                output.generated_file.path = output_path;
+                try argv_list.append(output_path);
+            },
         }
     }