authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 17:23:45-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 17:23:45-08:00
logb4dbe483a720f3e06559e659a410152511087395
tree786b85f7c6237896c59dc70bbae95a46387f491b
parente3b7cad81e483dc79716a388676f39c483589642

std.Build: adjust temp files API

Remove the RemoveDir step with no replacement. This step had no valid purpose. Mutating source files? That should be done with UpdateSourceFiles step. Deleting temporary directories? That required creating the tmp directories in the configure phase which is broken. Deleting cached artifacts? That's going to cause problems. Similarly, remove the `Build.makeTempPath` function. This was used to create a temporary path in the configure place which, again, is the wrong place to do it. Instead, the WriteFile step has been updated with more functionality: tmp mode: In this mode, the directory will be placed inside "tmp" rather than "o", and caching will be skipped. During the `make` phase, the step will always do all the file system operations, and on successful build completion, the dir will be deleted along with all other tmp directories. The directory is therefore eligible to be used for mutations by other steps. `Build.addTempFiles` is introduced to initialize a WriteFile step with this mode. mutate mode: The operations will not be performed against a freshly created directory, but instead act against a temporary directory. `Build.addMutateFiles` is introduced to initialize a WriteFile step with this mode. `Build.tmpPath` is introduced, which is a shortcut for `Build.addTempFiles` followed by `WriteFile.getDirectory`. * give Cache a gpa rather than arena because that's what it asks for

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

lib/compiler/build_runner.zig+1-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 },
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 {