configurer: serialize WriteFile

This commit is contained in:
Andrew Kelley
2026-05-08 12:20:21 -07:00
parent f4ae918684
commit 1186a10d4e
3 changed files with 181 additions and 109 deletions
+41 -1
View File
@@ -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);
+44 -7
View File
@@ -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);
+96 -101
View File
@@ -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});
}
}
}