authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-05-04 15:12:24-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-05-05 15:58:08-04:00
logdee9f82f69db0d034251b844e0bc4083a1b25fdd
tree14adb7ca55844ef04872501174cd006812edbf43
parente3424332d3fa1264e1f6861b76bb0d1b2996728d

Run: add output directory arguments

This allows running commands that take an output directory argument. The main thing that was needed for this feature was generated file subpaths, to allow access to the files in a generated directory. Additionally, a minor change was required to so that the correct directory is created for output directory args.

12 files changed, 274 insertions(+), 121 deletions(-)

lib/std/Build.zig+87-80
...@@ -2131,28 +2131,23 @@ test dirnameAllowEmpty {...@@ -2131,28 +2131,23 @@ test dirnameAllowEmpty {
21312131
2132/// A reference to an existing or future path.2132/// A reference to an existing or future path.
2133pub const LazyPath = union(enum) {2133pub const LazyPath = union(enum) {
2134 /// Deprecated; use the `path` function instead.
2135 path: []const u8,
2136
2137 /// A source file path relative to build root.2134 /// A source file path relative to build root.
2138 src_path: struct {2135 src_path: struct {
2139 owner: *std.Build,2136 owner: *std.Build,
2140 sub_path: []const u8,2137 sub_path: []const u8,
2141 },2138 },
21422139
2143 /// A file that is generated by an interface. Those files usually are2140 generated: struct {
2144 /// not available until built by a build step.2141 file: *const GeneratedFile,
2145 generated: *const GeneratedFile,
2146
2147 /// One of the parent directories of a file generated by an interface.
2148 /// The path is not available until built by a build step.
2149 generated_dirname: struct {
2150 generated: *const GeneratedFile,
21512142
2152 /// The number of parent directories to go up.2143 /// The number of parent directories to go up.
2153 /// 0 means the directory of the generated file,2144 /// 0 means the generated file itself.
2154 /// 1 means the parent of that directory, and so on.2145 /// 1 means the directory of the generated file.
2155 up: usize,2146 /// 2 means the parent of that directory, and so on.
2147 up: usize = 0,
2148
2149 /// Applied after `up`.
2150 sub_path: []const u8 = "",
2156 },2151 },
21572152
2158 /// An absolute path or a path relative to the current working directory of2153 /// An absolute path or a path relative to the current working directory of
...@@ -2168,12 +2163,6 @@ pub const LazyPath = union(enum) {...@@ -2168,12 +2163,6 @@ pub const LazyPath = union(enum) {
2168 sub_path: []const u8,2163 sub_path: []const u8,
2169 },2164 },
21702165
2171 /// Deprecated. Call `path` instead.
2172 pub fn relative(sub_path: []const u8) LazyPath {
2173 std.log.warn("deprecated. call std.Build.path instead", .{});
2174 return .{ .path = sub_path };
2175 }
2176
2177 /// Returns a lazy path referring to the directory containing this path.2166 /// Returns a lazy path referring to the directory containing this path.
2178 ///2167 ///
2179 /// The dirname is not allowed to escape the logical root for underlying path.2168 /// The dirname is not allowed to escape the logical root for underlying path.
...@@ -2183,8 +2172,6 @@ pub const LazyPath = union(enum) {...@@ -2183,8 +2172,6 @@ pub const LazyPath = union(enum) {
2183 /// the dirname is not allowed to traverse outside of zig-cache.2172 /// the dirname is not allowed to traverse outside of zig-cache.
2184 pub fn dirname(lazy_path: LazyPath) LazyPath {2173 pub fn dirname(lazy_path: LazyPath) LazyPath {
2185 return switch (lazy_path) {2174 return switch (lazy_path) {
2186 .generated => |gen| .{ .generated_dirname = .{ .generated = gen, .up = 0 } },
2187 .generated_dirname => |gen| .{ .generated_dirname = .{ .generated = gen.generated, .up = gen.up + 1 } },
2188 .src_path => |sp| .{ .src_path = .{2175 .src_path => |sp| .{ .src_path = .{
2189 .owner = sp.owner,2176 .owner = sp.owner,
2190 .sub_path = dirnameAllowEmpty(sp.sub_path) orelse {2177 .sub_path = dirnameAllowEmpty(sp.sub_path) orelse {
...@@ -2192,12 +2179,15 @@ pub const LazyPath = union(enum) {...@@ -2192,12 +2179,15 @@ pub const LazyPath = union(enum) {
2192 @panic("misconfigured build script");2179 @panic("misconfigured build script");
2193 },2180 },
2194 } },2181 } },
2195 .path => |sub_path| .{2182 .generated => |generated| .{ .generated = if (dirnameAllowEmpty(generated.sub_path)) |sub_dirname| .{
2196 .path = dirnameAllowEmpty(sub_path) orelse {2183 .file = generated.file,
2197 dumpBadDirnameHelp(null, null, "dirname() attempted to traverse outside the build root\n", .{}) catch {};2184 .up = generated.up,
2198 @panic("misconfigured build script");2185 .sub_path = sub_dirname,
2199 },2186 } else .{
2200 },2187 .file = generated.file,
2188 .up = generated.up + 1,
2189 .sub_path = "",
2190 } },
2201 .cwd_relative => |rel_path| .{2191 .cwd_relative => |rel_path| .{
2202 .cwd_relative = dirnameAllowEmpty(rel_path) orelse {2192 .cwd_relative = dirnameAllowEmpty(rel_path) orelse {
2203 // If we get null, it means one of two things:2193 // If we get null, it means one of two things:
...@@ -2234,14 +2224,34 @@ pub const LazyPath = union(enum) {...@@ -2234,14 +2224,34 @@ pub const LazyPath = union(enum) {
2234 };2224 };
2235 }2225 }
22362226
2227 pub fn path(lazy_path: LazyPath, b: *Build, sub_path: []const u8) LazyPath {
2228 return switch (lazy_path) {
2229 .src_path => |src| .{ .src_path = .{
2230 .owner = src.owner,
2231 .sub_path = b.pathResolve(&.{ src.sub_path, sub_path }),
2232 } },
2233 .generated => |gen| .{ .generated = .{
2234 .file = gen.file,
2235 .up = gen.up,
2236 .sub_path = b.pathResolve(&.{ gen.sub_path, sub_path }),
2237 } },
2238 .cwd_relative => |cwd_relative| .{
2239 .cwd_relative = b.pathResolve(&.{ cwd_relative, sub_path }),
2240 },
2241 .dependency => |dep| .{ .dependency = .{
2242 .dependency = dep.dependency,
2243 .sub_path = b.pathResolve(&.{ dep.sub_path, sub_path }),
2244 } },
2245 };
2246 }
2247
2237 /// Returns a string that can be shown to represent the file source.2248 /// Returns a string that can be shown to represent the file source.
2238 /// Either returns the path or `"generated"`.2249 /// Either returns the path, `"generated"`, or `"dependency"`.
2239 pub fn getDisplayName(lazy_path: LazyPath) []const u8 {2250 pub fn getDisplayName(lazy_path: LazyPath) []const u8 {
2240 return switch (lazy_path) {2251 return switch (lazy_path) {
2241 .src_path => |src_path| src_path.sub_path,2252 .src_path => |sp| sp.sub_path,
2242 .path, .cwd_relative => |sub_path| sub_path,2253 .cwd_relative => |p| p,
2243 .generated => "generated",2254 .generated => "generated",
2244 .generated_dirname => "generated",
2245 .dependency => "dependency",2255 .dependency => "dependency",
2246 };2256 };
2247 }2257 }
...@@ -2249,9 +2259,8 @@ pub const LazyPath = union(enum) {...@@ -2249,9 +2259,8 @@ pub const LazyPath = union(enum) {
2249 /// Adds dependencies this file source implies to the given step.2259 /// Adds dependencies this file source implies to the given step.
2250 pub fn addStepDependencies(lazy_path: LazyPath, other_step: *Step) void {2260 pub fn addStepDependencies(lazy_path: LazyPath, other_step: *Step) void {
2251 switch (lazy_path) {2261 switch (lazy_path) {
2252 .src_path, .path, .cwd_relative, .dependency => {},2262 .src_path, .cwd_relative, .dependency => {},
2253 .generated => |gen| other_step.dependOn(gen.step),2263 .generated => |gen| other_step.dependOn(gen.file.step),
2254 .generated_dirname => |gen| other_step.dependOn(gen.generated.step),
2255 }2264 }
2256 }2265 }
22572266
...@@ -2268,47 +2277,48 @@ pub const LazyPath = union(enum) {...@@ -2268,47 +2277,48 @@ pub const LazyPath = union(enum) {
2268 /// run that is asking for the path.2277 /// run that is asking for the path.
2269 pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {2278 pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
2270 switch (lazy_path) {2279 switch (lazy_path) {
2271 .path => |p| return src_builder.pathFromRoot(p),
2272 .src_path => |sp| return sp.owner.pathFromRoot(sp.sub_path),2280 .src_path => |sp| return sp.owner.pathFromRoot(sp.sub_path),
2273 .cwd_relative => |p| return src_builder.pathFromCwd(p),2281 .cwd_relative => |p| return src_builder.pathFromCwd(p),
2274 .generated => |gen| return gen.step.owner.pathFromRoot(gen.path orelse {2282 .generated => |gen| {
2275 std.debug.getStderrMutex().lock();2283 var file_path: []const u8 = gen.file.step.owner.pathFromRoot(gen.file.path orelse {
2276 const stderr = std.io.getStdErr();2284 std.debug.getStderrMutex().lock();
2277 dumpBadGetPathHelp(gen.step, stderr, src_builder, asking_step) catch {};2285 const stderr = std.io.getStdErr();
2278 @panic("misconfigured build script");2286 dumpBadGetPathHelp(gen.file.step, stderr, src_builder, asking_step) catch {};
2279 }),2287 std.debug.getStderrMutex().unlock();
2280 .generated_dirname => |gen| {2288 @panic("misconfigured build script");
2281 const cache_root_path = src_builder.cache_root.path orelse2289 });
2282 (src_builder.cache_root.join(src_builder.allocator, &.{"."}) catch @panic("OOM"));2290
22832291 if (gen.up > 0) {
2284 const gen_step = gen.generated.step;2292 const cache_root_path = src_builder.cache_root.path orelse
2285 var p = getPath2(LazyPath{ .generated = gen.generated }, src_builder, asking_step);2293 (src_builder.cache_root.join(src_builder.allocator, &.{"."}) catch @panic("OOM"));
2286 var i: usize = 0;2294
2287 while (i <= gen.up) : (i += 1) {2295 for (0..gen.up) |_| {
2288 // path is absolute.2296 if (mem.eql(u8, file_path, cache_root_path)) {
2289 // dirname will return null only if we're at root.2297 // If we hit the cache root and there's still more to go,
2290 // Typically, we'll stop well before that at the cache root.2298 // the script attempted to go too far.
2291 p = fs.path.dirname(p) orelse {2299 dumpBadDirnameHelp(gen.file.step, asking_step,
2292 dumpBadDirnameHelp(gen_step, asking_step,2300 \\dirname() attempted to traverse outside the cache root.
2293 \\dirname() reached root.2301 \\This is not allowed.
2294 \\No more directories left to go up.2302 \\
2295 \\2303 , .{}) catch {};
2296 , .{}) catch {};2304 @panic("misconfigured build script");
2297 @panic("misconfigured build script");2305 }
2298 };2306
22992307 // path is absolute.
2300 if (mem.eql(u8, p, cache_root_path) and i < gen.up) {2308 // dirname will return null only if we're at root.
2301 // If we hit the cache root and there's still more to go,2309 // Typically, we'll stop well before that at the cache root.
2302 // the script attempted to go too far.2310 file_path = fs.path.dirname(file_path) orelse {
2303 dumpBadDirnameHelp(gen_step, asking_step,2311 dumpBadDirnameHelp(gen.file.step, asking_step,
2304 \\dirname() attempted to traverse outside the cache root.2312 \\dirname() reached root.
2305 \\This is not allowed.2313 \\No more directories left to go up.
2306 \\2314 \\
2307 , .{}) catch {};2315 , .{}) catch {};
2308 @panic("misconfigured build script");2316 @panic("misconfigured build script");
2317 };
2309 }2318 }
2310 }2319 }
2311 return p;2320
2321 return src_builder.pathResolve(&.{ file_path, gen.sub_path });
2312 },2322 },
2313 .dependency => |dep| return dep.dependency.builder.pathFromRoot(dep.sub_path),2323 .dependency => |dep| return dep.dependency.builder.pathFromRoot(dep.sub_path),
2314 }2324 }
...@@ -2324,15 +2334,12 @@ pub const LazyPath = union(enum) {...@@ -2324,15 +2334,12 @@ pub const LazyPath = union(enum) {
2324 .owner = sp.owner,2334 .owner = sp.owner,
2325 .sub_path = sp.owner.dupePath(sp.sub_path),2335 .sub_path = sp.owner.dupePath(sp.sub_path),
2326 } },2336 } },
2327 .path => |p| .{ .path = b.dupePath(p) },
2328 .cwd_relative => |p| .{ .cwd_relative = b.dupePath(p) },2337 .cwd_relative => |p| .{ .cwd_relative = b.dupePath(p) },
2329 .generated => |gen| .{ .generated = gen },2338 .generated => |gen| .{ .generated = .{
2330 .generated_dirname => |gen| .{2339 .file = gen.file,
2331 .generated_dirname = .{2340 .up = gen.up,
2332 .generated = gen.generated,2341 .sub_path = b.dupePath(gen.sub_path),
2333 .up = gen.up,2342 } },
2334 },
2335 },
2336 .dependency => |dep| .{ .dependency = dep },2343 .dependency => |dep| .{ .dependency = dep },
2337 };2344 };
2338 }2345 }
lib/std/Build/Step/Compile.zig+2-4
...@@ -806,14 +806,12 @@ pub fn setLibCFile(compile: *Compile, libc_file: ?LazyPath) void {...@@ -806,14 +806,12 @@ pub fn setLibCFile(compile: *Compile, libc_file: ?LazyPath) void {
806}806}
807807
808fn getEmittedFileGeneric(compile: *Compile, output_file: *?*GeneratedFile) LazyPath {808fn getEmittedFileGeneric(compile: *Compile, output_file: *?*GeneratedFile) LazyPath {
809 if (output_file.*) |g| {809 if (output_file.*) |file| return .{ .generated = .{ .file = file } };
810 return .{ .generated = g };
811 }
812 const arena = compile.step.owner.allocator;810 const arena = compile.step.owner.allocator;
813 const generated_file = arena.create(GeneratedFile) catch @panic("OOM");811 const generated_file = arena.create(GeneratedFile) catch @panic("OOM");
814 generated_file.* = .{ .step = &compile.step };812 generated_file.* = .{ .step = &compile.step };
815 output_file.* = generated_file;813 output_file.* = generated_file;
816 return .{ .generated = generated_file };814 return .{ .generated = .{ .file = generated_file } };
817}815}
818816
819/// Returns the path to the directory that contains the emitted binary file.817/// Returns the path to the directory that contains the emitted binary file.
lib/std/Build/Step/ConfigHeader.zig+2-3
...@@ -59,8 +59,7 @@ pub fn create(owner: *std.Build, options: Options) *ConfigHeader {...@@ -59,8 +59,7 @@ pub fn create(owner: *std.Build, options: Options) *ConfigHeader {
59 if (options.style.getPath()) |s| default_include_path: {59 if (options.style.getPath()) |s| default_include_path: {
60 const sub_path = switch (s) {60 const sub_path = switch (s) {
61 .src_path => |sp| sp.sub_path,61 .src_path => |sp| sp.sub_path,
62 .path => |path| path,62 .generated => break :default_include_path,
63 .generated, .generated_dirname => break :default_include_path,
64 .cwd_relative => |sub_path| sub_path,63 .cwd_relative => |sub_path| sub_path,
65 .dependency => |dependency| dependency.sub_path,64 .dependency => |dependency| dependency.sub_path,
66 };65 };
...@@ -106,7 +105,7 @@ pub fn addValues(config_header: *ConfigHeader, values: anytype) void {...@@ -106,7 +105,7 @@ pub fn addValues(config_header: *ConfigHeader, values: anytype) void {
106}105}
107106
108pub fn getOutput(config_header: *ConfigHeader) std.Build.LazyPath {107pub fn getOutput(config_header: *ConfigHeader) std.Build.LazyPath {
109 return .{ .generated = &config_header.output_file };108 return .{ .generated = .{ .file = &config_header.output_file } };
110}109}
111110
112fn addValuesInner(config_header: *ConfigHeader, values: anytype) !void {111fn addValuesInner(config_header: *ConfigHeader, values: anytype) !void {
lib/std/Build/Step/ObjCopy.zig+2-2
...@@ -84,10 +84,10 @@ pub fn create(...@@ -84,10 +84,10 @@ pub fn create(
84pub const getOutputSource = getOutput;84pub const getOutputSource = getOutput;
8585
86pub fn getOutput(objcopy: *const ObjCopy) std.Build.LazyPath {86pub fn getOutput(objcopy: *const ObjCopy) std.Build.LazyPath {
87 return .{ .generated = &objcopy.output_file };87 return .{ .generated = .{ .file = &objcopy.output_file } };
88}88}
89pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath {89pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath {
90 return if (objcopy.output_file_debug) |*file| .{ .generated = file } else null;90 return if (objcopy.output_file_debug) |*file| .{ .generated = .{ .file = file } } else null;
91}91}
9292
93fn make(step: *Step, prog_node: *std.Progress.Node) !void {93fn make(step: *Step, prog_node: *std.Progress.Node) !void {
lib/std/Build/Step/Options.zig+1-1
...@@ -407,7 +407,7 @@ pub const getSource = getOutput;...@@ -407,7 +407,7 @@ pub const getSource = getOutput;
407/// Returns the main artifact of this Build Step which is a Zig source file407/// Returns the main artifact of this Build Step which is a Zig source file
408/// generated from the key-value pairs of the Options.408/// generated from the key-value pairs of the Options.
409pub fn getOutput(options: *Options) LazyPath {409pub fn getOutput(options: *Options) LazyPath {
410 return .{ .generated = &options.generated_file };410 return .{ .generated = .{ .file = &options.generated_file } };
411}411}
412412
413fn make(step: *Step, prog_node: *std.Progress.Node) !void {413fn make(step: *Step, prog_node: *std.Progress.Node) !void {
lib/std/Build/Step/Run.zig+113-26
...@@ -125,7 +125,8 @@ pub const Arg = union(enum) {...@@ -125,7 +125,8 @@ pub const Arg = union(enum) {
125 lazy_path: PrefixedLazyPath,125 lazy_path: PrefixedLazyPath,
126 directory_source: PrefixedLazyPath,126 directory_source: PrefixedLazyPath,
127 bytes: []u8,127 bytes: []u8,
128 output: *Output,128 output_file: *Output,
129 output_directory: *Output,
129};130};
130131
131pub const PrefixedLazyPath = struct {132pub const PrefixedLazyPath = struct {
...@@ -225,13 +226,13 @@ pub fn addPrefixedOutputFileArg(...@@ -225,13 +226,13 @@ pub fn addPrefixedOutputFileArg(
225 .basename = b.dupe(basename),226 .basename = b.dupe(basename),
226 .generated_file = .{ .step = &run.step },227 .generated_file = .{ .step = &run.step },
227 };228 };
228 run.argv.append(b.allocator, .{ .output = output }) catch @panic("OOM");229 run.argv.append(b.allocator, .{ .output_file = output }) catch @panic("OOM");
229230
230 if (run.rename_step_with_output_arg) {231 if (run.rename_step_with_output_arg) {
231 run.setName(b.fmt("{s} ({s})", .{ run.step.name, basename }));232 run.setName(b.fmt("{s} ({s})", .{ run.step.name, basename }));
232 }233 }
233234
234 return .{ .generated = &output.generated_file };235 return .{ .generated = .{ .file = &output.generated_file } };
235}236}
236237
237/// Appends an input file to the command line arguments.238/// Appends an input file to the command line arguments.
...@@ -270,6 +271,56 @@ pub fn addPrefixedFileArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath)...@@ -270,6 +271,56 @@ pub fn addPrefixedFileArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath)
270 lp.addStepDependencies(&run.step);271 lp.addStepDependencies(&run.step);
271}272}
272273
274/// Provides a directory path as a command line argument to the command being run.
275///
276/// Returns a `std.Build.LazyPath` which can be used as inputs to other APIs
277/// throughout the build system.
278///
279/// Related:
280/// * `addPrefixedOutputDirectoryArg` - same thing but prepends a string to the argument
281/// * `addDirectoryArg` - for input directories given to the child process
282pub fn addOutputDirectoryArg(run: *Run, basename: []const u8) std.Build.LazyPath {
283 return run.addPrefixedOutputDirectoryArg("", basename);
284}
285
286/// Provides a directory path as a command line argument to the command being run.
287/// Asserts `basename` is not empty.
288///
289/// For example, a prefix of "-o" and basename of "output_dir" will result in
290/// the child process seeing something like this: "-ozig-cache/.../output_dir"
291///
292/// The child process will see a single argument, regardless of whether the
293/// prefix or basename have spaces.
294///
295/// The returned `std.Build.LazyPath` can be used as inputs to other APIs
296/// throughout the build system.
297///
298/// Related:
299/// * `addOutputDirectoryArg` - same thing but without the prefix
300/// * `addDirectoryArg` - for input directories given to the child process
301pub fn addPrefixedOutputDirectoryArg(
302 run: *Run,
303 prefix: []const u8,
304 basename: []const u8,
305) std.Build.LazyPath {
306 if (basename.len == 0) @panic("basename must not be empty");
307 const b = run.step.owner;
308
309 const output = b.allocator.create(Output) catch @panic("OOM");
310 output.* = .{
311 .prefix = b.dupe(prefix),
312 .basename = b.dupe(basename),
313 .generated_file = .{ .step = &run.step },
314 };
315 run.argv.append(b.allocator, .{ .output_directory = output }) catch @panic("OOM");
316
317 if (run.rename_step_with_output_arg) {
318 run.setName(b.fmt("{s} ({s})", .{ run.step.name, basename }));
319 }
320
321 return .{ .generated = .{ .file = &output.generated_file } };
322}
323
273/// deprecated: use `addDirectoryArg`324/// deprecated: use `addDirectoryArg`
274pub const addDirectorySourceArg = addDirectoryArg;325pub const addDirectorySourceArg = addDirectoryArg;
275326
...@@ -314,9 +365,9 @@ pub fn addPrefixedDepFileOutputArg(run: *Run, prefix: []const u8, basename: []co...@@ -314,9 +365,9 @@ pub fn addPrefixedDepFileOutputArg(run: *Run, prefix: []const u8, basename: []co
314365
315 run.dep_output_file = dep_file;366 run.dep_output_file = dep_file;
316367
317 run.argv.append(b.allocator, .{ .output = dep_file }) catch @panic("OOM");368 run.argv.append(b.allocator, .{ .output_file = dep_file }) catch @panic("OOM");
318369
319 return .{ .generated = &dep_file.generated_file };370 return .{ .generated = .{ .file = &dep_file.generated_file } };
320}371}
321372
322pub fn addArg(run: *Run, arg: []const u8) void {373pub fn addArg(run: *Run, arg: []const u8) void {
...@@ -432,7 +483,7 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {...@@ -432,7 +483,7 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {
432pub fn captureStdErr(run: *Run) std.Build.LazyPath {483pub fn captureStdErr(run: *Run) std.Build.LazyPath {
433 assert(run.stdio != .inherit);484 assert(run.stdio != .inherit);
434485
435 if (run.captured_stderr) |output| return .{ .generated = &output.generated_file };486 if (run.captured_stderr) |output| return .{ .generated = .{ .file = &output.generated_file } };
436487
437 const output = run.step.owner.allocator.create(Output) catch @panic("OOM");488 const output = run.step.owner.allocator.create(Output) catch @panic("OOM");
438 output.* = .{489 output.* = .{
...@@ -441,13 +492,13 @@ pub fn captureStdErr(run: *Run) std.Build.LazyPath {...@@ -441,13 +492,13 @@ pub fn captureStdErr(run: *Run) std.Build.LazyPath {
441 .generated_file = .{ .step = &run.step },492 .generated_file = .{ .step = &run.step },
442 };493 };
443 run.captured_stderr = output;494 run.captured_stderr = output;
444 return .{ .generated = &output.generated_file };495 return .{ .generated = .{ .file = &output.generated_file } };
445}496}
446497
447pub fn captureStdOut(run: *Run) std.Build.LazyPath {498pub fn captureStdOut(run: *Run) std.Build.LazyPath {
448 assert(run.stdio != .inherit);499 assert(run.stdio != .inherit);
449500
450 if (run.captured_stdout) |output| return .{ .generated = &output.generated_file };501 if (run.captured_stdout) |output| return .{ .generated = .{ .file = &output.generated_file } };
451502
452 const output = run.step.owner.allocator.create(Output) catch @panic("OOM");503 const output = run.step.owner.allocator.create(Output) catch @panic("OOM");
453 output.* = .{504 output.* = .{
...@@ -456,7 +507,7 @@ pub fn captureStdOut(run: *Run) std.Build.LazyPath {...@@ -456,7 +507,7 @@ pub fn captureStdOut(run: *Run) std.Build.LazyPath {
456 .generated_file = .{ .step = &run.step },507 .generated_file = .{ .step = &run.step },
457 };508 };
458 run.captured_stdout = output;509 run.captured_stdout = output;
459 return .{ .generated = &output.generated_file };510 return .{ .generated = .{ .file = &output.generated_file } };
460}511}
461512
462/// Adds an additional input files that, when modified, indicates that this Run513/// Adds an additional input files that, when modified, indicates that this Run
...@@ -484,7 +535,7 @@ fn hasAnyOutputArgs(run: Run) bool {...@@ -484,7 +535,7 @@ fn hasAnyOutputArgs(run: Run) bool {
484 if (run.captured_stdout != null) return true;535 if (run.captured_stdout != null) return true;
485 if (run.captured_stderr != null) return true;536 if (run.captured_stderr != null) return true;
486 for (run.argv.items) |arg| switch (arg) {537 for (run.argv.items) |arg| switch (arg) {
487 .output => return true,538 .output_file, .output_directory => return true,
488 else => continue,539 else => continue,
489 };540 };
490 return false;541 return false;
...@@ -520,6 +571,7 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {...@@ -520,6 +571,7 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {
520571
521const IndexedOutput = struct {572const IndexedOutput = struct {
522 index: usize,573 index: usize,
574 tag: @typeInfo(Arg).Union.tag_type.?,
523 output: *Output,575 output: *Output,
524};576};
525fn make(step: *Step, prog_node: *std.Progress.Node) !void {577fn make(step: *Step, prog_node: *std.Progress.Node) !void {
...@@ -563,17 +615,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -563,17 +615,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
563615
564 _ = try man.addFile(file_path, null);616 _ = try man.addFile(file_path, null);
565 },617 },
566 .output => |output| {618 .output_file, .output_directory => |output| {
567 man.hash.addBytes(output.prefix);619 man.hash.addBytes(output.prefix);
568 man.hash.addBytes(output.basename);620 man.hash.addBytes(output.basename);
569 // Add a placeholder into the argument list because we need the621 // Add a placeholder into the argument list because we need the
570 // manifest hash to be updated with all arguments before the622 // manifest hash to be updated with all arguments before the
571 // object directory is computed.623 // object directory is computed.
572 try argv_list.append("");
573 try output_placeholders.append(.{624 try output_placeholders.append(.{
574 .index = argv_list.items.len - 1,625 .index = argv_list.items.len,
626 .tag = arg,
575 .output = output,627 .output = output,
576 });628 });
629 _ = try argv_list.addOne();
577 },630 },
578 }631 }
579 }632 }
...@@ -599,11 +652,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -599,11 +652,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
599652
600 hashStdIo(&man.hash, run.stdio);653 hashStdIo(&man.hash, run.stdio);
601654
602 if (has_side_effects) {
603 try runCommand(run, argv_list.items, has_side_effects, null, prog_node);
604 return;
605 }
606
607 for (run.extra_file_dependencies) |file_path| {655 for (run.extra_file_dependencies) |file_path| {
608 _ = try man.addFile(b.pathFromRoot(file_path), null);656 _ = try man.addFile(b.pathFromRoot(file_path), null);
609 }657 }
...@@ -611,7 +659,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -611,7 +659,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
611 _ = try man.addFile(lazy_path.getPath2(b, step), null);659 _ = try man.addFile(lazy_path.getPath2(b, step), null);
612 }660 }
613661
614 if (try step.cacheHit(&man)) {662 if (try step.cacheHit(&man) and !has_side_effects) {
615 // cache hit, skip running command663 // cache hit, skip running command
616 const digest = man.final();664 const digest = man.final();
617665
...@@ -628,13 +676,54 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -628,13 +676,54 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
628 return;676 return;
629 }677 }
630678
679 const dep_output_file = run.dep_output_file orelse {
680 // We already know the final output paths, use them directly.
681 const digest = man.final();
682
683 try populateGeneratedPaths(
684 arena,
685 output_placeholders.items,
686 run.captured_stdout,
687 run.captured_stderr,
688 b.cache_root,
689 &digest,
690 );
691
692 const output_dir_path = "o" ++ fs.path.sep_str ++ &digest;
693 for (output_placeholders.items) |placeholder| {
694 const output_sub_path = b.pathJoin(&.{ output_dir_path, placeholder.output.basename });
695 const output_sub_dir_path = switch (placeholder.tag) {
696 .output_file => fs.path.dirname(output_sub_path).?,
697 .output_directory => output_sub_path,
698 else => unreachable,
699 };
700 b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
701 return step.fail("unable to make path '{}{s}': {s}", .{
702 b.cache_root, output_sub_dir_path, @errorName(err),
703 });
704 };
705 const output_path = placeholder.output.generated_file.path.?;
706 argv_list.items[placeholder.index] = if (placeholder.output.prefix.len == 0)
707 output_path
708 else
709 b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });
710 }
711
712 return runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node);
713 };
714
715 // We do not know the final output paths yet, use temp paths to run the command.
631 const rand_int = std.crypto.random.int(u64);716 const rand_int = std.crypto.random.int(u64);
632 const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.Build.hex64(rand_int);717 const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.Build.hex64(rand_int);
633718
634 for (output_placeholders.items) |placeholder| {719 for (output_placeholders.items) |placeholder| {
635 const output_components = .{ tmp_dir_path, placeholder.output.basename };720 const output_components = .{ tmp_dir_path, placeholder.output.basename };
636 const output_sub_path = b.pathJoin(&output_components);721 const output_sub_path = b.pathJoin(&output_components);
637 const output_sub_dir_path = fs.path.dirname(output_sub_path).?;722 const output_sub_dir_path = switch (placeholder.tag) {
723 .output_file => fs.path.dirname(output_sub_path).?,
724 .output_directory => output_sub_path,
725 else => unreachable,
726 };
638 b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {727 b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
639 return step.fail("unable to make path '{}{s}': {s}", .{728 return step.fail("unable to make path '{}{s}': {s}", .{
640 b.cache_root, output_sub_dir_path, @errorName(err),729 b.cache_root, output_sub_dir_path, @errorName(err),
...@@ -642,17 +731,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -642,17 +731,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
642 };731 };
643 const output_path = try b.cache_root.join(arena, &output_components);732 const output_path = try b.cache_root.join(arena, &output_components);
644 placeholder.output.generated_file.path = output_path;733 placeholder.output.generated_file.path = output_path;
645 const cli_arg = if (placeholder.output.prefix.len == 0)734 argv_list.items[placeholder.index] = if (placeholder.output.prefix.len == 0)
646 output_path735 output_path
647 else736 else
648 b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });737 b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });
649 argv_list.items[placeholder.index] = cli_arg;
650 }738 }
651739
652 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node);740 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node);
653741
654 if (run.dep_output_file) |dep_output_file|742 try man.addDepFilePost(std.fs.cwd(), dep_output_file.generated_file.getPath());
655 try man.addDepFilePost(std.fs.cwd(), dep_output_file.generated_file.getPath());
656743
657 const digest = man.final();744 const digest = man.final();
658745
...@@ -777,7 +864,7 @@ fn runCommand(...@@ -777,7 +864,7 @@ fn runCommand(
777 run: *Run,864 run: *Run,
778 argv: []const []const u8,865 argv: []const []const u8,
779 has_side_effects: bool,866 has_side_effects: bool,
780 tmp_dir_path: ?[]const u8,867 output_dir_path: []const u8,
781 prog_node: *std.Progress.Node,868 prog_node: *std.Progress.Node,
782) !void {869) !void {
783 const step = &run.step;870 const step = &run.step;
...@@ -950,7 +1037,7 @@ fn runCommand(...@@ -950,7 +1037,7 @@ fn runCommand(
950 },1037 },
951 }) |stream| {1038 }) |stream| {
952 if (stream.captured) |output| {1039 if (stream.captured) |output| {
953 const output_components = .{ tmp_dir_path.?, output.basename };1040 const output_components = .{ output_dir_path, output.basename };
954 const output_path = try b.cache_root.join(arena, &output_components);1041 const output_path = try b.cache_root.join(arena, &output_components);
955 output.generated_file.path = output_path;1042 output.generated_file.path = output_path;
9561043
lib/std/Build/Step/TranslateC.zig+1-1
...@@ -59,7 +59,7 @@ pub const AddExecutableOptions = struct {...@@ -59,7 +59,7 @@ pub const AddExecutableOptions = struct {
59};59};
6060
61pub fn getOutput(translate_c: *TranslateC) std.Build.LazyPath {61pub fn getOutput(translate_c: *TranslateC) std.Build.LazyPath {
62 return .{ .generated = &translate_c.output_file };62 return .{ .generated = .{ .file = &translate_c.output_file } };
63}63}
6464
65/// Creates a step to build an executable from the translated source.65/// Creates a step to build an executable from the translated source.
lib/std/Build/Step/WriteFile.zig+3-3
...@@ -31,7 +31,7 @@ pub const File = struct {...@@ -31,7 +31,7 @@ pub const File = struct {
31 contents: Contents,31 contents: Contents,
3232
33 pub fn getPath(file: *File) std.Build.LazyPath {33 pub fn getPath(file: *File) std.Build.LazyPath {
34 return .{ .generated = &file.generated_file };34 return .{ .generated = .{ .file = &file.generated_file } };
35 }35 }
36};36};
3737
...@@ -58,7 +58,7 @@ pub const Directory = struct {...@@ -58,7 +58,7 @@ pub const Directory = struct {
58 };58 };
5959
60 pub fn getPath(dir: *Directory) std.Build.LazyPath {60 pub fn getPath(dir: *Directory) std.Build.LazyPath {
61 return .{ .generated = &dir.generated_dir };61 return .{ .generated = .{ .file = &dir.generated_dir } };
62 }62 }
63};63};
6464
...@@ -181,7 +181,7 @@ pub fn addBytesToSource(write_file: *WriteFile, bytes: []const u8, sub_path: []c...@@ -181,7 +181,7 @@ pub fn addBytesToSource(write_file: *WriteFile, bytes: []const u8, sub_path: []c
181/// Returns a `LazyPath` representing the base directory that contains all the181/// Returns a `LazyPath` representing the base directory that contains all the
182/// files from this `WriteFile`.182/// files from this `WriteFile`.
183pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath {183pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath {
184 return .{ .generated = &write_file.generated_directory };184 return .{ .generated = .{ .file = &write_file.generated_directory } };
185}185}
186186
187fn maybeUpdateName(write_file: *WriteFile) void {187fn maybeUpdateName(write_file: *WriteFile) void {
test/standalone/build.zig.zon+3
...@@ -164,6 +164,9 @@...@@ -164,6 +164,9 @@
164 .dependencyFromBuildZig = .{164 .dependencyFromBuildZig = .{
165 .path = "dependencyFromBuildZig",165 .path = "dependencyFromBuildZig",
166 },166 },
167 .run_output_paths = .{
168 .path = "run_output_paths",
169 },
167 },170 },
168 .paths = .{171 .paths = .{
169 "build.zig",172 "build.zig",
test/standalone/run_output_paths/build.zig created+40
...@@ -0,0 +1,40 @@
1const std = @import("std");
2
3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const target = b.standardTargetOptions(.{});
8 const optimize = b.standardOptimizeOption(.{});
9
10 const create_file_exe = b.addExecutable(.{
11 .name = "create_file",
12 .root_source_file = b.path("create_file.zig"),
13 .target = target,
14 .optimize = optimize,
15 });
16
17 const create_first = b.addRunArtifact(create_file_exe);
18 const first_dir = create_first.addOutputDirectoryArg("first");
19 create_first.addArg("hello1.txt");
20 test_step.dependOn(&b.addCheckFile(first_dir.path(b, "hello1.txt"), .{ .expected_matches = &.{
21 std.fs.path.sep_str ++
22 \\first
23 \\hello1.txt
24 \\Hello, world!
25 \\
26 ,
27 } }).step);
28
29 const create_second = b.addRunArtifact(create_file_exe);
30 const second_dir = create_second.addPrefixedOutputDirectoryArg("--dir=", "second");
31 create_second.addArg("hello2.txt");
32 test_step.dependOn(&b.addCheckFile(second_dir.path(b, "hello2.txt"), .{ .expected_matches = &.{
33 std.fs.path.sep_str ++
34 \\second
35 \\hello2.txt
36 \\Hello, world!
37 \\
38 ,
39 } }).step);
40}
test/standalone/run_output_paths/create_file.zig created+19
...@@ -0,0 +1,19 @@
1const std = @import("std");
2
3pub fn main() !void {
4 var args = try std.process.argsWithAllocator(std.heap.page_allocator);
5 _ = args.skip();
6 const dir_name = args.next().?;
7 const dir = try std.fs.cwd().openDir(if (std.mem.startsWith(u8, dir_name, "--dir="))
8 dir_name["--dir=".len..]
9 else
10 dir_name, .{});
11 const file_name = args.next().?;
12 const file = try dir.createFile(file_name, .{});
13 try file.writer().print(
14 \\{s}
15 \\{s}
16 \\Hello, world!
17 \\
18 , .{ dir_name, file_name });
19}
test/standalone/windows_resources/build.zig+1-1
...@@ -36,7 +36,7 @@ fn add(...@@ -36,7 +36,7 @@ fn add(
36 .file = b.path("res/zig.rc"),36 .file = b.path("res/zig.rc"),
37 .flags = &.{"/c65001"}, // UTF-8 code page37 .flags = &.{"/c65001"}, // UTF-8 code page
38 .include_paths = &.{38 .include_paths = &.{
39 .{ .generated = &generated_h_step.generated_directory },39 .{ .generated = .{ .file = &generated_h_step.generated_directory } },
40 },40 },
41 });41 });
42 exe.rc_includes = switch (rc_includes) {42 exe.rc_includes = switch (rc_includes) {