authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-09 21:47:26-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-12 00:14:08-07:00
logd1c14f2f52ddec476eca6d605b985a27f4d4fe28
tree34368ed797390d5e92a2b2125da1a3c1920aaebb
parent0cc492a272ef9c03a34b57a26bf570b242615ddf

std.Build.Step.WriteFile: extract UpdateSourceFiles

This has been planned for quite some time; this commit finally does it. Also implements file system watching integration in the make() implementation for UpdateSourceFiles and fixes the reporting of step caching for both. WriteFile does not yet have file system watching integration.

6 files changed, 128 insertions(+), 87 deletions(-)

build.zig+1-1
...@@ -595,7 +595,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {...@@ -595,7 +595,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
595 run_opt.addArg("-o");595 run_opt.addArg("-o");
596 run_opt.addFileArg(b.path("stage1/zig1.wasm"));596 run_opt.addFileArg(b.path("stage1/zig1.wasm"));
597597
598 const copy_zig_h = b.addWriteFiles();598 const copy_zig_h = b.addUpdateSourceFiles();
599 copy_zig_h.addCopyFileToSource(b.path("lib/zig.h"), "stage1/zig.h");599 copy_zig_h.addCopyFileToSource(b.path("lib/zig.h"), "stage1/zig.h");
600600
601 const update_zig1_step = b.step("update-zig1", "Update stage1/zig1.wasm");601 const update_zig1_step = b.step("update-zig1", "Update stage1/zig1.wasm");
lib/std/Build.zig+4
...@@ -1052,6 +1052,10 @@ pub fn addWriteFiles(b: *Build) *Step.WriteFile {...@@ -1052,6 +1052,10 @@ pub fn addWriteFiles(b: *Build) *Step.WriteFile {
1052 return Step.WriteFile.create(b);1052 return Step.WriteFile.create(b);
1053}1053}
10541054
1055pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles {
1056 return Step.UpdateSourceFiles.create(b);
1057}
1058
1055pub fn addRemoveDirTree(b: *Build, dir_path: LazyPath) *Step.RemoveDir {1059pub fn addRemoveDirTree(b: *Build, dir_path: LazyPath) *Step.RemoveDir {
1056 return Step.RemoveDir.create(b, dir_path);1060 return Step.RemoveDir.create(b, dir_path);
1057}1061}
lib/std/Build/Step.zig+4
...@@ -102,6 +102,7 @@ pub const Id = enum {...@@ -102,6 +102,7 @@ pub const Id = enum {
102 fmt,102 fmt,
103 translate_c,103 translate_c,
104 write_file,104 write_file,
105 update_source_files,
105 run,106 run,
106 check_file,107 check_file,
107 check_object,108 check_object,
...@@ -122,6 +123,7 @@ pub const Id = enum {...@@ -122,6 +123,7 @@ pub const Id = enum {
122 .fmt => Fmt,123 .fmt => Fmt,
123 .translate_c => TranslateC,124 .translate_c => TranslateC,
124 .write_file => WriteFile,125 .write_file => WriteFile,
126 .update_source_files => UpdateSourceFiles,
125 .run => Run,127 .run => Run,
126 .check_file => CheckFile,128 .check_file => CheckFile,
127 .check_object => CheckObject,129 .check_object => CheckObject,
...@@ -148,6 +150,7 @@ pub const RemoveDir = @import("Step/RemoveDir.zig");...@@ -148,6 +150,7 @@ pub const RemoveDir = @import("Step/RemoveDir.zig");
148pub const Run = @import("Step/Run.zig");150pub const Run = @import("Step/Run.zig");
149pub const TranslateC = @import("Step/TranslateC.zig");151pub const TranslateC = @import("Step/TranslateC.zig");
150pub const WriteFile = @import("Step/WriteFile.zig");152pub const WriteFile = @import("Step/WriteFile.zig");
153pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");
151154
152pub const Inputs = struct {155pub const Inputs = struct {
153 table: Table,156 table: Table,
...@@ -680,4 +683,5 @@ test {...@@ -680,4 +683,5 @@ test {
680 _ = Run;683 _ = Run;
681 _ = TranslateC;684 _ = TranslateC;
682 _ = WriteFile;685 _ = WriteFile;
686 _ = UpdateSourceFiles;
683}687}
lib/std/Build/Step/UpdateSourceFiles.zig created+114
...@@ -0,0 +1,114 @@
1//! Writes data to paths relative to the package root, effectively mutating the
2//! package's source files. Be careful with the latter functionality; it should
3//! not be used during the normal build process, but as a utility run by a
4//! developer with intention to update source files, which will then be
5//! committed to version control.
6const std = @import("std");
7const Step = std.Build.Step;
8const fs = std.fs;
9const ArrayList = std.ArrayList;
10const UpdateSourceFiles = @This();
11
12step: Step,
13output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
14
15pub const base_id: Step.Id = .update_source_files;
16
17pub const OutputSourceFile = struct {
18 contents: Contents,
19 sub_path: []const u8,
20};
21
22pub const Contents = union(enum) {
23 bytes: []const u8,
24 copy: std.Build.LazyPath,
25};
26
27pub fn create(owner: *std.Build) *UpdateSourceFiles {
28 const usf = owner.allocator.create(UpdateSourceFiles) catch @panic("OOM");
29 usf.* = .{
30 .step = Step.init(.{
31 .id = base_id,
32 .name = "UpdateSourceFiles",
33 .owner = owner,
34 .makeFn = make,
35 }),
36 .output_source_files = .{},
37 };
38 return usf;
39}
40
41/// A path relative to the package root.
42///
43/// Be careful with this because it updates source files. This should not be
44/// used as part of the normal build process, but as a utility occasionally
45/// run by a developer with intent to modify source files and then commit
46/// those changes to version control.
47pub fn addCopyFileToSource(usf: *UpdateSourceFiles, source: std.Build.LazyPath, sub_path: []const u8) void {
48 const b = usf.step.owner;
49 usf.output_source_files.append(b.allocator, .{
50 .contents = .{ .copy = source },
51 .sub_path = sub_path,
52 }) catch @panic("OOM");
53 source.addStepDependencies(&usf.step);
54}
55
56/// A path relative to the package root.
57///
58/// Be careful with this because it updates source files. This should not be
59/// used as part of the normal build process, but as a utility occasionally
60/// run by a developer with intent to modify source files and then commit
61/// those changes to version control.
62pub fn addBytesToSource(usf: *UpdateSourceFiles, bytes: []const u8, sub_path: []const u8) void {
63 const b = usf.step.owner;
64 usf.output_source_files.append(b.allocator, .{
65 .contents = .{ .bytes = bytes },
66 .sub_path = sub_path,
67 }) catch @panic("OOM");
68}
69
70fn make(step: *Step, prog_node: std.Progress.Node) !void {
71 _ = prog_node;
72 const b = step.owner;
73 const usf: *UpdateSourceFiles = @fieldParentPtr("step", step);
74
75 var any_miss = false;
76 for (usf.output_source_files.items) |output_source_file| {
77 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
78 b.build_root.handle.makePath(dirname) catch |err| {
79 return step.fail("unable to make path '{}{s}': {s}", .{
80 b.build_root, dirname, @errorName(err),
81 });
82 };
83 }
84 switch (output_source_file.contents) {
85 .bytes => |bytes| {
86 b.build_root.handle.writeFile(.{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {
87 return step.fail("unable to write file '{}{s}': {s}", .{
88 b.build_root, output_source_file.sub_path, @errorName(err),
89 });
90 };
91 any_miss = true;
92 },
93 .copy => |file_source| {
94 if (!step.inputs.populated()) try step.addWatchInput(file_source);
95
96 const source_path = file_source.getPath2(b, step);
97 const prev_status = fs.Dir.updateFile(
98 fs.cwd(),
99 source_path,
100 b.build_root.handle,
101 output_source_file.sub_path,
102 .{},
103 ) catch |err| {
104 return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{
105 source_path, b.build_root, output_source_file.sub_path, @errorName(err),
106 });
107 };
108 any_miss = any_miss or prev_status == .stale;
109 },
110 }
111 }
112
113 step.result_cached = !any_miss;
114}
lib/std/Build/Step/WriteFile.zig+4-85
...@@ -1,13 +1,6 @@...@@ -1,13 +1,6 @@
1//! WriteFile is primarily used to create a directory in an appropriate1//! WriteFile is used to create a directory in an appropriate location inside
2//! location inside the local cache which has a set of files that have either2//! the local cache which has a set of files that have either been generated
3//! been generated during the build, or are copied from the source package.3//! during the build, or are copied from the source package.
4//!
5//! However, this step has an additional capability of writing data to paths
6//! relative to the package root, effectively mutating the package's source
7//! files. Be careful with the latter functionality; it should not be used
8//! during the normal build process, but as a utility run by a developer with
9//! intention to update source files, which will then be committed to version
10//! control.
11const std = @import("std");4const std = @import("std");
12const Step = std.Build.Step;5const Step = std.Build.Step;
13const fs = std.fs;6const fs = std.fs;
...@@ -19,8 +12,6 @@ step: Step,...@@ -19,8 +12,6 @@ step: Step,
19// The elements here are pointers because we need stable pointers for the GeneratedFile field.12// The elements here are pointers because we need stable pointers for the GeneratedFile field.
20files: std.ArrayListUnmanaged(File),13files: std.ArrayListUnmanaged(File),
21directories: std.ArrayListUnmanaged(Directory),14directories: std.ArrayListUnmanaged(Directory),
22
23output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
24generated_directory: std.Build.GeneratedFile,15generated_directory: std.Build.GeneratedFile,
2516
26pub const base_id: Step.Id = .write_file;17pub const base_id: Step.Id = .write_file;
...@@ -52,11 +43,6 @@ pub const Directory = struct {...@@ -52,11 +43,6 @@ pub const Directory = struct {
52 };43 };
53};44};
5445
55pub const OutputSourceFile = struct {
56 contents: Contents,
57 sub_path: []const u8,
58};
59
60pub const Contents = union(enum) {46pub const Contents = union(enum) {
61 bytes: []const u8,47 bytes: []const u8,
62 copy: std.Build.LazyPath,48 copy: std.Build.LazyPath,
...@@ -73,7 +59,6 @@ pub fn create(owner: *std.Build) *WriteFile {...@@ -73,7 +59,6 @@ pub fn create(owner: *std.Build) *WriteFile {
73 }),59 }),
74 .files = .{},60 .files = .{},
75 .directories = .{},61 .directories = .{},
76 .output_source_files = .{},
77 .generated_directory = .{ .step = &write_file.step },62 .generated_directory = .{ .step = &write_file.step },
78 };63 };
79 return write_file;64 return write_file;
...@@ -150,33 +135,6 @@ pub fn addCopyDirectory(...@@ -150,33 +135,6 @@ pub fn addCopyDirectory(
150 };135 };
151}136}
152137
153/// A path relative to the package root.
154/// Be careful with this because it updates source files. This should not be
155/// used as part of the normal build process, but as a utility occasionally
156/// run by a developer with intent to modify source files and then commit
157/// those changes to version control.
158pub fn addCopyFileToSource(write_file: *WriteFile, source: std.Build.LazyPath, sub_path: []const u8) void {
159 const b = write_file.step.owner;
160 write_file.output_source_files.append(b.allocator, .{
161 .contents = .{ .copy = source },
162 .sub_path = sub_path,
163 }) catch @panic("OOM");
164 source.addStepDependencies(&write_file.step);
165}
166
167/// A path relative to the package root.
168/// Be careful with this because it updates source files. This should not be
169/// used as part of the normal build process, but as a utility occasionally
170/// run by a developer with intent to modify source files and then commit
171/// those changes to version control.
172pub fn addBytesToSource(write_file: *WriteFile, bytes: []const u8, sub_path: []const u8) void {
173 const b = write_file.step.owner;
174 write_file.output_source_files.append(b.allocator, .{
175 .contents = .{ .bytes = bytes },
176 .sub_path = sub_path,
177 }) catch @panic("OOM");
178}
179
180/// Returns a `LazyPath` representing the base directory that contains all the138/// Returns a `LazyPath` representing the base directory that contains all the
181/// files from this `WriteFile`.139/// files from this `WriteFile`.
182pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath {140pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath {
...@@ -202,46 +160,6 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -202,46 +160,6 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
202 const b = step.owner;160 const b = step.owner;
203 const write_file: *WriteFile = @fieldParentPtr("step", step);161 const write_file: *WriteFile = @fieldParentPtr("step", step);
204162
205 // Writing to source files is kind of an extra capability of this
206 // WriteFile - arguably it should be a different step. But anyway here
207 // it is, it happens unconditionally and does not interact with the other
208 // files here.
209 var any_miss = false;
210 for (write_file.output_source_files.items) |output_source_file| {
211 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
212 b.build_root.handle.makePath(dirname) catch |err| {
213 return step.fail("unable to make path '{}{s}': {s}", .{
214 b.build_root, dirname, @errorName(err),
215 });
216 };
217 }
218 switch (output_source_file.contents) {
219 .bytes => |bytes| {
220 b.build_root.handle.writeFile(.{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {
221 return step.fail("unable to write file '{}{s}': {s}", .{
222 b.build_root, output_source_file.sub_path, @errorName(err),
223 });
224 };
225 any_miss = true;
226 },
227 .copy => |file_source| {
228 const source_path = file_source.getPath2(b, step);
229 const prev_status = fs.Dir.updateFile(
230 fs.cwd(),
231 source_path,
232 b.build_root.handle,
233 output_source_file.sub_path,
234 .{},
235 ) catch |err| {
236 return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{
237 source_path, b.build_root, output_source_file.sub_path, @errorName(err),
238 });
239 };
240 any_miss = any_miss or prev_status == .stale;
241 },
242 }
243 }
244
245 // The cache is used here not really as a way to speed things up - because writing163 // The cache is used here not really as a way to speed things up - because writing
246 // the data to a file would probably be very fast - but as a way to find a canonical164 // the data to a file would probably be very fast - but as a way to find a canonical
247 // location to put build artifacts.165 // location to put build artifacts.
...@@ -278,6 +196,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {...@@ -278,6 +196,7 @@ fn make(step: *Step, prog_node: std.Progress.Node) !void {
278 if (try step.cacheHit(&man)) {196 if (try step.cacheHit(&man)) {
279 const digest = man.final();197 const digest = man.final();
280 write_file.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });198 write_file.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
199 step.result_cached = true;
281 return;200 return;
282 }201 }
283202
test/tests.zig+1-1
...@@ -882,7 +882,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -882,7 +882,7 @@ pub fn addCliTests(b: *std.Build) *Step {
882882
883 const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";883 const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";
884 const fmt6_path = std.fs.path.join(b.allocator, &.{ tmp_path, "fmt6.zig" }) catch @panic("OOM");884 const fmt6_path = std.fs.path.join(b.allocator, &.{ tmp_path, "fmt6.zig" }) catch @panic("OOM");
885 const write6 = b.addWriteFiles();885 const write6 = b.addUpdateSourceFiles();
886 write6.addBytesToSource(unformatted_code_utf16, fmt6_path);886 write6.addBytesToSource(unformatted_code_utf16, fmt6_path);
887 write6.step.dependOn(&run5.step);887 write6.step.dependOn(&run5.step);
888888