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 {...@@ -891,7 +891,47 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
891 .find_program => @panic("TODO"),891 .find_program => @panic("TODO"),
892 .fmt => @panic("TODO"),892 .fmt => @panic("TODO"),
893 .translate_c => @panic("TODO"),893 .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 },
895 .update_source_files => @panic("TODO"),935 .update_source_files => @panic("TODO"),
896 .run => e: {936 .run => e: {
897 const run: *Step.Run = @fieldParentPtr("step", step);937 const run: *Step.Run = @fieldParentPtr("step", step);
lib/std/Build/Configuration.zig+44-7
...@@ -1251,10 +1251,42 @@ pub const Step = extern struct {...@@ -1251,10 +1251,42 @@ pub const Step = extern struct {
12511251
1252 pub const WriteFile = struct {1252 pub const WriteFile = struct {
1253 flags: @This().Flags,1253 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
1255 pub const Flags = packed struct(u32) {1283 pub const Flags = packed struct(u32) {
1256 tag: Tag = .write_file,1284 tag: Tag = .write_file,
1257 _: u27 = 0,1285 embeds: bool,
1286 copies: bool,
1287 directories: bool,
1288 mode: Mode,
1289 _: u22 = 0,
1258 };1290 };
1259 };1291 };
12601292
...@@ -2370,8 +2402,11 @@ pub const Storage = enum {...@@ -2370,8 +2402,11 @@ pub const Storage = enum {
2370 };2402 };
2371 }2403 }
23722404
2373 /// A field in flags determines whether the length is zero or nonzero. If the length is2405 /// A field in flags determines whether the length is zero or nonzero. If
2374 /// nonzero, then there is a length field followed by the list.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.
2375 pub fn FlagLengthPrefixedList(2410 pub fn FlagLengthPrefixedList(
2376 comptime flags_arg: @EnumLiteral(),2411 comptime flags_arg: @EnumLiteral(),
2377 comptime flag_arg: @EnumLiteral(),2412 comptime flag_arg: @EnumLiteral(),
...@@ -2767,14 +2802,16 @@ pub const Storage = enum {...@@ -2767,14 +2802,16 @@ pub const Storage = enum {
2767 const len: u32 = @intCast(value.slice.len);2802 const len: u32 = @intCast(value.slice.len);
2768 if (len == 0) return 0; // Flag bit hides the length prefix.2803 if (len == 0) return 0; // Flag bit hides the length prefix.
2769 buffer[i] = len;2804 buffer[i] = len;
2770 @memcpy(buffer[i + 1 ..][0..len], @as([]const u32, @ptrCast(value.slice)));2805 const buf_len = len * @divExact(@sizeOf(Field.Elem), @sizeOf(u32));
2771 return len + 1;2806 @memcpy(buffer[i + 1 ..][0..buf_len], @as([]const u32, @ptrCast(value.slice)));
2807 return 1 + buf_len;
2772 },2808 },
2773 .length_prefixed_list => {2809 .length_prefixed_list => {
2774 const len: u32 = @intCast(value.slice.len);2810 const len: u32 = @intCast(value.slice.len);
2775 buffer[i] = len;2811 buffer[i] = len;
2776 @memcpy(buffer[i + 1 ..][0..len], @as([]const u32, @ptrCast(value.slice)));2812 const buf_len = len * @divExact(@sizeOf(Field.Elem), @sizeOf(u32));
2777 return len + 1;2813 @memcpy(buffer[i + 1 ..][0..buf_len], @as([]const u32, @ptrCast(value.slice)));
2814 return 1 + buf_len;
2778 },2815 },
2779 .flag_list => {2816 .flag_list => {
2780 const len: u32 = @intCast(value.slice.len);2817 const len: u32 = @intCast(value.slice.len);
lib/std/Build/Step/WriteFile.zig+96-101
...@@ -13,8 +13,9 @@ const Configuration = std.Build.Configuration;...@@ -13,8 +13,9 @@ const Configuration = std.Build.Configuration;
1313
14step: Step,14step: Step,
1515
16files: std.ArrayList(File),16embeds: std.ArrayList(Embed) = .empty,
17directories: std.ArrayList(Directory),17copies: std.ArrayList(Copy) = .empty,
18directories: std.ArrayList(Directory) = .empty,
18generated_directory: Configuration.GeneratedFileIndex,19generated_directory: Configuration.GeneratedFileIndex,
19mode: Mode = .whole_cached,20mode: Mode = .whole_cached,
2021
...@@ -37,158 +38,152 @@ pub const Mode = union(enum) {...@@ -37,158 +38,152 @@ pub const Mode = union(enum) {
37 mutate: std.Build.LazyPath,38 mutate: std.Build.LazyPath,
38};39};
3940
40pub const File = struct {41pub const Embed = Configuration.Step.WriteFile.Embed;
41 sub_path: []const u8,
42 contents: Contents,
43};
4442
45pub const Directory = struct {43pub const Copy = struct {
46 source: std.Build.LazyPath,44 sub_path: Configuration.String,
47 sub_path: []const u8,45 src_file: std.Build.LazyPath,
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 };
81};46};
8247
83pub const Contents = union(enum) {48pub const Directory = struct {
84 bytes: []const u8,49 sub_path: Configuration.String,
85 copy: std.Build.LazyPath,50 src_path: std.Build.LazyPath,
51 exclude_extensions: Configuration.OptionalStringList,
52 include_extensions: Configuration.OptionalStringList,
86};53};
8754
88pub fn create(owner: *std.Build) *WriteFile {55pub fn create(owner: *std.Build) *WriteFile {
89 const graph = owner.graph;56 const graph = owner.graph;
90 const arena = graph.arena;57 const wf = graph.create(WriteFile);
91 const write_file = arena.create(WriteFile) catch @panic("OOM");58 wf.* = .{
92 write_file.* = .{59 .step = .init(.{
93 .step = Step.init(.{
94 .tag = base_tag,60 .tag = base_tag,
95 .name = "WriteFile",61 .name = "WriteFile",
96 .owner = owner,62 .owner = owner,
97 }),63 }),
98 .files = .empty,64 .generated_directory = graph.addGeneratedFile(&wf.step),
99 .directories = .empty,
100 .generated_directory = graph.addGeneratedFile(&write_file.step),
101 };65 };
102 return write_file;66 return wf;
103}67}
10468
105pub fn add(write_file: *WriteFile, sub_path: []const u8, bytes: []const u8) std.Build.LazyPath {69/// Writes `contents` to a file at `sub_path` relative to the output
106 const graph = write_file.step.owner.graph;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;
107 const arena = graph.arena;77 const arena = graph.arena;
108 const file: File = .{78
109 .sub_path = graph.dupePath(sub_path),79 wf.embeds.append(arena, .{
110 .contents = .{ .bytes = graph.dupeString(bytes) },80 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
111 };81 .contents = wc.addBytes(contents) catch @panic("OOM"),
112 write_file.files.append(arena, file) catch @panic("OOM");82 }) catch @panic("OOM");
113 write_file.maybeUpdateName();83
84 wf.maybeUpdateName();
85
114 return .{86 return .{
115 .generated = .{87 .generated = .{
116 .index = write_file.generated_directory,88 .index = wf.generated_directory,
117 .sub_path = file.sub_path,89 .sub_path = graph.dupeString(sub_path),
118 },90 },
119 };91 };
120}92}
12193
122/// Copies the provided file into the generated directory within the local94/// Copies the provided file to `sub_path` relative to the output directory.
123/// cache, along with all the rest of the files added to this step.
124///95///
125/// `sub_path` is the destination path relative to the local cache directory96/// `sub_path` may be a basename, or it may include subdirectories, which are
126/// associated with this WriteFile. It may be a basename, or it may include97/// created as needed.
127/// subdirectories, which are created as needed.98pub fn addCopyFile(wf: *WriteFile, src_file: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath {
128pub fn addCopyFile(write_file: *WriteFile, source: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath {99 const graph = wf.step.owner.graph;
129 const graph = write_file.step.owner.graph;100 const wc = &graph.wip_configuration;
130 const duped_path = graph.dupePath(sub_path);
131 const arena = graph.arena;101 const arena = graph.arena;
132102
133 write_file.files.append(arena, .{103 wf.copies.append(arena, .{
134 .sub_path = duped_path,104 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
135 .contents = .{ .copy = source },105 .src_file = src_file.dupe(graph),
136 }) catch @panic("OOM");106 }) catch @panic("OOM");
137107
138 write_file.maybeUpdateName();108 wf.maybeUpdateName();
139 source.addStepDependencies(&write_file.step);109
110 src_file.addStepDependencies(&wf.step);
140111
141 return .{ .generated = .{112 return .{ .generated = .{
142 .index = write_file.generated_directory,113 .index = wf.generated_directory,
143 .sub_path = duped_path,114 .sub_path = graph.dupePath(sub_path),
144 } };115 } };
145}116}
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
147/// Copy files matching the specified exclude/include patterns to the specified127/// Copy files matching the specified exclude/include patterns to the specified
148/// subdirectory relative to this step's generated directory.128/// subdirectory relative to this step's generated directory.
149///129///
150/// The returned value is a lazy path to the generated subdirectory.130/// The returned value is a lazy path to the generated subdirectory.
151pub fn addCopyDirectory(131pub fn addCopyDirectory(
152 write_file: *WriteFile,132 wf: *WriteFile,
153 source: std.Build.LazyPath,133 src_path: std.Build.LazyPath,
154 sub_path: []const u8,134 sub_path: []const u8,
155 options: Directory.Options,135 options: CopyDirectoryOptions,
156) std.Build.LazyPath {136) std.Build.LazyPath {
157 const graph = write_file.step.owner.graph;137 const graph = wf.step.owner.graph;
138 const wc = &graph.wip_configuration;
158 const arena = graph.arena;139 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();141 wf.directories.append(arena, .{
167 source.addStepDependencies(&write_file.step);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
168 return .{158 return .{
169 .generated = .{159 .generated = .{
170 .index = write_file.generated_directory,160 .index = wf.generated_directory,
171 .sub_path = dir.sub_path,161 .sub_path = graph.dupePath(sub_path),
172 },162 },
173 };163 };
174}164}
175165
176/// Returns a `LazyPath` representing the base directory that contains all the166/// Returns a `LazyPath` representing the base directory that contains all the
177/// files from this `WriteFile`.167/// files from this `WriteFile`.
178pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath {168pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath {
179 return .{ .generated = .{ .index = write_file.generated_directory } };169 return .{ .generated = .{ .index = wf.generated_directory } };
180}170}
181171
182fn maybeUpdateName(write_file: *WriteFile) void {172fn maybeUpdateName(wf: *WriteFile) void {
183 if (write_file.files.items.len == 1 and write_file.directories.items.len == 0) {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) {
184 // First time adding a file; update name.177 // First time adding a file; update name.
185 if (std.mem.eql(u8, write_file.step.name, "WriteFile")) {178 const sub_path = if (wf.embeds.items.len == 1) wf.embeds.items[0].sub_path else wf.copies.items[0].sub_path;
186 write_file.step.name = write_file.step.owner.fmt("WriteFile {s}", .{write_file.files.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)});
187 }181 }
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) {
189 // First time adding a directory; update name.183 // First time adding a directory; update name.
190 if (std.mem.eql(u8, write_file.step.name, "WriteFile")) {184 const dir_name = wc.stringSlice(wf.directories.items[0].sub_path);
191 write_file.step.name = write_file.step.owner.fmt("WriteFile {s}", .{write_file.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});
192 }187 }
193 }188 }
194}189}