From 1186a10d4e145f553a4b749042fcc02ba6efe18b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 8 May 2026 12:20:21 -0700 Subject: [PATCH] configurer: serialize WriteFile --- lib/compiler/configurer.zig | 42 ++++++- lib/std/Build/Configuration.zig | 51 ++++++-- lib/std/Build/Step/WriteFile.zig | 197 +++++++++++++++---------------- 3 files changed, 181 insertions(+), 109 deletions(-) diff --git a/lib/compiler/configurer.zig b/lib/compiler/configurer.zig index 9ae2ab0a0e..b3490936b7 100644 --- a/lib/compiler/configurer.zig +++ b/lib/compiler/configurer.zig @@ -891,7 +891,47 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { .find_program => @panic("TODO"), .fmt => @panic("TODO"), .translate_c => @panic("TODO"), - .write_file => @panic("TODO"), + .write_file => e: { + const wf: *Step.WriteFile = @fieldParentPtr("step", step); + + const copies = try arena.alloc(Configuration.Step.WriteFile.Copy, wf.copies.items.len); + for (copies, wf.copies.items) |*dest, src| dest.* = .{ + .sub_path = src.sub_path, + .src_file = try s.addLazyPath(src.src_file), + }; + + const directories = try arena.alloc( + Configuration.Step.WriteFile.Directory, + wf.directories.items.len, + ); + for (directories, wf.directories.items) |*dest, src| dest.* = .{ + .sub_path = src.sub_path, + .src_path = try s.addLazyPath(src.src_path), + .exclude_extensions = src.exclude_extensions, + .include_extensions = src.include_extensions, + }; + + break :e @enumFromInt(try wc.addExtra(@as(Configuration.Step.WriteFile, .{ + .flags = .{ + .embeds = wf.embeds.items.len != 0, + .copies = copies.len != 0, + .directories = directories.len != 0, + .mode = switch (wf.mode) { + .whole_cached => .whole_cached, + .tmp => .tmp, + .mutate => .mutate, + }, + }, + .generated_directory = wf.generated_directory, + .embeds = .{ .slice = wf.embeds.items }, + .copies = .{ .slice = copies }, + .directories = .{ .slice = directories }, + .mutate_path = .{ .value = switch (wf.mode) { + .mutate => |lp| try s.addLazyPath(lp), + .whole_cached, .tmp => null, + } }, + }))); + }, .update_source_files => @panic("TODO"), .run => e: { const run: *Step.Run = @fieldParentPtr("step", step); diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index 33e583402e..266a36bf43 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -1251,10 +1251,42 @@ pub const Step = extern struct { pub const WriteFile = struct { flags: @This().Flags, + generated_directory: GeneratedFileIndex, + embeds: Storage.FlagLengthPrefixedList(.flags, .embeds, Embed), + copies: Storage.FlagLengthPrefixedList(.flags, .copies, Copy), + directories: Storage.FlagLengthPrefixedList(.flags, .directories, Directory), + mutate_path: Storage.EnumOptional(.flags, .mode, .mutate, LazyPath.Index), + + pub const Embed = extern struct { + sub_path: String, + contents: Bytes, + }; + + pub const Copy = extern struct { + sub_path: String, + src_file: LazyPath.Index, + }; + + pub const Directory = extern struct { + sub_path: String, + src_path: LazyPath.Index, + exclude_extensions: OptionalStringList, + include_extensions: OptionalStringList, + }; + + pub const Mode = enum(u2) { + whole_cached, + tmp, + mutate, + }; pub const Flags = packed struct(u32) { tag: Tag = .write_file, - _: u27 = 0, + embeds: bool, + copies: bool, + directories: bool, + mode: Mode, + _: u22 = 0, }; }; @@ -2370,8 +2402,11 @@ pub const Storage = enum { }; } - /// A field in flags determines whether the length is zero or nonzero. If the length is - /// nonzero, then there is a length field followed by the list. + /// A field in flags determines whether the length is zero or nonzero. If + /// the length is nonzero, then there is a length field followed by the + /// list. The elements need well-defined memory layout but can otherwise be + /// any multiple of u32 length. The length is the number of elements, not + /// the number of u32s. pub fn FlagLengthPrefixedList( comptime flags_arg: @EnumLiteral(), comptime flag_arg: @EnumLiteral(), @@ -2767,14 +2802,16 @@ pub const Storage = enum { const len: u32 = @intCast(value.slice.len); if (len == 0) return 0; // Flag bit hides the length prefix. buffer[i] = len; - @memcpy(buffer[i + 1 ..][0..len], @as([]const u32, @ptrCast(value.slice))); - return len + 1; + const buf_len = len * @divExact(@sizeOf(Field.Elem), @sizeOf(u32)); + @memcpy(buffer[i + 1 ..][0..buf_len], @as([]const u32, @ptrCast(value.slice))); + return 1 + buf_len; }, .length_prefixed_list => { const len: u32 = @intCast(value.slice.len); buffer[i] = len; - @memcpy(buffer[i + 1 ..][0..len], @as([]const u32, @ptrCast(value.slice))); - return len + 1; + const buf_len = len * @divExact(@sizeOf(Field.Elem), @sizeOf(u32)); + @memcpy(buffer[i + 1 ..][0..buf_len], @as([]const u32, @ptrCast(value.slice))); + return 1 + buf_len; }, .flag_list => { const len: u32 = @intCast(value.slice.len); diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index 00ffe5e456..64a7a05666 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -13,8 +13,9 @@ const Configuration = std.Build.Configuration; step: Step, -files: std.ArrayList(File), -directories: std.ArrayList(Directory), +embeds: std.ArrayList(Embed) = .empty, +copies: std.ArrayList(Copy) = .empty, +directories: std.ArrayList(Directory) = .empty, generated_directory: Configuration.GeneratedFileIndex, mode: Mode = .whole_cached, @@ -37,158 +38,152 @@ pub const Mode = union(enum) { mutate: std.Build.LazyPath, }; -pub const File = struct { - sub_path: []const u8, - contents: Contents, +pub const Embed = Configuration.Step.WriteFile.Embed; + +pub const Copy = struct { + sub_path: Configuration.String, + src_file: std.Build.LazyPath, }; pub const Directory = struct { - source: std.Build.LazyPath, - sub_path: []const u8, - options: Options, - - pub const Options = struct { - /// File paths that end in any of these suffixes will be excluded from copying. - exclude_extensions: []const []const u8 = &.{}, - /// Only file paths that end in any of these suffixes will be included in copying. - /// `null` means that all suffixes will be included. - /// `exclude_extensions` takes precedence over `include_extensions`. - include_extensions: ?[]const []const u8 = null, - - pub fn dupe(opts: Options, graph: *std.Build.Graph) Options { - return .{ - .exclude_extensions = graph.dupeStrings(opts.exclude_extensions), - .include_extensions = if (opts.include_extensions) |incs| graph.dupeStrings(incs) else null, - }; - } - - pub fn pathIncluded(opts: Options, path: []const u8) bool { - for (opts.exclude_extensions) |ext| { - if (std.mem.endsWith(u8, path, ext)) - return false; - } - if (opts.include_extensions) |incs| { - for (incs) |inc| { - if (std.mem.endsWith(u8, path, inc)) - return true; - } else { - return false; - } - } - return true; - } - }; -}; - -pub const Contents = union(enum) { - bytes: []const u8, - copy: std.Build.LazyPath, + sub_path: Configuration.String, + src_path: std.Build.LazyPath, + exclude_extensions: Configuration.OptionalStringList, + include_extensions: Configuration.OptionalStringList, }; pub fn create(owner: *std.Build) *WriteFile { const graph = owner.graph; - const arena = graph.arena; - const write_file = arena.create(WriteFile) catch @panic("OOM"); - write_file.* = .{ - .step = Step.init(.{ + const wf = graph.create(WriteFile); + wf.* = .{ + .step = .init(.{ .tag = base_tag, .name = "WriteFile", .owner = owner, }), - .files = .empty, - .directories = .empty, - .generated_directory = graph.addGeneratedFile(&write_file.step), + .generated_directory = graph.addGeneratedFile(&wf.step), }; - return write_file; + return wf; } -pub fn add(write_file: *WriteFile, sub_path: []const u8, bytes: []const u8) std.Build.LazyPath { - const graph = write_file.step.owner.graph; +/// Writes `contents` to a file at `sub_path` relative to the output +/// directory. +/// +/// `sub_path` may be a basename, or it may include subdirectories, which are +/// created as needed. +pub fn add(wf: *WriteFile, sub_path: []const u8, contents: []const u8) std.Build.LazyPath { + const graph = wf.step.owner.graph; + const wc = &graph.wip_configuration; const arena = graph.arena; - const file: File = .{ - .sub_path = graph.dupePath(sub_path), - .contents = .{ .bytes = graph.dupeString(bytes) }, - }; - write_file.files.append(arena, file) catch @panic("OOM"); - write_file.maybeUpdateName(); + + wf.embeds.append(arena, .{ + .sub_path = wc.addString(sub_path) catch @panic("OOM"), + .contents = wc.addBytes(contents) catch @panic("OOM"), + }) catch @panic("OOM"); + + wf.maybeUpdateName(); + return .{ .generated = .{ - .index = write_file.generated_directory, - .sub_path = file.sub_path, + .index = wf.generated_directory, + .sub_path = graph.dupeString(sub_path), }, }; } -/// Copies the provided file into the generated directory within the local -/// cache, along with all the rest of the files added to this step. +/// Copies the provided file to `sub_path` relative to the output directory. /// -/// `sub_path` is the destination path relative to the local cache directory -/// associated with this WriteFile. It may be a basename, or it may include -/// subdirectories, which are created as needed. -pub fn addCopyFile(write_file: *WriteFile, source: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath { - const graph = write_file.step.owner.graph; - const duped_path = graph.dupePath(sub_path); +/// `sub_path` may be a basename, or it may include subdirectories, which are +/// created as needed. +pub fn addCopyFile(wf: *WriteFile, src_file: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath { + const graph = wf.step.owner.graph; + const wc = &graph.wip_configuration; const arena = graph.arena; - write_file.files.append(arena, .{ - .sub_path = duped_path, - .contents = .{ .copy = source }, + wf.copies.append(arena, .{ + .sub_path = wc.addString(sub_path) catch @panic("OOM"), + .src_file = src_file.dupe(graph), }) catch @panic("OOM"); - write_file.maybeUpdateName(); - source.addStepDependencies(&write_file.step); + wf.maybeUpdateName(); + + src_file.addStepDependencies(&wf.step); return .{ .generated = .{ - .index = write_file.generated_directory, - .sub_path = duped_path, + .index = wf.generated_directory, + .sub_path = graph.dupePath(sub_path), } }; } +pub const CopyDirectoryOptions = struct { + /// File paths that end in any of these suffixes will be excluded from copying. + exclude_extensions: []const []const u8 = &.{}, + /// Only file paths that end in any of these suffixes will be included in copying. + /// `null` means that all suffixes will be included. + /// `exclude_extensions` takes precedence over `include_extensions`. + include_extensions: ?[]const []const u8 = null, +}; + /// Copy files matching the specified exclude/include patterns to the specified /// subdirectory relative to this step's generated directory. /// /// The returned value is a lazy path to the generated subdirectory. pub fn addCopyDirectory( - write_file: *WriteFile, - source: std.Build.LazyPath, + wf: *WriteFile, + src_path: std.Build.LazyPath, sub_path: []const u8, - options: Directory.Options, + options: CopyDirectoryOptions, ) std.Build.LazyPath { - const graph = write_file.step.owner.graph; + const graph = wf.step.owner.graph; + const wc = &graph.wip_configuration; const arena = graph.arena; - const dir = Directory{ - .source = source.dupe(graph), - .sub_path = graph.dupePath(sub_path), - .options = options.dupe(graph), - }; - write_file.directories.append(arena, dir) catch @panic("OOM"); - write_file.maybeUpdateName(); - source.addStepDependencies(&write_file.step); + wf.directories.append(arena, .{ + .sub_path = wc.addString(sub_path) catch @panic("OOM"), + .src_path = src_path.dupe(graph), + .exclude_extensions = if (options.exclude_extensions.len != 0) + .init(wc.addStringList(options.exclude_extensions) catch @panic("OOM")) + else + .none, + .include_extensions = if (options.include_extensions) |list| + .init(wc.addStringList(list) catch @panic("OOM")) + else + .none, + }) catch @panic("OOM"); + + wf.maybeUpdateName(); + + src_path.addStepDependencies(&wf.step); + return .{ .generated = .{ - .index = write_file.generated_directory, - .sub_path = dir.sub_path, + .index = wf.generated_directory, + .sub_path = graph.dupePath(sub_path), }, }; } /// Returns a `LazyPath` representing the base directory that contains all the /// files from this `WriteFile`. -pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath { - return .{ .generated = .{ .index = write_file.generated_directory } }; +pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath { + return .{ .generated = .{ .index = wf.generated_directory } }; } -fn maybeUpdateName(write_file: *WriteFile) void { - if (write_file.files.items.len == 1 and write_file.directories.items.len == 0) { +fn maybeUpdateName(wf: *WriteFile) void { + const graph = wf.step.owner.graph; + const wc = &graph.wip_configuration; + const files_count = wf.embeds.items.len + wf.copies.items.len; + if (files_count == 1 and wf.directories.items.len == 0) { // First time adding a file; update name. - if (std.mem.eql(u8, write_file.step.name, "WriteFile")) { - write_file.step.name = write_file.step.owner.fmt("WriteFile {s}", .{write_file.files.items[0].sub_path}); + const sub_path = if (wf.embeds.items.len == 1) wf.embeds.items[0].sub_path else wf.copies.items[0].sub_path; + if (std.mem.eql(u8, wf.step.name, "WriteFile")) { + wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wc.stringSlice(sub_path)}); } - } else if (write_file.directories.items.len == 1 and write_file.files.items.len == 0) { + } else if (wf.directories.items.len == 1 and files_count == 0) { // First time adding a directory; update name. - if (std.mem.eql(u8, write_file.step.name, "WriteFile")) { - write_file.step.name = write_file.step.owner.fmt("WriteFile {s}", .{write_file.directories.items[0].sub_path}); + const dir_name = wc.stringSlice(wf.directories.items[0].sub_path); + if (std.mem.eql(u8, wf.step.name, "WriteFile")) { + wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{dir_name}); } } }