diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 82fc696618..395ae76426 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -82,7 +82,7 @@ pub fn main(init: process.Init.Minimal) !void { .arena = arena, .cache = .{ .io = io, - .gpa = arena, + .gpa = gpa, .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), .cwd = try process.getCwdAlloc(single_threaded_arena.allocator()), }, diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 1724025bea..242949605c 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -111,6 +111,7 @@ pub const ReleaseMode = enum { /// Settings that are here rather than in Build are not configurable per-package. pub const Graph = struct { io: Io, + /// Process lifetime. arena: Allocator, system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty, system_package_mode: bool = false, @@ -1057,6 +1058,38 @@ pub fn addNamedLazyPath(b: *Build, name: []const u8, lp: LazyPath) void { b.named_lazy_paths.put(b.dupe(name), lp.dupe(b)) catch @panic("OOM"); } +/// Creates a step for mutating files inside a temporary directory created lazily +/// and automatically cleaned up upon successful build. +/// +/// 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. +/// +/// See also: +/// * `addWriteFiles` +/// * `addMutateFiles` +pub fn addTempFiles(b: *Build) *Step.WriteFile { + const wf = addWriteFiles(b); + wf.mode = .tmp; + return wf; +} + +/// Creates a step for mutating temporary directories created with `addTempFiles`. +/// +/// Consider instead `addWriteFiles` which is for creating a cached directory +/// of files to operate on. +/// +/// This should only be used with a `tmp_path` obtained via `addTempFiles` or +/// `tmpPath`. +pub fn addMutateFiles(b: *Build, tmp_path: LazyPath) *Step.WriteFile { + const wf = addWriteFiles(b); + wf.mode = .{ .mutate = tmp_path }; + tmp_path.addStepDependencies(&wf.step); + return wf; +} + pub fn addWriteFiles(b: *Build) *Step.WriteFile { return Step.WriteFile.create(b); } @@ -1065,10 +1098,6 @@ pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles { return Step.UpdateSourceFiles.create(b); } -pub fn addRemoveDirTree(b: *Build, dir_path: LazyPath) *Step.RemoveDir { - return Step.RemoveDir.create(b, dir_path); -} - pub fn addFail(b: *Build, error_msg: []const u8) *Step.Fail { return Step.Fail.create(b, error_msg); } @@ -2235,9 +2264,8 @@ pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void { /// A file that is generated by a build step. /// This struct is an interface that is meant to be used with `@fieldParentPtr` to implement the actual path logic. pub const GeneratedFile = struct { - /// The step that generates the file + /// The step that generates the file. step: *Step, - /// The path to the generated file. Must be either absolute or relative to the build runner cwd. /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards. path: ?[]const u8 = null, @@ -2321,9 +2349,11 @@ pub const LazyPath = union(enum) { /// An absolute path or a path relative to the current working directory of /// the build runner process. + /// /// This is uncommon but used for system environment paths such as `--zig-lib-dir` which /// ignore the file system path of build.zig and instead are relative to the directory from /// which `zig build` was invoked. + /// /// Use of this tag indicates a dependency on the host system. cwd_relative: []const u8, @@ -2646,19 +2676,12 @@ pub const InstallDir = union(enum) { } }; -/// This function is intended to be called in the `configure` phase only. -/// It returns an absolute directory path, which is potentially going to be a -/// source of API breakage in the future, so keep that in mind when using this -/// function. -pub fn makeTempPath(b: *Build) []const u8 { - const io = b.graph.io; - const rand_int = std.crypto.random.int(u64); - const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); - const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM"); - b.cache_root.handle.createDirPath(io, tmp_dir_sub_path) catch |err| { - std.debug.print("unable to make tmp path '{s}': {t}\n", .{ result_path, err }); - }; - return result_path; +/// Creates a path leading to a directory inside "tmp" subdirectory of +/// `cache_root` which is created on demand and cleaned up by the build runner +/// upon success. +pub fn tmpPath(b: *Build) LazyPath { + const wf = b.addTempFiles(); + return wf.getDirectory(); } /// A pair of target query and fully resolved target. diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 28141cebe5..007765c4d5 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -173,7 +173,6 @@ pub const Id = enum { .install_artifact => InstallArtifact, .install_file => InstallFile, .install_dir => InstallDir, - .remove_dir => RemoveDir, .fail => Fail, .fmt => Fmt, .translate_c => TranslateC, @@ -201,7 +200,6 @@ pub const InstallFile = @import("Step/InstallFile.zig"); pub const ObjCopy = @import("Step/ObjCopy.zig"); pub const Compile = @import("Step/Compile.zig"); pub const Options = @import("Step/Options.zig"); -pub const RemoveDir = @import("Step/RemoveDir.zig"); pub const Run = @import("Step/Run.zig"); pub const TranslateC = @import("Step/TranslateC.zig"); pub const WriteFile = @import("Step/WriteFile.zig"); @@ -1008,7 +1006,6 @@ test { _ = ObjCopy; _ = Compile; _ = Options; - _ = RemoveDir; _ = Run; _ = TranslateC; _ = WriteFile; diff --git a/lib/std/Build/Step/RemoveDir.zig b/lib/std/Build/Step/RemoveDir.zig deleted file mode 100644 index 6f933da9ee..0000000000 --- a/lib/std/Build/Step/RemoveDir.zig +++ /dev/null @@ -1,45 +0,0 @@ -const std = @import("std"); -const fs = std.fs; -const Step = std.Build.Step; -const RemoveDir = @This(); -const LazyPath = std.Build.LazyPath; - -pub const base_id: Step.Id = .remove_dir; - -step: Step, -doomed_path: LazyPath, - -pub fn create(owner: *std.Build, doomed_path: LazyPath) *RemoveDir { - const remove_dir = owner.allocator.create(RemoveDir) catch @panic("OOM"); - remove_dir.* = .{ - .step = Step.init(.{ - .id = base_id, - .name = owner.fmt("RemoveDir {s}", .{doomed_path.getDisplayName()}), - .owner = owner, - .makeFn = make, - }), - .doomed_path = doomed_path.dupe(owner), - }; - return remove_dir; -} - -fn make(step: *Step, options: Step.MakeOptions) !void { - _ = options; - - const b = step.owner; - const io = b.graph.io; - const remove_dir: *RemoveDir = @fieldParentPtr("step", step); - - step.clearWatchInputs(); - try step.addWatchInput(remove_dir.doomed_path); - - const full_doomed_path = remove_dir.doomed_path.getPath2(b, step); - - b.build_root.handle.deleteTree(io, full_doomed_path) catch |err| { - if (b.build_root.path) |base| { - return step.fail("unable to recursively delete path '{s}/{s}': {t}", .{ base, full_doomed_path, err }); - } else { - return step.fail("unable to recursively delete path '{s}': {t}", .{ full_doomed_path, err }); - } - }; -} diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 145c7f9bb3..d2c5587283 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -1,22 +1,42 @@ //! WriteFile is used to create a directory in an appropriate location inside //! the local cache which has a set of files that have either been generated //! during the build, or are copied from the source package. +const WriteFile = @This(); + const std = @import("std"); const Io = std.Io; +const Dir = std.Io.Dir; const Step = std.Build.Step; -const fs = std.fs; const ArrayList = std.ArrayList; -const WriteFile = @This(); +const assert = std.debug.assert; step: Step, -// The elements here are pointers because we need stable pointers for the GeneratedFile field. +/// The elements here are pointers because we need stable pointers for the GeneratedFile field. files: std.ArrayList(File), directories: std.ArrayList(Directory), generated_directory: std.Build.GeneratedFile, +mode: Mode = .whole_cached, pub const base_id: Step.Id = .write_file; +pub const Mode = union(enum) { + /// Default mode. Integrates with the cache system. The directory should be + /// read-only during the make phase. Any different inputs result in + /// different "o" subdirectory. + whole_cached, + /// 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. + tmp, + /// The operations will not be performed against a freshly created + /// directory, but instead act against a temporary directory. + mutate: std.Build.LazyPath, +}; + pub const File = struct { sub_path: []const u8, contents: Contents, @@ -175,115 +195,155 @@ fn maybeUpdateName(write_file: *WriteFile) void { fn make(step: *Step, options: Step.MakeOptions) !void { _ = options; const b = step.owner; - const io = b.graph.io; + const graph = b.graph; + const io = graph.io; const arena = b.allocator; - const gpa = arena; + const gpa = graph.cache.gpa; const write_file: *WriteFile = @fieldParentPtr("step", step); - step.clearWatchInputs(); - - // The cache is used here not really as a way to speed things up - because writing - // the data to a file would probably be very fast - but as a way to find a canonical - // location to put build artifacts. - - // If, for example, a hard-coded path was used as the location to put WriteFile - // files, then two WriteFiles executing in parallel might clobber each other. - - var man = b.graph.cache.obtain(); - defer man.deinit(); - - for (write_file.files.items) |file| { - man.hash.addBytes(file.sub_path); - - switch (file.contents) { - .bytes => |bytes| { - man.hash.addBytes(bytes); - }, - .copy => |lazy_path| { - const path = lazy_path.getPath3(b, step); - _ = try man.addFilePath(path, null); - try step.addWatchInput(lazy_path); - }, - } - } const open_dir_cache = try arena.alloc(Io.Dir, write_file.directories.items.len); var open_dirs_count: usize = 0; defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]); - for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| { - man.hash.addBytes(dir.sub_path); - for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext); - if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc); + switch (write_file.mode) { + .whole_cached => { + step.clearWatchInputs(); - const need_derived_inputs = try step.addDirectoryWatchInput(dir.source); - const src_dir_path = dir.source.getPath3(b, step); + // The cache is used here not really as a way to speed things up - because writing + // the data to a file would probably be very fast - but as a way to find a canonical + // location to put build artifacts. - var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { - return step.fail("unable to open source directory '{f}': {s}", .{ - src_dir_path, @errorName(err), - }); - }; - open_dir_cache_elem.* = src_dir; - open_dirs_count += 1; + // If, for example, a hard-coded path was used as the location to put WriteFile + // files, then two WriteFiles executing in parallel might clobber each other. - var it = try src_dir.walk(gpa); - defer it.deinit(); - while (try it.next(io)) |entry| { - if (!dir.options.pathIncluded(entry.path)) continue; + var man = b.graph.cache.obtain(); + defer man.deinit(); - switch (entry.kind) { - .directory => { - if (need_derived_inputs) { - const entry_path = try src_dir_path.join(arena, entry.path); - try step.addDirectoryWatchInputFromPath(entry_path); - } - }, - .file => { - const entry_path = try src_dir_path.join(arena, entry.path); - _ = try man.addFilePath(entry_path, null); - }, - else => continue, + for (write_file.files.items) |file| { + man.hash.addBytes(file.sub_path); + + switch (file.contents) { + .bytes => |bytes| { + man.hash.addBytes(bytes); + }, + .copy => |lazy_path| { + const path = lazy_path.getPath3(b, step); + _ = try man.addFilePath(path, null); + try step.addWatchInput(lazy_path); + }, + } } - } + + for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| { + man.hash.addBytes(dir.sub_path); + for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext); + if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc); + + const need_derived_inputs = try step.addDirectoryWatchInput(dir.source); + const src_dir_path = dir.source.getPath3(b, step); + + var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { + return step.fail("unable to open source directory '{f}': {s}", .{ + src_dir_path, @errorName(err), + }); + }; + open_dir_cache_elem.* = src_dir; + open_dirs_count += 1; + + var it = try src_dir.walk(gpa); + defer it.deinit(); + while (try it.next(io)) |entry| { + if (!dir.options.pathIncluded(entry.path)) continue; + + switch (entry.kind) { + .directory => { + if (need_derived_inputs) { + const entry_path = try src_dir_path.join(arena, entry.path); + try step.addDirectoryWatchInputFromPath(entry_path); + } + }, + .file => { + const entry_path = try src_dir_path.join(arena, entry.path); + _ = try man.addFilePath(entry_path, null); + }, + else => continue, + } + } + } + + if (try step.cacheHit(&man)) { + const digest = man.final(); + write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest }); + assert(step.result_cached); + return; + } + + const digest = man.final(); + const cache_path = "o" ++ Dir.path.sep_str ++ digest; + + write_file.generated_directory.path = try b.cache_root.join(arena, &.{cache_path}); + + try operate(write_file, open_dir_cache, .{ + .root_dir = b.cache_root, + .sub_path = cache_path, + }); + + try step.writeManifest(&man); + }, + .tmp => { + step.result_cached = false; + + const rand_int = std.crypto.random.int(u64); + const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); + + write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path}); + + try operate(write_file, open_dir_cache, .{ + .root_dir = b.cache_root, + .sub_path = tmp_dir_sub_path, + }); + }, + .mutate => |lp| { + step.result_cached = false; + const root_path = try lp.getPath4(b, step); + write_file.generated_directory.path = try root_path.toString(arena); + try operate(write_file, open_dir_cache, root_path); + }, } +} - if (try step.cacheHit(&man)) { - const digest = man.final(); - write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest }); - step.result_cached = true; - return; - } +fn operate(write_file: *WriteFile, open_dir_cache: []const Io.Dir, root_path: std.Build.Cache.Path) !void { + const step = &write_file.step; + const b = step.owner; + const io = b.graph.io; + const gpa = b.graph.cache.gpa; + const arena = b.allocator; - const digest = man.final(); - const cache_path = "o" ++ fs.path.sep_str ++ digest; - - write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest }); - - var cache_dir = b.cache_root.handle.createDirPathOpen(io, cache_path, .{}) catch |err| - return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, cache_path, err }); + var cache_dir = root_path.root_dir.handle.createDirPathOpen(io, root_path.sub_path, .{}) catch |err| + return step.fail("unable to make path {f}: {t}", .{ root_path, err }); defer cache_dir.close(io); for (write_file.files.items) |file| { - if (fs.path.dirname(file.sub_path)) |dirname| { + if (Dir.path.dirname(file.sub_path)) |dirname| { cache_dir.createDirPath(io, dirname) catch |err| { - return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{ - b.cache_root, cache_path, fs.path.sep, dirname, err, + return step.fail("unable to make path '{f}{c}{s}': {t}", .{ + root_path, Dir.path.sep, dirname, err, }); }; } switch (file.contents) { .bytes => |bytes| { cache_dir.writeFile(io, .{ .sub_path = file.sub_path, .data = bytes }) catch |err| { - return step.fail("unable to write file '{f}{s}{c}{s}': {t}", .{ - b.cache_root, cache_path, fs.path.sep, file.sub_path, err, + return step.fail("unable to write file '{f}{c}{s}': {t}", .{ + root_path, Dir.path.sep, file.sub_path, err, }); }; }, .copy => |file_source| { const source_path = file_source.getPath2(b, step); const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir, file.sub_path, .{}) catch |err| { - return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {t}", .{ - source_path, b.cache_root, cache_path, fs.path.sep, file.sub_path, err, + return step.fail("unable to update file from '{s}' to '{f}{c}{s}': {t}", .{ + source_path, root_path, Dir.path.sep, file.sub_path, err, }); }; // 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 { if (dest_dirname.len != 0) { cache_dir.createDirPath(io, dest_dirname) catch |err| { - return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{ - b.cache_root, cache_path, fs.path.sep, dest_dirname, @errorName(err), + return step.fail("unable to make path '{f}{c}{s}': {t}", .{ + root_path, Dir.path.sep, dest_dirname, err, }); }; } @@ -325,8 +385,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void { dest_path, .{}, ) catch |err| { - return step.fail("unable to update file from '{f}' to '{f}{s}{c}{s}': {s}", .{ - src_entry_path, b.cache_root, cache_path, fs.path.sep, dest_path, @errorName(err), + return step.fail("unable to update file from '{f}' to '{f}{c}{s}': {t}", .{ + src_entry_path, root_path, Dir.path.sep, dest_path, err, }); }; _ = prev_status; @@ -335,6 +395,4 @@ fn make(step: *Step, options: Step.MakeOptions) !void { } } } - - try step.writeManifest(&man); } diff --git a/test/standalone/dirname/build.zig b/test/standalone/dirname/build.zig index b850680ba9..2dc9e31d3d 100644 --- a/test/standalone/dirname/build.zig +++ b/test/standalone/dirname/build.zig @@ -58,19 +58,9 @@ pub fn build(b: *std.Build) void { ); // Absolute path: - const abs_path = setup_abspath: { - // TODO this is a bad pattern, don't do this - const io = b.graph.io; - const temp_dir = b.makeTempPath(); - - var dir = std.Io.Dir.cwd().openDir(io, temp_dir, .{}) catch @panic("failed to open temp dir"); - defer dir.close(io); - - var file = dir.createFile(io, "foo.txt", .{}) catch @panic("failed to create file"); - file.close(io); - - break :setup_abspath std.Build.LazyPath{ .cwd_relative = temp_dir }; - }; + const write_files = b.addWriteFiles(); + _ = write_files.add("foo.txt", ""); + const abs_path = write_files.getDirectory(); addTestRun(test_step, exists_in, abs_path, &.{"foo.txt"}); } diff --git a/test/tests.zig b/test/tests.zig index 5fc4a967af..3e8c0dab84 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2026,13 +2026,12 @@ pub fn addLinkTests( pub fn addCliTests(b: *std.Build) *Step { const step = b.step("test-cli", "Test the command line interface"); const s = std.fs.path.sep_str; - const io = b.graph.io; { // Test `zig init`. - const tmp_path = b.makeTempPath(); + const tmp_path = b.tmpPath(); const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init" }); - init_exe.setCwd(.{ .cwd_relative = tmp_path }); + init_exe.setCwd(tmp_path); init_exe.setName("zig init"); init_exe.expectStdOutEqual(""); init_exe.expectStdErrEqual("info: created build.zig\n" ++ @@ -2053,31 +2052,28 @@ pub fn addCliTests(b: *std.Build) *Step { run_bad.step.dependOn(&init_exe.step); const run_test = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "test" }); - run_test.setCwd(.{ .cwd_relative = tmp_path }); + run_test.setCwd(tmp_path); run_test.setName("zig build test"); run_test.expectStdOutEqual(""); run_test.step.dependOn(&init_exe.step); const run_run = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "run" }); - run_run.setCwd(.{ .cwd_relative = tmp_path }); + run_run.setCwd(tmp_path); run_run.setName("zig build run"); run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n"); run_run.expectStdErrMatch("All your codebase are belong to us.\n"); run_run.step.dependOn(&init_exe.step); - const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path }); - cleanup.step.dependOn(&run_test.step); - cleanup.step.dependOn(&run_run.step); - cleanup.step.dependOn(&run_bad.step); - - step.dependOn(&cleanup.step); + step.dependOn(&run_test.step); + step.dependOn(&run_run.step); + step.dependOn(&run_bad.step); } { // Test `zig init -m`. - const tmp_path = b.makeTempPath(); + const tmp_path = b.tmpPath(); const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init", "-m" }); - init_exe.setCwd(.{ .cwd_relative = tmp_path }); + init_exe.setCwd(tmp_path); init_exe.setName("zig init -m"); init_exe.expectStdOutEqual(""); init_exe.expectStdErrEqual("info: successfully populated 'build.zig.zon' and 'build.zig'\n"); @@ -2085,7 +2081,7 @@ pub fn addCliTests(b: *std.Build) *Step { // Test Godbolt API if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) { - const tmp_path = b.makeTempPath(); + const tmp_path = b.tmpPath(); const example_zig = b.addWriteFiles().add("example.zig", \\// Type your code here, or load an example. @@ -2101,13 +2097,9 @@ pub fn addCliTests(b: *std.Build) *Step { ); // This is intended to be the exact CLI usage used by godbolt.org. - const run = b.addSystemCommand(&.{ - b.graph.zig_exe, "build-obj", - "--cache-dir", tmp_path, - "--name", "example", - "-fno-emit-bin", "-fno-emit-h", - "-fstrip", "-OReleaseFast", - }); + const run = b.addSystemCommand(&.{ b.graph.zig_exe, "build-obj", "--cache-dir" }); + run.addDirectoryArg(tmp_path); + run.addArgs(&.{ "--name", "example", "-fno-emit-bin", "-fno-emit-h", "-fstrip", "-OReleaseFast" }); run.addFileArg(example_zig); const example_s = run.addPrefixedOutputFileArg("-femit-asm=", "example.s"); @@ -2120,10 +2112,7 @@ pub fn addCliTests(b: *std.Build) *Step { }); checkfile.setName("check godbolt.org CLI usage generating valid asm"); - const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path }); - cleanup.step.dependOn(&checkfile.step); - - step.dependOn(&cleanup.step); + step.dependOn(&checkfile.step); } { @@ -2132,22 +2121,19 @@ pub fn addCliTests(b: *std.Build) *Step { // directory because this test will be mutating the files. The cache // system relies on cache directories being mutated only by their // owners. - const tmp_path = b.makeTempPath(); + const tmp_wf = b.addTempFiles(); const unformatted_code = " // no reason for indent"; - var dir = std.Io.Dir.cwd().openDir(io, tmp_path, .{}) catch @panic("unhandled"); - defer dir.close(io); - dir.writeFile(io, .{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled"); - dir.writeFile(io, .{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled"); - dir.createDir(io, "subdir", .default_dir) catch @panic("unhandled"); - var subdir = dir.openDir(io, "subdir", .{}) catch @panic("unhandled"); - defer subdir.close(io); - subdir.writeFile(io, .{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled"); + _ = tmp_wf.add("fmt1.zig", unformatted_code); + _ = tmp_wf.add("fmt2.zig", unformatted_code); + _ = tmp_wf.add("subdir/fmt3.zig", unformatted_code); + + const tmp_path = tmp_wf.getDirectory(); // Test zig fmt affecting only the appropriate files. const run1 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "fmt1.zig" }); run1.setName("run zig fmt one file"); - run1.setCwd(.{ .cwd_relative = tmp_path }); + run1.setCwd(tmp_path); run1.has_side_effects = true; // stdout should be file path + \n run1.expectStdOutEqual("fmt1.zig\n"); @@ -2155,7 +2141,7 @@ pub fn addCliTests(b: *std.Build) *Step { // Test excluding files and directories from a run const run2 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "subdir", "." }); run2.setName("run zig fmt on directory with exclusions"); - run2.setCwd(.{ .cwd_relative = tmp_path }); + run2.setCwd(tmp_path); run2.has_side_effects = true; run2.expectStdOutEqual(""); run2.step.dependOn(&run1.step); @@ -2163,7 +2149,7 @@ pub fn addCliTests(b: *std.Build) *Step { // Test excluding non-existent file const run3 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--exclude", "fmt2.zig", "--exclude", "nonexistent.zig", "." }); run3.setName("run zig fmt on directory with non-existent exclusion"); - run3.setCwd(.{ .cwd_relative = tmp_path }); + run3.setCwd(tmp_path); run3.has_side_effects = true; run3.expectStdOutEqual("." ++ s ++ "subdir" ++ s ++ "fmt3.zig\n"); run3.step.dependOn(&run2.step); @@ -2171,7 +2157,7 @@ pub fn addCliTests(b: *std.Build) *Step { // running it on the dir, only the new file should be changed const run4 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." }); run4.setName("run zig fmt the directory"); - run4.setCwd(.{ .cwd_relative = tmp_path }); + run4.setCwd(tmp_path); run4.has_side_effects = true; run4.expectStdOutEqual("." ++ s ++ "fmt2.zig\n"); run4.step.dependOn(&run3.step); @@ -2179,37 +2165,33 @@ pub fn addCliTests(b: *std.Build) *Step { // both files have been formatted, nothing should change now const run5 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." }); run5.setName("run zig fmt with nothing to do"); - run5.setCwd(.{ .cwd_relative = tmp_path }); + run5.setCwd(tmp_path); run5.has_side_effects = true; run5.expectStdOutEqual(""); run5.step.dependOn(&run4.step); const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00"; - const fmt6_path = b.pathJoin(&.{ tmp_path, "fmt6.zig" }); - const write6 = b.addUpdateSourceFiles(); - write6.addBytesToSource(unformatted_code_utf16, fmt6_path); + const write6 = b.addMutateFiles(tmp_path); + const fmt6_path = write6.add("fmt6.zig", unformatted_code_utf16); write6.step.dependOn(&run5.step); // Test `zig fmt` handling UTF-16 decoding. const run6 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "." }); run6.setName("run zig fmt convert UTF-16 to UTF-8"); - run6.setCwd(.{ .cwd_relative = tmp_path }); + run6.setCwd(tmp_path); run6.has_side_effects = true; run6.expectStdOutEqual("." ++ s ++ "fmt6.zig\n"); run6.step.dependOn(&write6.step); // TODO change this to an exact match - const check6 = b.addCheckFile(.{ .cwd_relative = fmt6_path }, .{ + const check6 = b.addCheckFile(fmt6_path, .{ .expected_matches = &.{ "// no reason", }, }); check6.step.dependOn(&run6.step); - const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path }); - cleanup.step.dependOn(&check6.step); - - step.dependOn(&cleanup.step); + step.dependOn(&check6.step); } {