master
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 UpdateSourceFiles = @This();
7
8const std = @import("std");
9const Io = std.Io;
10const Step = std.Build.Step;
11const fs = std.fs;
12const ArrayList = std.ArrayList;
13
14step: Step,
15output_source_files: std.ArrayList(OutputSourceFile),
16
17pub const base_id: Step.Id = .update_source_files;
18
19pub const OutputSourceFile = struct {
20 contents: Contents,
21 sub_path: []const u8,
22};
23
24pub const Contents = union(enum) {
25 bytes: []const u8,
26 copy: std.Build.LazyPath,
27};
28
29pub fn create(owner: *std.Build) *UpdateSourceFiles {
30 const usf = owner.allocator.create(UpdateSourceFiles) catch @panic("OOM");
31 usf.* = .{
32 .step = Step.init(.{
33 .id = base_id,
34 .name = "UpdateSourceFiles",
35 .owner = owner,
36 .makeFn = make,
37 }),
38 .output_source_files = .{},
39 };
40 return usf;
41}
42
43/// A path relative to the package root.
44///
45/// Be careful with this because it updates source files. This should not be
46/// used as part of the normal build process, but as a utility occasionally
47/// run by a developer with intent to modify source files and then commit
48/// those changes to version control.
49pub fn addCopyFileToSource(usf: *UpdateSourceFiles, source: std.Build.LazyPath, sub_path: []const u8) void {
50 const b = usf.step.owner;
51 usf.output_source_files.append(b.allocator, .{
52 .contents = .{ .copy = source },
53 .sub_path = sub_path,
54 }) catch @panic("OOM");
55 source.addStepDependencies(&usf.step);
56}
57
58/// A path relative to the package root.
59///
60/// Be careful with this because it updates source files. This should not be
61/// used as part of the normal build process, but as a utility occasionally
62/// run by a developer with intent to modify source files and then commit
63/// those changes to version control.
64pub fn addBytesToSource(usf: *UpdateSourceFiles, bytes: []const u8, sub_path: []const u8) void {
65 const b = usf.step.owner;
66 usf.output_source_files.append(b.allocator, .{
67 .contents = .{ .bytes = bytes },
68 .sub_path = sub_path,
69 }) catch @panic("OOM");
70}
71
72fn make(step: *Step, options: Step.MakeOptions) !void {
73 _ = options;
74 const b = step.owner;
75 const io = b.graph.io;
76 const usf: *UpdateSourceFiles = @fieldParentPtr("step", step);
77
78 var any_miss = false;
79 for (usf.output_source_files.items) |output_source_file| {
80 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
81 b.build_root.handle.makePath(dirname) catch |err| {
82 return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err });
83 };
84 }
85 switch (output_source_file.contents) {
86 .bytes => |bytes| {
87 b.build_root.handle.writeFile(.{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {
88 return step.fail("unable to write file '{f}{s}': {t}", .{
89 b.build_root, output_source_file.sub_path, err,
90 });
91 };
92 any_miss = true;
93 },
94 .copy => |file_source| {
95 if (!step.inputs.populated()) try step.addWatchInput(file_source);
96
97 const source_path = file_source.getPath2(b, step);
98 const prev_status = Io.Dir.updateFile(
99 .cwd(),
100 io,
101 source_path,
102 b.build_root.handle.adaptToNewApi(),
103 output_source_file.sub_path,
104 .{},
105 ) catch |err| {
106 return step.fail("unable to update file from '{s}' to '{f}{s}': {t}", .{
107 source_path, b.build_root, output_source_file.sub_path, err,
108 });
109 };
110 any_miss = any_miss or prev_status == .stale;
111 },
112 }
113 }
114
115 step.result_cached = !any_miss;
116}