authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-05 06:02:25+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-05 06:02:25+01:00
log435cd6f129172733fdc4b9bd62abe3c94f91d583
tree8346c38b5f579ec0ddfda202806e5e70b6b497ba
parent8e091047b5a8fd2f8a74d56a7353f16fd66dff25
parentc8ecfad41a322260fae38a4115a94345ed9f94b7

Merge pull request 'std.Build: adjust temp files API' (#30683) from std.Build-temp into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30683

7 files changed, 232 insertions(+), 213 deletions(-)

lib/compiler/build_runner.zig+15-1
...@@ -82,7 +82,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -82,7 +82,7 @@ pub fn main(init: process.Init.Minimal) !void {
82 .arena = arena,82 .arena = arena,
83 .cache = .{83 .cache = .{
84 .io = io,84 .io = io,
85 .gpa = arena,85 .gpa = gpa,
86 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),86 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),
87 .cwd = try process.getCwdAlloc(single_threaded_arena.allocator()),87 .cwd = try process.getCwdAlloc(single_threaded_arena.allocator()),
88 },88 },
...@@ -784,6 +784,9 @@ fn runStepNames(...@@ -784,6 +784,9 @@ fn runStepNames(
784 var pending_count: usize = 0;784 var pending_count: usize = 0;
785 var total_compile_errors: usize = 0;785 var total_compile_errors: usize = 0;
786786
787 var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() });
788 defer cleanup_task.await(io);
789
787 for (step_stack.keys()) |s| {790 for (step_stack.keys()) |s| {
788 test_pass_count += s.test_results.passCount();791 test_pass_count += s.test_results.passCount();
789 test_skip_count += s.test_results.skip_count;792 test_skip_count += s.test_results.skip_count;
...@@ -1849,3 +1852,14 @@ fn initStdoutWriter(io: Io) *Writer {...@@ -1849,3 +1852,14 @@ fn initStdoutWriter(io: Io) *Writer {
1849 stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation);1852 stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation);
1850 return &stdout_writer_allocation.interface;1853 return &stdout_writer_allocation.interface;
1851}1854}
1855
1856fn cleanTmpFiles(io: Io, steps: []const *Step) void {
1857 for (steps) |step| {
1858 const wf = step.cast(std.Build.Step.WriteFile) orelse continue;
1859 if (wf.mode != .tmp) continue;
1860 const path = wf.generated_directory.path orelse continue;
1861 Io.Dir.cwd().deleteTree(io, path) catch |err| {
1862 std.log.warn("failed to delete {s}: {t}", .{ path, err });
1863 };
1864 }
1865}
lib/std/Build.zig+42-19
...@@ -111,6 +111,7 @@ pub const ReleaseMode = enum {...@@ -111,6 +111,7 @@ pub const ReleaseMode = enum {
111/// Settings that are here rather than in Build are not configurable per-package.111/// Settings that are here rather than in Build are not configurable per-package.
112pub const Graph = struct {112pub const Graph = struct {
113 io: Io,113 io: Io,
114 /// Process lifetime.
114 arena: Allocator,115 arena: Allocator,
115 system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty,116 system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty,
116 system_package_mode: bool = false,117 system_package_mode: bool = false,
...@@ -1057,6 +1058,38 @@ pub fn addNamedLazyPath(b: *Build, name: []const u8, lp: LazyPath) void {...@@ -1057,6 +1058,38 @@ pub fn addNamedLazyPath(b: *Build, name: []const u8, lp: LazyPath) void {
1057 b.named_lazy_paths.put(b.dupe(name), lp.dupe(b)) catch @panic("OOM");1058 b.named_lazy_paths.put(b.dupe(name), lp.dupe(b)) catch @panic("OOM");
1058}1059}
10591060
1061/// Creates a step for mutating files inside a temporary directory created lazily
1062/// and automatically cleaned up upon successful build.
1063///
1064/// The directory will be placed inside "tmp" rather than "o", and caching will
1065/// be skipped. During the `make` phase, the step will always do all the file
1066/// system operations, and on successful build completion, the dir will be
1067/// deleted along with all other tmp directories. The directory is therefore
1068/// eligible to be used for mutations by other steps.
1069///
1070/// See also:
1071/// * `addWriteFiles`
1072/// * `addMutateFiles`
1073pub fn addTempFiles(b: *Build) *Step.WriteFile {
1074 const wf = addWriteFiles(b);
1075 wf.mode = .tmp;
1076 return wf;
1077}
1078
1079/// Creates a step for mutating temporary directories created with `addTempFiles`.
1080///
1081/// Consider instead `addWriteFiles` which is for creating a cached directory
1082/// of files to operate on.
1083///
1084/// This should only be used with a `tmp_path` obtained via `addTempFiles` or
1085/// `tmpPath`.
1086pub fn addMutateFiles(b: *Build, tmp_path: LazyPath) *Step.WriteFile {
1087 const wf = addWriteFiles(b);
1088 wf.mode = .{ .mutate = tmp_path };
1089 tmp_path.addStepDependencies(&wf.step);
1090 return wf;
1091}
1092
1060pub fn addWriteFiles(b: *Build) *Step.WriteFile {1093pub fn addWriteFiles(b: *Build) *Step.WriteFile {
1061 return Step.WriteFile.create(b);1094 return Step.WriteFile.create(b);
1062}1095}
...@@ -1065,10 +1098,6 @@ pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles {...@@ -1065,10 +1098,6 @@ pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles {
1065 return Step.UpdateSourceFiles.create(b);1098 return Step.UpdateSourceFiles.create(b);
1066}1099}
10671100
1068pub fn addRemoveDirTree(b: *Build, dir_path: LazyPath) *Step.RemoveDir {
1069 return Step.RemoveDir.create(b, dir_path);
1070}
1071
1072pub fn addFail(b: *Build, error_msg: []const u8) *Step.Fail {1101pub fn addFail(b: *Build, error_msg: []const u8) *Step.Fail {
1073 return Step.Fail.create(b, error_msg);1102 return Step.Fail.create(b, error_msg);
1074}1103}
...@@ -2235,9 +2264,8 @@ pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void {...@@ -2235,9 +2264,8 @@ pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void {
2235/// A file that is generated by a build step.2264/// A file that is generated by a build step.
2236/// This struct is an interface that is meant to be used with `@fieldParentPtr` to implement the actual path logic.2265/// This struct is an interface that is meant to be used with `@fieldParentPtr` to implement the actual path logic.
2237pub const GeneratedFile = struct {2266pub const GeneratedFile = struct {
2238 /// The step that generates the file2267 /// The step that generates the file.
2239 step: *Step,2268 step: *Step,
2240
2241 /// The path to the generated file. Must be either absolute or relative to the build runner cwd.2269 /// The path to the generated file. Must be either absolute or relative to the build runner cwd.
2242 /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards.2270 /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards.
2243 path: ?[]const u8 = null,2271 path: ?[]const u8 = null,
...@@ -2321,9 +2349,11 @@ pub const LazyPath = union(enum) {...@@ -2321,9 +2349,11 @@ pub const LazyPath = union(enum) {
23212349
2322 /// An absolute path or a path relative to the current working directory of2350 /// An absolute path or a path relative to the current working directory of
2323 /// the build runner process.2351 /// the build runner process.
2352 ///
2324 /// This is uncommon but used for system environment paths such as `--zig-lib-dir` which2353 /// This is uncommon but used for system environment paths such as `--zig-lib-dir` which
2325 /// ignore the file system path of build.zig and instead are relative to the directory from2354 /// ignore the file system path of build.zig and instead are relative to the directory from
2326 /// which `zig build` was invoked.2355 /// which `zig build` was invoked.
2356 ///
2327 /// Use of this tag indicates a dependency on the host system.2357 /// Use of this tag indicates a dependency on the host system.
2328 cwd_relative: []const u8,2358 cwd_relative: []const u8,
23292359
...@@ -2646,19 +2676,12 @@ pub const InstallDir = union(enum) {...@@ -2646,19 +2676,12 @@ pub const InstallDir = union(enum) {
2646 }2676 }
2647};2677};
26482678
2649/// This function is intended to be called in the `configure` phase only.2679/// Creates a path leading to a directory inside "tmp" subdirectory of
2650/// It returns an absolute directory path, which is potentially going to be a2680/// `cache_root` which is created on demand and cleaned up by the build runner
2651/// source of API breakage in the future, so keep that in mind when using this2681/// upon success.
2652/// function.2682pub fn tmpPath(b: *Build) LazyPath {
2653pub fn makeTempPath(b: *Build) []const u8 {2683 const wf = b.addTempFiles();
2654 const io = b.graph.io;2684 return wf.getDirectory();
2655 const rand_int = std.crypto.random.int(u64);
2656 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
2657 const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM");
2658 b.cache_root.handle.createDirPath(io, tmp_dir_sub_path) catch |err| {
2659 std.debug.print("unable to make tmp path '{s}': {t}\n", .{ result_path, err });
2660 };
2661 return result_path;
2662}2685}
26632686
2664/// A pair of target query and fully resolved target.2687/// A pair of target query and fully resolved target.
lib/std/Build/Step.zig-3
...@@ -173,7 +173,6 @@ pub const Id = enum {...@@ -173,7 +173,6 @@ pub const Id = enum {
173 .install_artifact => InstallArtifact,173 .install_artifact => InstallArtifact,
174 .install_file => InstallFile,174 .install_file => InstallFile,
175 .install_dir => InstallDir,175 .install_dir => InstallDir,
176 .remove_dir => RemoveDir,
177 .fail => Fail,176 .fail => Fail,
178 .fmt => Fmt,177 .fmt => Fmt,
179 .translate_c => TranslateC,178 .translate_c => TranslateC,
...@@ -201,7 +200,6 @@ pub const InstallFile = @import("Step/InstallFile.zig");...@@ -201,7 +200,6 @@ pub const InstallFile = @import("Step/InstallFile.zig");
201pub const ObjCopy = @import("Step/ObjCopy.zig");200pub const ObjCopy = @import("Step/ObjCopy.zig");
202pub const Compile = @import("Step/Compile.zig");201pub const Compile = @import("Step/Compile.zig");
203pub const Options = @import("Step/Options.zig");202pub const Options = @import("Step/Options.zig");
204pub const RemoveDir = @import("Step/RemoveDir.zig");
205pub const Run = @import("Step/Run.zig");203pub const Run = @import("Step/Run.zig");
206pub const TranslateC = @import("Step/TranslateC.zig");204pub const TranslateC = @import("Step/TranslateC.zig");
207pub const WriteFile = @import("Step/WriteFile.zig");205pub const WriteFile = @import("Step/WriteFile.zig");
...@@ -1008,7 +1006,6 @@ test {...@@ -1008,7 +1006,6 @@ test {
1008 _ = ObjCopy;1006 _ = ObjCopy;
1009 _ = Compile;1007 _ = Compile;
1010 _ = Options;1008 _ = Options;
1011 _ = RemoveDir;
1012 _ = Run;1009 _ = Run;
1013 _ = TranslateC;1010 _ = TranslateC;
1014 _ = WriteFile;1011 _ = WriteFile;
lib/std/Build/Step/RemoveDir.zig deleted-45
...@@ -1,45 +0,0 @@
1const std = @import("std");
2const fs = std.fs;
3const Step = std.Build.Step;
4const RemoveDir = @This();
5const LazyPath = std.Build.LazyPath;
6
7pub const base_id: Step.Id = .remove_dir;
8
9step: Step,
10doomed_path: LazyPath,
11
12pub fn create(owner: *std.Build, doomed_path: LazyPath) *RemoveDir {
13 const remove_dir = owner.allocator.create(RemoveDir) catch @panic("OOM");
14 remove_dir.* = .{
15 .step = Step.init(.{
16 .id = base_id,
17 .name = owner.fmt("RemoveDir {s}", .{doomed_path.getDisplayName()}),
18 .owner = owner,
19 .makeFn = make,
20 }),
21 .doomed_path = doomed_path.dupe(owner),
22 };
23 return remove_dir;
24}
25
26fn make(step: *Step, options: Step.MakeOptions) !void {
27 _ = options;
28
29 const b = step.owner;
30 const io = b.graph.io;
31 const remove_dir: *RemoveDir = @fieldParentPtr("step", step);
32
33 step.clearWatchInputs();
34 try step.addWatchInput(remove_dir.doomed_path);
35
36 const full_doomed_path = remove_dir.doomed_path.getPath2(b, step);
37
38 b.build_root.handle.deleteTree(io, full_doomed_path) catch |err| {
39 if (b.build_root.path) |base| {
40 return step.fail("unable to recursively delete path '{s}/{s}': {t}", .{ base, full_doomed_path, err });
41 } else {
42 return step.fail("unable to recursively delete path '{s}': {t}", .{ full_doomed_path, err });
43 }
44 };
45}
lib/std/Build/Step/WriteFile.zig+142-84
...@@ -1,22 +1,42 @@...@@ -1,22 +1,42 @@
1//! WriteFile is used to create a directory in an appropriate location inside1//! WriteFile is used to create a directory in an appropriate location inside
2//! the local cache which has a set of files that have either been generated2//! the local cache which has a set of files that have either been generated
3//! during the build, or are copied from the source package.3//! during the build, or are copied from the source package.
4const WriteFile = @This();
5
4const std = @import("std");6const std = @import("std");
5const Io = std.Io;7const Io = std.Io;
8const Dir = std.Io.Dir;
6const Step = std.Build.Step;9const Step = std.Build.Step;
7const fs = std.fs;
8const ArrayList = std.ArrayList;10const ArrayList = std.ArrayList;
9const WriteFile = @This();11const assert = std.debug.assert;
1012
11step: Step,13step: Step,
1214
13// The elements here are pointers because we need stable pointers for the GeneratedFile field.15/// The elements here are pointers because we need stable pointers for the GeneratedFile field.
14files: std.ArrayList(File),16files: std.ArrayList(File),
15directories: std.ArrayList(Directory),17directories: std.ArrayList(Directory),
16generated_directory: std.Build.GeneratedFile,18generated_directory: std.Build.GeneratedFile,
19mode: Mode = .whole_cached,
1720
18pub const base_id: Step.Id = .write_file;21pub const base_id: Step.Id = .write_file;
1922
23pub const Mode = union(enum) {
24 /// Default mode. Integrates with the cache system. The directory should be
25 /// read-only during the make phase. Any different inputs result in
26 /// different "o" subdirectory.
27 whole_cached,
28 /// In this mode, the directory will be placed inside "tmp" rather than
29 /// "o", and caching will be skipped. During the `make` phase, the step
30 /// will always do all the file system operations, and on successful build
31 /// completion, the dir will be deleted along with all other tmp
32 /// directories. The directory is therefore eligible to be used for
33 /// mutations by other steps.
34 tmp,
35 /// The operations will not be performed against a freshly created
36 /// directory, but instead act against a temporary directory.
37 mutate: std.Build.LazyPath,
38};
39
20pub const File = struct {40pub const File = struct {
21 sub_path: []const u8,41 sub_path: []const u8,
22 contents: Contents,42 contents: Contents,
...@@ -175,115 +195,155 @@ fn maybeUpdateName(write_file: *WriteFile) void {...@@ -175,115 +195,155 @@ fn maybeUpdateName(write_file: *WriteFile) void {
175fn make(step: *Step, options: Step.MakeOptions) !void {195fn make(step: *Step, options: Step.MakeOptions) !void {
176 _ = options;196 _ = options;
177 const b = step.owner;197 const b = step.owner;
178 const io = b.graph.io;198 const graph = b.graph;
199 const io = graph.io;
179 const arena = b.allocator;200 const arena = b.allocator;
180 const gpa = arena;201 const gpa = graph.cache.gpa;
181 const write_file: *WriteFile = @fieldParentPtr("step", step);202 const write_file: *WriteFile = @fieldParentPtr("step", step);
182 step.clearWatchInputs();
183
184 // The cache is used here not really as a way to speed things up - because writing
185 // the data to a file would probably be very fast - but as a way to find a canonical
186 // location to put build artifacts.
187203
188 // If, for example, a hard-coded path was used as the location to put WriteFile204 const open_dir_cache = try arena.alloc(Io.Dir, write_file.directories.items.len);
189 // files, then two WriteFiles executing in parallel might clobber each other.205 var open_dirs_count: usize = 0;
206 defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]);
190207
191 var man = b.graph.cache.obtain();208 switch (write_file.mode) {
192 defer man.deinit();209 .whole_cached => {
210 step.clearWatchInputs();
193211
194 for (write_file.files.items) |file| {212 // The cache is used here not really as a way to speed things up - because writing
195 man.hash.addBytes(file.sub_path);213 // the data to a file would probably be very fast - but as a way to find a canonical
214 // location to put build artifacts.
196215
197 switch (file.contents) {216 // If, for example, a hard-coded path was used as the location to put WriteFile
198 .bytes => |bytes| {217 // files, then two WriteFiles executing in parallel might clobber each other.
199 man.hash.addBytes(bytes);
200 },
201 .copy => |lazy_path| {
202 const path = lazy_path.getPath3(b, step);
203 _ = try man.addFilePath(path, null);
204 try step.addWatchInput(lazy_path);
205 },
206 }
207 }
208218
209 const open_dir_cache = try arena.alloc(Io.Dir, write_file.directories.items.len);219 var man = b.graph.cache.obtain();
210 var open_dirs_count: usize = 0;220 defer man.deinit();
211 defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]);
212221
213 for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| {222 for (write_file.files.items) |file| {
214 man.hash.addBytes(dir.sub_path);223 man.hash.addBytes(file.sub_path);
215 for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext);
216 if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc);
217224
218 const need_derived_inputs = try step.addDirectoryWatchInput(dir.source);225 switch (file.contents) {
219 const src_dir_path = dir.source.getPath3(b, step);226 .bytes => |bytes| {
227 man.hash.addBytes(bytes);
228 },
229 .copy => |lazy_path| {
230 const path = lazy_path.getPath3(b, step);
231 _ = try man.addFilePath(path, null);
232 try step.addWatchInput(lazy_path);
233 },
234 }
235 }
220236
221 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {237 for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| {
222 return step.fail("unable to open source directory '{f}': {s}", .{238 man.hash.addBytes(dir.sub_path);
223 src_dir_path, @errorName(err),239 for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext);
224 });240 if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc);
225 };
226 open_dir_cache_elem.* = src_dir;
227 open_dirs_count += 1;
228241
229 var it = try src_dir.walk(gpa);242 const need_derived_inputs = try step.addDirectoryWatchInput(dir.source);
230 defer it.deinit();243 const src_dir_path = dir.source.getPath3(b, step);
231 while (try it.next(io)) |entry| {
232 if (!dir.options.pathIncluded(entry.path)) continue;
233244
234 switch (entry.kind) {245 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
235 .directory => {246 return step.fail("unable to open source directory '{f}': {s}", .{
236 if (need_derived_inputs) {247 src_dir_path, @errorName(err),
237 const entry_path = try src_dir_path.join(arena, entry.path);248 });
238 try step.addDirectoryWatchInputFromPath(entry_path);249 };
250 open_dir_cache_elem.* = src_dir;
251 open_dirs_count += 1;
252
253 var it = try src_dir.walk(gpa);
254 defer it.deinit();
255 while (try it.next(io)) |entry| {
256 if (!dir.options.pathIncluded(entry.path)) continue;
257
258 switch (entry.kind) {
259 .directory => {
260 if (need_derived_inputs) {
261 const entry_path = try src_dir_path.join(arena, entry.path);
262 try step.addDirectoryWatchInputFromPath(entry_path);
263 }
264 },
265 .file => {
266 const entry_path = try src_dir_path.join(arena, entry.path);
267 _ = try man.addFilePath(entry_path, null);
268 },
269 else => continue,
239 }270 }
240 },271 }
241 .file => {
242 const entry_path = try src_dir_path.join(arena, entry.path);
243 _ = try man.addFilePath(entry_path, null);
244 },
245 else => continue,
246 }272 }
247 }
248 }
249273
250 if (try step.cacheHit(&man)) {274 if (try step.cacheHit(&man)) {
251 const digest = man.final();275 const digest = man.final();
252 write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest });276 write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest });
253 step.result_cached = true;277 assert(step.result_cached);
254 return;278 return;
255 }279 }
280
281 const digest = man.final();
282 const cache_path = "o" ++ Dir.path.sep_str ++ digest;
283
284 write_file.generated_directory.path = try b.cache_root.join(arena, &.{cache_path});
285
286 try operate(write_file, open_dir_cache, .{
287 .root_dir = b.cache_root,
288 .sub_path = cache_path,
289 });
290
291 try step.writeManifest(&man);
292 },
293 .tmp => {
294 step.result_cached = false;
295
296 const rand_int = std.crypto.random.int(u64);
297 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
256298
257 const digest = man.final();299 write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path});
258 const cache_path = "o" ++ fs.path.sep_str ++ digest;300
301 try operate(write_file, open_dir_cache, .{
302 .root_dir = b.cache_root,
303 .sub_path = tmp_dir_sub_path,
304 });
305 },
306 .mutate => |lp| {
307 step.result_cached = false;
308 const root_path = try lp.getPath4(b, step);
309 write_file.generated_directory.path = try root_path.toString(arena);
310 try operate(write_file, open_dir_cache, root_path);
311 },
312 }
313}
259314
260 write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest });315fn operate(write_file: *WriteFile, open_dir_cache: []const Io.Dir, root_path: std.Build.Cache.Path) !void {
316 const step = &write_file.step;
317 const b = step.owner;
318 const io = b.graph.io;
319 const gpa = b.graph.cache.gpa;
320 const arena = b.allocator;
261321
262 var cache_dir = b.cache_root.handle.createDirPathOpen(io, cache_path, .{}) catch |err|322 var cache_dir = root_path.root_dir.handle.createDirPathOpen(io, root_path.sub_path, .{}) catch |err|
263 return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, cache_path, err });323 return step.fail("unable to make path {f}: {t}", .{ root_path, err });
264 defer cache_dir.close(io);324 defer cache_dir.close(io);
265325
266 for (write_file.files.items) |file| {326 for (write_file.files.items) |file| {
267 if (fs.path.dirname(file.sub_path)) |dirname| {327 if (Dir.path.dirname(file.sub_path)) |dirname| {
268 cache_dir.createDirPath(io, dirname) catch |err| {328 cache_dir.createDirPath(io, dirname) catch |err| {
269 return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{329 return step.fail("unable to make path '{f}{c}{s}': {t}", .{
270 b.cache_root, cache_path, fs.path.sep, dirname, err,330 root_path, Dir.path.sep, dirname, err,
271 });331 });
272 };332 };
273 }333 }
274 switch (file.contents) {334 switch (file.contents) {
275 .bytes => |bytes| {335 .bytes => |bytes| {
276 cache_dir.writeFile(io, .{ .sub_path = file.sub_path, .data = bytes }) catch |err| {336 cache_dir.writeFile(io, .{ .sub_path = file.sub_path, .data = bytes }) catch |err| {
277 return step.fail("unable to write file '{f}{s}{c}{s}': {t}", .{337 return step.fail("unable to write file '{f}{c}{s}': {t}", .{
278 b.cache_root, cache_path, fs.path.sep, file.sub_path, err,338 root_path, Dir.path.sep, file.sub_path, err,
279 });339 });
280 };340 };
281 },341 },
282 .copy => |file_source| {342 .copy => |file_source| {
283 const source_path = file_source.getPath2(b, step);343 const source_path = file_source.getPath2(b, step);
284 const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir, file.sub_path, .{}) catch |err| {344 const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir, file.sub_path, .{}) catch |err| {
285 return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {t}", .{345 return step.fail("unable to update file from '{s}' to '{f}{c}{s}': {t}", .{
286 source_path, b.cache_root, cache_path, fs.path.sep, file.sub_path, err,346 source_path, root_path, Dir.path.sep, file.sub_path, err,
287 });347 });
288 };348 };
289 // At this point we already will mark the step as a cache miss.349 // At this point we already will mark the step as a cache miss.
...@@ -301,8 +361,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -301,8 +361,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
301361
302 if (dest_dirname.len != 0) {362 if (dest_dirname.len != 0) {
303 cache_dir.createDirPath(io, dest_dirname) catch |err| {363 cache_dir.createDirPath(io, dest_dirname) catch |err| {
304 return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{364 return step.fail("unable to make path '{f}{c}{s}': {t}", .{
305 b.cache_root, cache_path, fs.path.sep, dest_dirname, @errorName(err),365 root_path, Dir.path.sep, dest_dirname, err,
306 });366 });
307 };367 };
308 }368 }
...@@ -325,8 +385,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -325,8 +385,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
325 dest_path,385 dest_path,
326 .{},386 .{},
327 ) catch |err| {387 ) catch |err| {
328 return step.fail("unable to update file from '{f}' to '{f}{s}{c}{s}': {s}", .{388 return step.fail("unable to update file from '{f}' to '{f}{c}{s}': {t}", .{
329 src_entry_path, b.cache_root, cache_path, fs.path.sep, dest_path, @errorName(err),389 src_entry_path, root_path, Dir.path.sep, dest_path, err,
330 });390 });
331 };391 };
332 _ = prev_status;392 _ = prev_status;
...@@ -335,6 +395,4 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -335,6 +395,4 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
335 }395 }
336 }396 }
337 }397 }
338
339 try step.writeManifest(&man);
340}398}
test/standalone/dirname/build.zig+3-13
...@@ -58,19 +58,9 @@ pub fn build(b: *std.Build) void {...@@ -58,19 +58,9 @@ pub fn build(b: *std.Build) void {
58 );58 );
5959
60 // Absolute path:60 // Absolute path:
61 const abs_path = setup_abspath: {61 const write_files = b.addWriteFiles();
62 // TODO this is a bad pattern, don't do this62 _ = write_files.add("foo.txt", "");
63 const io = b.graph.io;63 const abs_path = write_files.getDirectory();
64 const temp_dir = b.makeTempPath();
65
66 var dir = std.Io.Dir.cwd().openDir(io, temp_dir, .{}) catch @panic("failed to open temp dir");
67 defer dir.close(io);
68
69 var file = dir.createFile(io, "foo.txt", .{}) catch @panic("failed to create file");
70 file.close(io);
71
72 break :setup_abspath std.Build.LazyPath{ .cwd_relative = temp_dir };
73 };
74 addTestRun(test_step, exists_in, abs_path, &.{"foo.txt"});64 addTestRun(test_step, exists_in, abs_path, &.{"foo.txt"});
75}65}
7666
test/tests.zig+30-48
...@@ -2026,13 +2026,12 @@ pub fn addLinkTests(...@@ -2026,13 +2026,12 @@ pub fn addLinkTests(
2026pub fn addCliTests(b: *std.Build) *Step {2026pub fn addCliTests(b: *std.Build) *Step {
2027 const step = b.step("test-cli", "Test the command line interface");2027 const step = b.step("test-cli", "Test the command line interface");
2028 const s = std.fs.path.sep_str;2028 const s = std.fs.path.sep_str;
2029 const io = b.graph.io;
20302029
2031 {2030 {
2032 // Test `zig init`.2031 // Test `zig init`.
2033 const tmp_path = b.makeTempPath();2032 const tmp_path = b.tmpPath();
2034 const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init" });2033 const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init" });
2035 init_exe.setCwd(.{ .cwd_relative = tmp_path });2034 init_exe.setCwd(tmp_path);
2036 init_exe.setName("zig init");2035 init_exe.setName("zig init");
2037 init_exe.expectStdOutEqual("");2036 init_exe.expectStdOutEqual("");
2038 init_exe.expectStdErrEqual("info: created build.zig\n" ++2037 init_exe.expectStdErrEqual("info: created build.zig\n" ++
...@@ -2053,31 +2052,28 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2053,31 +2052,28 @@ pub fn addCliTests(b: *std.Build) *Step {
2053 run_bad.step.dependOn(&init_exe.step);2052 run_bad.step.dependOn(&init_exe.step);
20542053
2055 const run_test = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "test" });2054 const run_test = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "test" });
2056 run_test.setCwd(.{ .cwd_relative = tmp_path });2055 run_test.setCwd(tmp_path);
2057 run_test.setName("zig build test");2056 run_test.setName("zig build test");
2058 run_test.expectStdOutEqual("");2057 run_test.expectStdOutEqual("");
2059 run_test.step.dependOn(&init_exe.step);2058 run_test.step.dependOn(&init_exe.step);
20602059
2061 const run_run = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "run" });2060 const run_run = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "run" });
2062 run_run.setCwd(.{ .cwd_relative = tmp_path });2061 run_run.setCwd(tmp_path);
2063 run_run.setName("zig build run");2062 run_run.setName("zig build run");
2064 run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n");2063 run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n");
2065 run_run.expectStdErrMatch("All your codebase are belong to us.\n");2064 run_run.expectStdErrMatch("All your codebase are belong to us.\n");
2066 run_run.step.dependOn(&init_exe.step);2065 run_run.step.dependOn(&init_exe.step);
20672066
2068 const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path });2067 step.dependOn(&run_test.step);
2069 cleanup.step.dependOn(&run_test.step);2068 step.dependOn(&run_run.step);
2070 cleanup.step.dependOn(&run_run.step);2069 step.dependOn(&run_bad.step);
2071 cleanup.step.dependOn(&run_bad.step);
2072
2073 step.dependOn(&cleanup.step);
2074 }2070 }
20752071
2076 {2072 {
2077 // Test `zig init -m`.2073 // Test `zig init -m`.
2078 const tmp_path = b.makeTempPath();2074 const tmp_path = b.tmpPath();
2079 const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init", "-m" });2075 const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init", "-m" });
2080 init_exe.setCwd(.{ .cwd_relative = tmp_path });2076 init_exe.setCwd(tmp_path);
2081 init_exe.setName("zig init -m");2077 init_exe.setName("zig init -m");
2082 init_exe.expectStdOutEqual("");2078 init_exe.expectStdOutEqual("");
2083 init_exe.expectStdErrEqual("info: successfully populated 'build.zig.zon' and 'build.zig'\n");2079 init_exe.expectStdErrEqual("info: successfully populated 'build.zig.zon' and 'build.zig'\n");
...@@ -2085,7 +2081,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2085,7 +2081,7 @@ pub fn addCliTests(b: *std.Build) *Step {
20852081
2086 // Test Godbolt API2082 // Test Godbolt API
2087 if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {2083 if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {
2088 const tmp_path = b.makeTempPath();2084 const tmp_path = b.tmpPath();
20892085
2090 const example_zig = b.addWriteFiles().add("example.zig",2086 const example_zig = b.addWriteFiles().add("example.zig",
2091 \\// Type your code here, or load an example.2087 \\// Type your code here, or load an example.
...@@ -2101,13 +2097,9 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2101,13 +2097,9 @@ pub fn addCliTests(b: *std.Build) *Step {
2101 );2097 );
21022098
2103 // This is intended to be the exact CLI usage used by godbolt.org.2099 // This is intended to be the exact CLI usage used by godbolt.org.
2104 const run = b.addSystemCommand(&.{2100 const run = b.addSystemCommand(&.{ b.graph.zig_exe, "build-obj", "--cache-dir" });
2105 b.graph.zig_exe, "build-obj",2101 run.addDirectoryArg(tmp_path);
2106 "--cache-dir", tmp_path,2102 run.addArgs(&.{ "--name", "example", "-fno-emit-bin", "-fno-emit-h", "-fstrip", "-OReleaseFast" });
2107 "--name", "example",
2108 "-fno-emit-bin", "-fno-emit-h",
2109 "-fstrip", "-OReleaseFast",
2110 });
2111 run.addFileArg(example_zig);2103 run.addFileArg(example_zig);
2112 const example_s = run.addPrefixedOutputFileArg("-femit-asm=", "example.s");2104 const example_s = run.addPrefixedOutputFileArg("-femit-asm=", "example.s");
21132105
...@@ -2120,10 +2112,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2120,10 +2112,7 @@ pub fn addCliTests(b: *std.Build) *Step {
2120 });2112 });
2121 checkfile.setName("check godbolt.org CLI usage generating valid asm");2113 checkfile.setName("check godbolt.org CLI usage generating valid asm");
21222114
2123 const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path });2115 step.dependOn(&checkfile.step);
2124 cleanup.step.dependOn(&checkfile.step);
2125
2126 step.dependOn(&cleanup.step);
2127 }2116 }
21282117
2129 {2118 {
...@@ -2132,22 +2121,19 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2132,22 +2121,19 @@ pub fn addCliTests(b: *std.Build) *Step {
2132 // directory because this test will be mutating the files. The cache2121 // directory because this test will be mutating the files. The cache
2133 // system relies on cache directories being mutated only by their2122 // system relies on cache directories being mutated only by their
2134 // owners.2123 // owners.
2135 const tmp_path = b.makeTempPath();2124 const tmp_wf = b.addTempFiles();
2136 const unformatted_code = " // no reason for indent";2125 const unformatted_code = " // no reason for indent";
21372126
2138 var dir = std.Io.Dir.cwd().openDir(io, tmp_path, .{}) catch @panic("unhandled");2127 _ = tmp_wf.add("fmt1.zig", unformatted_code);
2139 defer dir.close(io);2128 _ = tmp_wf.add("fmt2.zig", unformatted_code);
2140 dir.writeFile(io, .{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled");2129 _ = tmp_wf.add("subdir/fmt3.zig", unformatted_code);
2141 dir.writeFile(io, .{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled");2130
2142 dir.createDir(io, "subdir", .default_dir) catch @panic("unhandled");2131 const tmp_path = tmp_wf.getDirectory();
2143 var subdir = dir.openDir(io, "subdir", .{}) catch @panic("unhandled");
2144 defer subdir.close(io);
2145 subdir.writeFile(io, .{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled");
21462132
2147 // Test zig fmt affecting only the appropriate files.2133 // Test zig fmt affecting only the appropriate files.
2148 const run1 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "fmt1.zig" });2134 const run1 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "fmt1.zig" });
2149 run1.setName("run zig fmt one file");2135 run1.setName("run zig fmt one file");
2150 run1.setCwd(.{ .cwd_relative = tmp_path });2136 run1.setCwd(tmp_path);
2151 run1.has_side_effects = true;2137 run1.has_side_effects = true;
2152 // stdout should be file path + \n2138 // stdout should be file path + \n
2153 run1.expectStdOutEqual("fmt1.zig\n");2139 run1.expectStdOutEqual("fmt1.zig\n");
...@@ -2155,7 +2141,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2155,7 +2141,7 @@ pub fn addCliTests(b: *std.Build) *Step {
2155 // Test excluding files and directories from a run2141 // Test excluding files and directories from a run
2156 const run2 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "subdir", "." });2142 const run2 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "subdir", "." });
2157 run2.setName("run zig fmt on directory with exclusions");2143 run2.setName("run zig fmt on directory with exclusions");
2158 run2.setCwd(.{ .cwd_relative = tmp_path });2144 run2.setCwd(tmp_path);
2159 run2.has_side_effects = true;2145 run2.has_side_effects = true;
2160 run2.expectStdOutEqual("");2146 run2.expectStdOutEqual("");
2161 run2.step.dependOn(&run1.step);2147 run2.step.dependOn(&run1.step);
...@@ -2163,7 +2149,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2163,7 +2149,7 @@ pub fn addCliTests(b: *std.Build) *Step {
2163 // Test excluding non-existent file2149 // Test excluding non-existent file
2164 const run3 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "nonexistent.zig", "." });2150 const run3 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "nonexistent.zig", "." });
2165 run3.setName("run zig fmt on directory with non-existent exclusion");2151 run3.setName("run zig fmt on directory with non-existent exclusion");
2166 run3.setCwd(.{ .cwd_relative = tmp_path });2152 run3.setCwd(tmp_path);
2167 run3.has_side_effects = true;2153 run3.has_side_effects = true;
2168 run3.expectStdOutEqual("." ++ s ++ "subdir" ++ s ++ "fmt3.zig\n");2154 run3.expectStdOutEqual("." ++ s ++ "subdir" ++ s ++ "fmt3.zig\n");
2169 run3.step.dependOn(&run2.step);2155 run3.step.dependOn(&run2.step);
...@@ -2171,7 +2157,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2171,7 +2157,7 @@ pub fn addCliTests(b: *std.Build) *Step {
2171 // running it on the dir, only the new file should be changed2157 // running it on the dir, only the new file should be changed
2172 const run4 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });2158 const run4 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });
2173 run4.setName("run zig fmt the directory");2159 run4.setName("run zig fmt the directory");
2174 run4.setCwd(.{ .cwd_relative = tmp_path });2160 run4.setCwd(tmp_path);
2175 run4.has_side_effects = true;2161 run4.has_side_effects = true;
2176 run4.expectStdOutEqual("." ++ s ++ "fmt2.zig\n");2162 run4.expectStdOutEqual("." ++ s ++ "fmt2.zig\n");
2177 run4.step.dependOn(&run3.step);2163 run4.step.dependOn(&run3.step);
...@@ -2179,37 +2165,33 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2179,37 +2165,33 @@ pub fn addCliTests(b: *std.Build) *Step {
2179 // both files have been formatted, nothing should change now2165 // both files have been formatted, nothing should change now
2180 const run5 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });2166 const run5 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });
2181 run5.setName("run zig fmt with nothing to do");2167 run5.setName("run zig fmt with nothing to do");
2182 run5.setCwd(.{ .cwd_relative = tmp_path });2168 run5.setCwd(tmp_path);
2183 run5.has_side_effects = true;2169 run5.has_side_effects = true;
2184 run5.expectStdOutEqual("");2170 run5.expectStdOutEqual("");
2185 run5.step.dependOn(&run4.step);2171 run5.step.dependOn(&run4.step);
21862172
2187 const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";2173 const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";
2188 const fmt6_path = b.pathJoin(&.{ tmp_path, "fmt6.zig" });2174 const write6 = b.addMutateFiles(tmp_path);
2189 const write6 = b.addUpdateSourceFiles();2175 const fmt6_path = write6.add("fmt6.zig", unformatted_code_utf16);
2190 write6.addBytesToSource(unformatted_code_utf16, fmt6_path);
2191 write6.step.dependOn(&run5.step);2176 write6.step.dependOn(&run5.step);
21922177
2193 // Test `zig fmt` handling UTF-16 decoding.2178 // Test `zig fmt` handling UTF-16 decoding.
2194 const run6 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });2179 const run6 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." });
2195 run6.setName("run zig fmt convert UTF-16 to UTF-8");2180 run6.setName("run zig fmt convert UTF-16 to UTF-8");
2196 run6.setCwd(.{ .cwd_relative = tmp_path });2181 run6.setCwd(tmp_path);
2197 run6.has_side_effects = true;2182 run6.has_side_effects = true;
2198 run6.expectStdOutEqual("." ++ s ++ "fmt6.zig\n");2183 run6.expectStdOutEqual("." ++ s ++ "fmt6.zig\n");
2199 run6.step.dependOn(&write6.step);2184 run6.step.dependOn(&write6.step);
22002185
2201 // TODO change this to an exact match2186 // TODO change this to an exact match
2202 const check6 = b.addCheckFile(.{ .cwd_relative = fmt6_path }, .{2187 const check6 = b.addCheckFile(fmt6_path, .{
2203 .expected_matches = &.{2188 .expected_matches = &.{
2204 "// no reason",2189 "// no reason",
2205 },2190 },
2206 });2191 });
2207 check6.step.dependOn(&run6.step);2192 check6.step.dependOn(&run6.step);
22082193
2209 const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path });2194 step.dependOn(&check6.step);
2210 cleanup.step.dependOn(&check6.step);
2211
2212 step.dependOn(&cleanup.step);
2213 }2195 }
22142196
2215 {2197 {