authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-08 12:20:21-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 18:54:35-07:00
log1186a10d4e145f553a4b749042fcc02ba6efe18b
tree163f5cc11216cab9011eded29682048407e351ea
parentf4ae918684975c191749edec26223b7dba7cd9ed

configurer: serialize WriteFile


3 files changed, 181 insertions(+), 109 deletions(-)

lib/compiler/configurer.zig+41-1
......@@ -891,7 +891,47 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
891891 .find_program => @panic("TODO"),
892892 .fmt => @panic("TODO"),
893893 .translate_c => @panic("TODO"),
894 .write_file => @panic("TODO"),
894 .write_file => e: {
895 const wf: *Step.WriteFile = @fieldParentPtr("step", step);
896
897 const copies = try arena.alloc(Configuration.Step.WriteFile.Copy, wf.copies.items.len);
898 for (copies, wf.copies.items) |*dest, src| dest.* = .{
899 .sub_path = src.sub_path,
900 .src_file = try s.addLazyPath(src.src_file),
901 };
902
903 const directories = try arena.alloc(
904 Configuration.Step.WriteFile.Directory,
905 wf.directories.items.len,
906 );
907 for (directories, wf.directories.items) |*dest, src| dest.* = .{
908 .sub_path = src.sub_path,
909 .src_path = try s.addLazyPath(src.src_path),
910 .exclude_extensions = src.exclude_extensions,
911 .include_extensions = src.include_extensions,
912 };
913
914 break :e @enumFromInt(try wc.addExtra(@as(Configuration.Step.WriteFile, .{
915 .flags = .{
916 .embeds = wf.embeds.items.len != 0,
917 .copies = copies.len != 0,
918 .directories = directories.len != 0,
919 .mode = switch (wf.mode) {
920 .whole_cached => .whole_cached,
921 .tmp => .tmp,
922 .mutate => .mutate,
923 },
924 },
925 .generated_directory = wf.generated_directory,
926 .embeds = .{ .slice = wf.embeds.items },
927 .copies = .{ .slice = copies },
928 .directories = .{ .slice = directories },
929 .mutate_path = .{ .value = switch (wf.mode) {
930 .mutate => |lp| try s.addLazyPath(lp),
931 .whole_cached, .tmp => null,
932 } },
933 })));
934 },
895935 .update_source_files => @panic("TODO"),
896936 .run => e: {
897937 const run: *Step.Run = @fieldParentPtr("step", step);
lib/std/Build/Configuration.zig+44-7
......@@ -1251,10 +1251,42 @@ pub const Step = extern struct {
12511251
12521252 pub const WriteFile = struct {
12531253 flags: @This().Flags,
1254 generated_directory: GeneratedFileIndex,
1255 embeds: Storage.FlagLengthPrefixedList(.flags, .embeds, Embed),
1256 copies: Storage.FlagLengthPrefixedList(.flags, .copies, Copy),
1257 directories: Storage.FlagLengthPrefixedList(.flags, .directories, Directory),
1258 mutate_path: Storage.EnumOptional(.flags, .mode, .mutate, LazyPath.Index),
1259
1260 pub const Embed = extern struct {
1261 sub_path: String,
1262 contents: Bytes,
1263 };
1264
1265 pub const Copy = extern struct {
1266 sub_path: String,
1267 src_file: LazyPath.Index,
1268 };
1269
1270 pub const Directory = extern struct {
1271 sub_path: String,
1272 src_path: LazyPath.Index,
1273 exclude_extensions: OptionalStringList,
1274 include_extensions: OptionalStringList,
1275 };
1276
1277 pub const Mode = enum(u2) {
1278 whole_cached,
1279 tmp,
1280 mutate,
1281 };
12541282
12551283 pub const Flags = packed struct(u32) {
12561284 tag: Tag = .write_file,
1257 _: u27 = 0,
1285 embeds: bool,
1286 copies: bool,
1287 directories: bool,
1288 mode: Mode,
1289 _: u22 = 0,
12581290 };
12591291 };
12601292
......@@ -2370,8 +2402,11 @@ pub const Storage = enum {
23702402 };
23712403 }
23722404
2373 /// A field in flags determines whether the length is zero or nonzero. If the length is
2374 /// nonzero, then there is a length field followed by the list.
2405 /// A field in flags determines whether the length is zero or nonzero. If
2406 /// the length is nonzero, then there is a length field followed by the
2407 /// list. The elements need well-defined memory layout but can otherwise be
2408 /// any multiple of u32 length. The length is the number of elements, not
2409 /// the number of u32s.
23752410 pub fn FlagLengthPrefixedList(
23762411 comptime flags_arg: @EnumLiteral(),
23772412 comptime flag_arg: @EnumLiteral(),
......@@ -2767,14 +2802,16 @@ pub const Storage = enum {
27672802 const len: u32 = @intCast(value.slice.len);
27682803 if (len == 0) return 0; // Flag bit hides the length prefix.
27692804 buffer[i] = len;
2770 @memcpy(buffer[i + 1 ..][0..len], @as([]const u32, @ptrCast(value.slice)));
2771 return len + 1;
2805 const buf_len = len * @divExact(@sizeOf(Field.Elem), @sizeOf(u32));
2806 @memcpy(buffer[i + 1 ..][0..buf_len], @as([]const u32, @ptrCast(value.slice)));
2807 return 1 + buf_len;
27722808 },
27732809 .length_prefixed_list => {
27742810 const len: u32 = @intCast(value.slice.len);
27752811 buffer[i] = len;
2776 @memcpy(buffer[i + 1 ..][0..len], @as([]const u32, @ptrCast(value.slice)));
2777 return len + 1;
2812 const buf_len = len * @divExact(@sizeOf(Field.Elem), @sizeOf(u32));
2813 @memcpy(buffer[i + 1 ..][0..buf_len], @as([]const u32, @ptrCast(value.slice)));
2814 return 1 + buf_len;
27782815 },
27792816 .flag_list => {
27802817 const len: u32 = @intCast(value.slice.len);
lib/std/Build/Step/WriteFile.zig+96-101
......@@ -13,8 +13,9 @@ const Configuration = std.Build.Configuration;
1313
1414step: Step,
1515
16files: std.ArrayList(File),
17directories: std.ArrayList(Directory),
16embeds: std.ArrayList(Embed) = .empty,
17copies: std.ArrayList(Copy) = .empty,
18directories: std.ArrayList(Directory) = .empty,
1819generated_directory: Configuration.GeneratedFileIndex,
1920mode: Mode = .whole_cached,
2021
......@@ -37,158 +38,152 @@ pub const Mode = union(enum) {
3738 mutate: std.Build.LazyPath,
3839};
3940
40pub const File = struct {
41 sub_path: []const u8,
42 contents: Contents,
43};
41pub const Embed = Configuration.Step.WriteFile.Embed;
4442
45pub const Directory = struct {
46 source: std.Build.LazyPath,
47 sub_path: []const u8,
48 options: Options,
49
50 pub const Options = struct {
51 /// File paths that end in any of these suffixes will be excluded from copying.
52 exclude_extensions: []const []const u8 = &.{},
53 /// Only file paths that end in any of these suffixes will be included in copying.
54 /// `null` means that all suffixes will be included.
55 /// `exclude_extensions` takes precedence over `include_extensions`.
56 include_extensions: ?[]const []const u8 = null,
57
58 pub fn dupe(opts: Options, graph: *std.Build.Graph) Options {
59 return .{
60 .exclude_extensions = graph.dupeStrings(opts.exclude_extensions),
61 .include_extensions = if (opts.include_extensions) |incs| graph.dupeStrings(incs) else null,
62 };
63 }
64
65 pub fn pathIncluded(opts: Options, path: []const u8) bool {
66 for (opts.exclude_extensions) |ext| {
67 if (std.mem.endsWith(u8, path, ext))
68 return false;
69 }
70 if (opts.include_extensions) |incs| {
71 for (incs) |inc| {
72 if (std.mem.endsWith(u8, path, inc))
73 return true;
74 } else {
75 return false;
76 }
77 }
78 return true;
79 }
80 };
43pub const Copy = struct {
44 sub_path: Configuration.String,
45 src_file: std.Build.LazyPath,
8146};
8247
83pub const Contents = union(enum) {
84 bytes: []const u8,
85 copy: std.Build.LazyPath,
48pub const Directory = struct {
49 sub_path: Configuration.String,
50 src_path: std.Build.LazyPath,
51 exclude_extensions: Configuration.OptionalStringList,
52 include_extensions: Configuration.OptionalStringList,
8653};
8754
8855pub fn create(owner: *std.Build) *WriteFile {
8956 const graph = owner.graph;
90 const arena = graph.arena;
91 const write_file = arena.create(WriteFile) catch @panic("OOM");
92 write_file.* = .{
93 .step = Step.init(.{
57 const wf = graph.create(WriteFile);
58 wf.* = .{
59 .step = .init(.{
9460 .tag = base_tag,
9561 .name = "WriteFile",
9662 .owner = owner,
9763 }),
98 .files = .empty,
99 .directories = .empty,
100 .generated_directory = graph.addGeneratedFile(&write_file.step),
64 .generated_directory = graph.addGeneratedFile(&wf.step),
10165 };
102 return write_file;
66 return wf;
10367}
10468
105pub fn add(write_file: *WriteFile, sub_path: []const u8, bytes: []const u8) std.Build.LazyPath {
106 const graph = write_file.step.owner.graph;
69/// Writes `contents` to a file at `sub_path` relative to the output
70/// directory.
71///
72/// `sub_path` may be a basename, or it may include subdirectories, which are
73/// created as needed.
74pub fn add(wf: *WriteFile, sub_path: []const u8, contents: []const u8) std.Build.LazyPath {
75 const graph = wf.step.owner.graph;
76 const wc = &graph.wip_configuration;
10777 const arena = graph.arena;
108 const file: File = .{
109 .sub_path = graph.dupePath(sub_path),
110 .contents = .{ .bytes = graph.dupeString(bytes) },
111 };
112 write_file.files.append(arena, file) catch @panic("OOM");
113 write_file.maybeUpdateName();
78
79 wf.embeds.append(arena, .{
80 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
81 .contents = wc.addBytes(contents) catch @panic("OOM"),
82 }) catch @panic("OOM");
83
84 wf.maybeUpdateName();
85
11486 return .{
11587 .generated = .{
116 .index = write_file.generated_directory,
117 .sub_path = file.sub_path,
88 .index = wf.generated_directory,
89 .sub_path = graph.dupeString(sub_path),
11890 },
11991 };
12092}
12193
122/// Copies the provided file into the generated directory within the local
123/// cache, along with all the rest of the files added to this step.
94/// Copies the provided file to `sub_path` relative to the output directory.
12495///
125/// `sub_path` is the destination path relative to the local cache directory
126/// associated with this WriteFile. It may be a basename, or it may include
127/// subdirectories, which are created as needed.
128pub fn addCopyFile(write_file: *WriteFile, source: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath {
129 const graph = write_file.step.owner.graph;
130 const duped_path = graph.dupePath(sub_path);
96/// `sub_path` may be a basename, or it may include subdirectories, which are
97/// created as needed.
98pub fn addCopyFile(wf: *WriteFile, src_file: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath {
99 const graph = wf.step.owner.graph;
100 const wc = &graph.wip_configuration;
131101 const arena = graph.arena;
132102
133 write_file.files.append(arena, .{
134 .sub_path = duped_path,
135 .contents = .{ .copy = source },
103 wf.copies.append(arena, .{
104 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
105 .src_file = src_file.dupe(graph),
136106 }) catch @panic("OOM");
137107
138 write_file.maybeUpdateName();
139 source.addStepDependencies(&write_file.step);
108 wf.maybeUpdateName();
109
110 src_file.addStepDependencies(&wf.step);
140111
141112 return .{ .generated = .{
142 .index = write_file.generated_directory,
143 .sub_path = duped_path,
113 .index = wf.generated_directory,
114 .sub_path = graph.dupePath(sub_path),
144115 } };
145116}
146117
118pub const CopyDirectoryOptions = struct {
119 /// File paths that end in any of these suffixes will be excluded from copying.
120 exclude_extensions: []const []const u8 = &.{},
121 /// Only file paths that end in any of these suffixes will be included in copying.
122 /// `null` means that all suffixes will be included.
123 /// `exclude_extensions` takes precedence over `include_extensions`.
124 include_extensions: ?[]const []const u8 = null,
125};
126
147127/// Copy files matching the specified exclude/include patterns to the specified
148128/// subdirectory relative to this step's generated directory.
149129///
150130/// The returned value is a lazy path to the generated subdirectory.
151131pub fn addCopyDirectory(
152 write_file: *WriteFile,
153 source: std.Build.LazyPath,
132 wf: *WriteFile,
133 src_path: std.Build.LazyPath,
154134 sub_path: []const u8,
155 options: Directory.Options,
135 options: CopyDirectoryOptions,
156136) std.Build.LazyPath {
157 const graph = write_file.step.owner.graph;
137 const graph = wf.step.owner.graph;
138 const wc = &graph.wip_configuration;
158139 const arena = graph.arena;
159 const dir = Directory{
160 .source = source.dupe(graph),
161 .sub_path = graph.dupePath(sub_path),
162 .options = options.dupe(graph),
163 };
164 write_file.directories.append(arena, dir) catch @panic("OOM");
165140
166 write_file.maybeUpdateName();
167 source.addStepDependencies(&write_file.step);
141 wf.directories.append(arena, .{
142 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
143 .src_path = src_path.dupe(graph),
144 .exclude_extensions = if (options.exclude_extensions.len != 0)
145 .init(wc.addStringList(options.exclude_extensions) catch @panic("OOM"))
146 else
147 .none,
148 .include_extensions = if (options.include_extensions) |list|
149 .init(wc.addStringList(list) catch @panic("OOM"))
150 else
151 .none,
152 }) catch @panic("OOM");
153
154 wf.maybeUpdateName();
155
156 src_path.addStepDependencies(&wf.step);
157
168158 return .{
169159 .generated = .{
170 .index = write_file.generated_directory,
171 .sub_path = dir.sub_path,
160 .index = wf.generated_directory,
161 .sub_path = graph.dupePath(sub_path),
172162 },
173163 };
174164}
175165
176166/// Returns a `LazyPath` representing the base directory that contains all the
177167/// files from this `WriteFile`.
178pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath {
179 return .{ .generated = .{ .index = write_file.generated_directory } };
168pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath {
169 return .{ .generated = .{ .index = wf.generated_directory } };
180170}
181171
182fn maybeUpdateName(write_file: *WriteFile) void {
183 if (write_file.files.items.len == 1 and write_file.directories.items.len == 0) {
172fn maybeUpdateName(wf: *WriteFile) void {
173 const graph = wf.step.owner.graph;
174 const wc = &graph.wip_configuration;
175 const files_count = wf.embeds.items.len + wf.copies.items.len;
176 if (files_count == 1 and wf.directories.items.len == 0) {
184177 // First time adding a file; update name.
185 if (std.mem.eql(u8, write_file.step.name, "WriteFile")) {
186 write_file.step.name = write_file.step.owner.fmt("WriteFile {s}", .{write_file.files.items[0].sub_path});
178 const sub_path = if (wf.embeds.items.len == 1) wf.embeds.items[0].sub_path else wf.copies.items[0].sub_path;
179 if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
180 wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wc.stringSlice(sub_path)});
187181 }
188 } else if (write_file.directories.items.len == 1 and write_file.files.items.len == 0) {
182 } else if (wf.directories.items.len == 1 and files_count == 0) {
189183 // First time adding a directory; update name.
190 if (std.mem.eql(u8, write_file.step.name, "WriteFile")) {
191 write_file.step.name = write_file.step.owner.fmt("WriteFile {s}", .{write_file.directories.items[0].sub_path});
184 const dir_name = wc.stringSlice(wf.directories.items[0].sub_path);
185 if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
186 wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{dir_name});
192187 }
193188 }
194189}