zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit db7ceada15b007747bfd433e6635324edcec918e (tree)
parent 398ea7e492b7016dcb6cebfe6656876002f5bf73
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Mon, 18 May 2026 20:34:51 -0700

Maker: implement Step.WriteFile

Diffstat:
Mlib/compiler/Maker/Step.zig | 35++++++++++++++---------------------
Mlib/compiler/Maker/Step/UpdateSourceFiles.zig | 12+++++++-----
Mlib/compiler/Maker/Step/WriteFile.zig | 339++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
3 files changed, 235 insertions(+), 151 deletions(-)

diff --git a/lib/compiler/Maker/Step.zig b/lib/compiler/Maker/Step.zig @@ -26,6 +26,7 @@ pub const ObjCopy = @import("Step/ObjCopy.zig"); pub const Options = @import("Step/Options.zig"); pub const Run = @import("Step/Run.zig"); pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig"); +pub const WriteFile = @import("Step/WriteFile.zig"); /// Avoid false sharing. _: void align(std.atomic.cache_line) = {}, @@ -87,7 +88,7 @@ pub const Extended = union(enum) { top_level: TopLevel, translate_c: Todo, update_source_files: UpdateSourceFiles, - write_file: Todo, + write_file: WriteFile, pub fn init(tag: Configuration.Step.Tag) Extended { return switch (tag) { @@ -119,9 +120,10 @@ pub const Extended = union(enum) { progress_node: std.Progress.Node, ) Step.ExtendedMakeError!void { _ = todo; - _ = maker; _ = progress_node; - std.debug.panic("TODO implement another step type (index {d})", .{step_index}); + const conf = &maker.scanned_config.configuration; + const conf_step = step_index.ptr(conf); + std.debug.panic("TODO implement another step type: {s}", .{conf_step.name.slice(conf)}); } }; @@ -807,19 +809,17 @@ pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: La /// Paths derived from this directory should also be manually added via /// `addDirectoryWatchInputFromPath` if and only if this function returns /// `true`. -pub fn addDirectoryWatchInput(step: *Step, lazy_directory: LazyPath) Allocator.Error!bool { +pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPath) Allocator.Error!bool { switch (lazy_directory) { - .src_path => |src_path| try addDirectoryWatchInputFromBuilder(step, src_path.owner, src_path.sub_path), - .dependency => |d| try addDirectoryWatchInputFromBuilder(step, d.dependency.builder, d.sub_path), - .cwd_relative => |path_string| { - try addDirectoryWatchInputFromPath(step, .{ - .root_dir = .{ - .path = null, - .handle = .cwd(), - }, - .sub_path = path_string, - }); + .source_path => |source_path| { + const conf = &maker.scanned_config.configuration; + const graph = maker.graph; + const arena = graph.arena; // TODO don't leak into the process arena + const sub_path = source_path.sub_path.slice(conf); + const pkg_path = try maker.packagePath(arena, source_path.owner, sub_path); + try addDirectoryWatchInputFromPath(step, maker, pkg_path); }, + .relative => |relative| try addDirectoryWatchInputFromPath(step, maker, maker.relativePath(relative)), // Nothing to watch because this dependency edge is modeled instead via `dependants`. .generated => return false, } @@ -838,13 +838,6 @@ pub fn addDirectoryWatchInputFromPath(step: *Step, maker: *Maker, path: Path) !v return addWatchInputFromPath(step, maker, path, "."); } -fn addDirectoryWatchInputFromBuilder(step: *Step, package: Package, sub_path: []const u8) !void { - return addDirectoryWatchInputFromPath(step, .{ - .root_dir = package.build_root, - .sub_path = sub_path, - }); -} - fn addWatchInputPath(step: *Step, maker: *Maker, path: Path) Allocator.Error!void { return addWatchInputFromPath(step, maker, .{ .root_dir = path.root_dir, diff --git a/lib/compiler/Maker/Step/UpdateSourceFiles.zig b/lib/compiler/Maker/Step/UpdateSourceFiles.zig @@ -32,6 +32,8 @@ pub fn make( progress_node.setEstimatedTotalItems(conf_usf.embeds.slice.len + conf_usf.copies.slice.len); + step.clearWatchInputs(maker); + for (conf_usf.embeds.slice) |*embed| { const dest_path: Path = .{ .root_dir = build_root, @@ -43,12 +45,12 @@ pub fn make( .sub_path = dirname, }; dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err| - return step.fail(maker, "failed to create path {f}: {t}", .{ dirname_path, err }); + return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err }); } dest_path.root_dir.handle.writeFile(io, .{ .sub_path = dest_path.sub_path, .data = embed.contents.slice(conf), - }) catch |err| return step.fail(maker, "failed to write file {f}: {t}", .{ dest_path, err }); + }) catch |err| return step.fail(maker, "failed writing file {f}: {t}", .{ dest_path, err }); any_miss = true; progress_node.completeOne(); } @@ -64,11 +66,11 @@ pub fn make( .sub_path = dirname, }; dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err| - return step.fail(maker, "failed to create path {f}: {t}", .{ dirname_path, err }); + return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err }); } const src_lazy_path = copy.src_file.get(conf); const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index); - if (!step.inputs.populated()) try step.addWatchInput(maker, arena, src_lazy_path); + try step.addWatchInput(maker, arena, src_lazy_path); const prev_status = source_path.root_dir.handle.updateFile( io, @@ -76,7 +78,7 @@ pub fn make( dest_path.root_dir.handle, dest_path.sub_path, .{}, - ) catch |err| return step.fail(maker, "unable to update file from {f} to {f}: {t}", .{ + ) catch |err| return step.fail(maker, "failed updating file from {f} to {f}: {t}", .{ source_path, dest_path, err, }); any_miss = any_miss or prev_status == .stale; diff --git a/lib/compiler/Maker/Step/WriteFile.zig b/lib/compiler/Maker/Step/WriteFile.zig @@ -1,205 +1,294 @@ -fn make(step: *Step, options: Step.MakeOptions) !void { - _ = options; - const b = step.owner; - const graph = b.graph; +const WriteFile = @This(); + +const std = @import("std"); +const Io = std.Io; +const assert = std.debug.assert; +const Path = std.Build.Cache.Path; +const allocPrint = std.fmt.allocPrint; +const Configuration = std.Build.Configuration; + +const Step = @import("../Step.zig"); +const Maker = @import("../../Maker.zig"); + +pub fn make( + wf: *WriteFile, + step_index: Configuration.Step.Index, + maker: *Maker, + progress_node: std.Progress.Node, +) Step.ExtendedMakeError!void { + _ = wf; + const graph = maker.graph; + const gpa = maker.gpa; + const arena = maker.graph.arena; // TODO don't leak into process arena const io = graph.io; - const arena = b.allocator; - const gpa = graph.cache.gpa; - const write_file: *WriteFile = @fieldParentPtr("step", step); + const step = maker.stepByIndex(step_index); + const conf = &maker.scanned_config.configuration; + const conf_step = step_index.ptr(conf); + const conf_wf = conf_step.extended.get(conf.extra).write_file; + const cache_root = graph.local_cache_root; + const directories = conf_wf.directories.slice; - const open_dir_cache = try arena.alloc(Io.Dir, write_file.directories.items.len); - var open_dirs_count: usize = 0; + const open_dir_cache = try arena.alloc(Io.Dir, directories.len); + var open_dirs_count: u32 = 0; defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]); - switch (write_file.mode) { - .whole_cached => { - step.clearWatchInputs(); + // Doesn't yet include contents of directories. + var total_items: usize = conf_wf.embeds.slice.len + conf_wf.copies.slice.len + conf_wf.directories.slice.len; + progress_node.setEstimatedTotalItems(total_items); - // 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. + switch (conf_wf.flags.mode) { + .whole_cached => { + step.clearWatchInputs(maker); - // 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. + // The cache is used here primarily as a way to find a canonical + // location to put build artifacts without parallel step execution + // clobbering each other. - var man = b.graph.cache.obtain(); + var man = 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); - }, - } + for (conf_wf.embeds.slice) |*embed| { + man.hash.addBytes(embed.sub_path.slice(conf)); + man.hash.addBytes(embed.contents.slice(conf)); + } + + for (conf_wf.copies.slice) |*copy| { + man.hash.addBytes(copy.sub_path.slice(conf)); + const src_lazy_path = copy.src_file.get(conf); + const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index); + _ = try man.addFilePath(source_path, null); + try step.addWatchInput(maker, arena, src_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); + for (directories, open_dir_cache) |conf_dir, *opened_dir| { + const exclude_extensions = conf_dir.exclude_extensions.slice(conf) orelse &.{}; + const include_extensions = conf_dir.include_extensions.slice(conf); + + man.hash.addBytes(conf_dir.sub_path.slice(conf)); + for (exclude_extensions) |ext| man.hash.addBytes(ext.slice(conf)); + if (include_extensions) |includes| for (includes) |inc| { + man.hash.addBytes(inc.slice(conf)); + }; - const need_derived_inputs = try step.addDirectoryWatchInput(dir.source); - const src_dir_path = dir.source.getPath3(b, step); + const src_lazy_path = conf_dir.src_path.get(conf); + const need_derived_inputs = try step.addDirectoryWatchInput(maker, src_lazy_path); + const src_dir_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index); 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), - }); + return step.fail(maker, "failed opening source directory {f}: {t}", .{ src_dir_path, err }); }; - open_dir_cache_elem.* = src_dir; + opened_dir.* = 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; + while (it.next(io) catch |err| switch (err) { + error.Canceled, error.OutOfMemory => |e| return e, + else => |e| return step.fail(maker, "failed iterating dir {f}: {t}", .{ src_dir_path, e }), + }) |entry| { + if (!pathIncluded(conf, exclude_extensions, include_extensions, 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); + try step.addDirectoryWatchInputFromPath(maker, entry_path); } }, .file => { const entry_path = try src_dir_path.join(arena, entry.path); _ = try man.addFilePath(entry_path, null); + total_items += 1; }, else => continue, } } } - if (try step.cacheHit(&man)) { + if (try step.cacheHit(maker, &man)) { const digest = man.final(); - write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest }); + maker.generatedPath(conf_wf.generated_directory).* = .{ + .root_dir = cache_root, + .sub_path = try Io.Dir.path.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}); + const out_path: Path = .{ + .root_dir = cache_root, + .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), + }; - try operate(write_file, open_dir_cache, .{ - .root_dir = b.cache_root, - .sub_path = cache_path, - }); + progress_node.setEstimatedTotalItems(total_items); + try operate(maker, step_index, open_dir_cache, out_path, progress_node); + try step.writeManifest(maker, &man); - try step.writeManifest(&man); + maker.generatedPath(conf_wf.generated_directory).* = out_path; }, .tmp => { step.result_cached = false; var rand_int: u64 = undefined; io.random(@ptrCast(&rand_int)); - const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); + const hex_digest = std.fmt.hex(rand_int); - write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path}); + const out_path: Path = .{ + .root_dir = cache_root, + .sub_path = try Io.Dir.path.join(arena, &.{ "tmp", &hex_digest }), + }; - try operate(write_file, open_dir_cache, .{ - .root_dir = b.cache_root, - .sub_path = tmp_dir_sub_path, - }); + try operate(maker, step_index, open_dir_cache, out_path, progress_node); + + maker.generatedPath(conf_wf.generated_directory).* = out_path; }, - .mutate => |lp| { + .mutate => { 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); + const root_path = try maker.resolveLazyPathIndex(arena, conf_wf.mutate_path.value.?, step_index); + try operate(maker, step_index, open_dir_cache, root_path, progress_node); + maker.generatedPath(conf_wf.generated_directory).* = root_path; }, } } -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; - - 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 (Dir.path.dirname(file.sub_path)) |dirname| { - cache_dir.createDirPath(io, dirname) catch |err| { - return step.fail("unable to make path '{f}{c}{s}': {t}", .{ - root_path, Dir.path.sep, dirname, err, - }); +fn operate( + maker: *Maker, + step_index: Configuration.Step.Index, + open_dir_cache: []const Io.Dir, + root_path: std.Build.Cache.Path, + progress_node: std.Progress.Node, +) !void { + const graph = maker.graph; + const gpa = maker.gpa; + const arena = maker.graph.arena; // TODO don't leak into process arena + const io = graph.io; + const step = maker.stepByIndex(step_index); + const conf = &maker.scanned_config.configuration; + const conf_step = step_index.ptr(conf); + const conf_wf = conf_step.extended.get(conf.extra).write_file; + + const root_directory: std.Build.Cache.Directory = .{ + .handle = root_path.root_dir.handle.createDirPathOpen(io, root_path.sub_path, .{}) catch |err| + return step.fail(maker, "failed creating path {f}: {t}", .{ root_path, err }), + .path = try root_path.toString(arena), + }; + defer root_directory.handle.close(io); + + for (conf_wf.embeds.slice) |*embed| { + const dest_path: Path = .{ + .root_dir = root_directory, + .sub_path = embed.sub_path.slice(conf), + }; + if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| { + const dirname_path: Path = .{ + .root_dir = root_directory, + .sub_path = dirname, }; + dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err| + return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, 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}{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}{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. - // But this is kind of a partial cache hit since individual - // file copies may be avoided. Oh well, this information is - // discarded. - _ = prev_status; - }, + dest_path.root_dir.handle.writeFile(io, .{ + .sub_path = dest_path.sub_path, + .data = embed.contents.slice(conf), + }) catch |err| return step.fail(maker, "failed writing contents to file {f}: {t}", .{ dest_path, err }); + progress_node.completeOne(); + } + + for (conf_wf.copies.slice) |*copy| { + const dest_path: Path = .{ + .root_dir = root_directory, + .sub_path = copy.sub_path.slice(conf), + }; + // Rather than passing make_path = true below, this optimizes for the + // more common case where the directory does not exist. + if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| { + const dirname_path: Path = .{ + .root_dir = root_directory, + .sub_path = dirname, + }; + dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err| + return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err }); } + const source_path = try maker.resolveLazyPathIndex(arena, copy.src_file, step_index); + Io.Dir.copyFile( + source_path.root_dir.handle, + source_path.sub_path, + dest_path.root_dir.handle, + dest_path.sub_path, + io, + .{}, + ) catch |err| return step.fail(maker, "failed copying file from {f} to {f}: {t}", .{ + source_path, dest_path, err, + }); + progress_node.completeOne(); } - for (write_file.directories.items, open_dir_cache) |dir, already_open_dir| { - const src_dir_path = dir.source.getPath3(b, step); - const dest_dirname = dir.sub_path; + for (conf_wf.directories.slice, open_dir_cache) |conf_dir, already_open_dir| { + const exclude_extensions = conf_dir.exclude_extensions.slice(conf) orelse &.{}; + const include_extensions = conf_dir.include_extensions.slice(conf); - if (dest_dirname.len != 0) { - cache_dir.createDirPath(io, dest_dirname) catch |err| { - return step.fail("unable to make path '{f}{c}{s}': {t}", .{ - root_path, Dir.path.sep, dest_dirname, err, - }); - }; + const src_dir_path = try maker.resolveLazyPathIndex(arena, conf_dir.src_path, step_index); + const dest_dir_path: Path = .{ + .root_dir = root_directory, + .sub_path = conf_dir.sub_path.slice(conf), + }; + + if (dest_dir_path.sub_path.len != 0) { + dest_dir_path.root_dir.handle.createDirPath(io, dest_dir_path.sub_path) catch |err| + return step.fail(maker, "failed creating path {f}: {t}", .{ dest_dir_path, err }); } var it = try already_open_dir.walk(gpa); defer it.deinit(); - while (try it.next(io)) |entry| { - if (!dir.options.pathIncluded(entry.path)) continue; + while (it.next(io) catch |err| switch (err) { + error.Canceled, error.OutOfMemory => |e| return e, + else => |e| return step.fail(maker, "failed iterating dir {f}: {t}", .{ src_dir_path, e }), + }) |entry| { + if (!pathIncluded(conf, exclude_extensions, include_extensions, entry.path)) continue; const src_entry_path = try src_dir_path.join(arena, entry.path); - const dest_path = b.pathJoin(&.{ dest_dirname, entry.path }); + const dest_path = try dest_dir_path.join(arena, entry.path); switch (entry.kind) { - .directory => try cache_dir.createDirPath(io, dest_path), + .directory => dest_path.root_dir.handle.createDirPath(io, dest_path.sub_path) catch |err| { + return step.fail(maker, "failed creating path {f}: {t}", .{ dest_path, err }); + }, .file => { - const prev_status = Io.Dir.updateFile( + Io.Dir.copyFile( src_entry_path.root_dir.handle, - io, src_entry_path.sub_path, - cache_dir, - dest_path, + dest_path.root_dir.handle, + dest_path.sub_path, + io, .{}, - ) catch |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; + ) catch |err| return step.fail(maker, "failed copying file from {f} to {f}: {t}", .{ + src_entry_path, dest_path, err, + }); + progress_node.completeOne(); }, else => continue, } } } } + +fn pathIncluded( + conf: *const Configuration, + exclude_extensions: []const Configuration.String, + include_extensions: ?[]const Configuration.String, + path: []const u8, +) bool { + for (exclude_extensions) |ext| { + if (std.mem.endsWith(u8, path, ext.slice(conf))) + return false; + } + if (include_extensions) |incs| { + for (incs) |inc| { + if (std.mem.endsWith(u8, path, inc.slice(conf))) + return true; + } else { + return false; + } + } + return true; +}