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
This commit is contained in:
Andrew Kelley 2026-01-04 17:23:45 -08:00
parent e3b7cad81e
commit b4dbe483a7
7 changed files with 221 additions and 216 deletions

View file

@ -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()),
},

View file

@ -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.

View file

@ -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;

View file

@ -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 });
}
};
}

View file

@ -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);
}

View file

@ -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"});
}

View file

@ -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);
}
{