1//! WriteFile is used to create a directory in an appropriate location inside
2//! the local cache which has a set of files that have either been generated
3//! during the build, or are copied from the source package.
4const WriteFile = @This();
5
6const std = @import("std");
7const Step = std.Build.Step;
8const Configuration = std.Build.Configuration;
9
10step: Step,
11embeds: std.ArrayList(Embed) = .empty,
12copies: std.ArrayList(Copy) = .empty,
13directories: std.ArrayList(Directory) = .empty,
14generated_directory: Configuration.GeneratedFileIndex,
15mode: Mode = .whole_cached,
16
17pub const base_tag: Step.Tag = .write_file;
18
19pub const Mode = union(enum) {
20 /// Default mode. Integrates with the cache system. The directory should be
21 /// read-only during the make phase. Any different inputs result in
22 /// different "o" subdirectory.
23 whole_cached,
24 /// In this mode, the directory will be placed inside "tmp" rather than
25 /// "o", and caching will be skipped. During the `make` phase, the step
26 /// will always do all the file system operations, and on successful build
27 /// completion, the dir will be deleted along with all other tmp
28 /// directories. The directory is therefore eligible to be used for
29 /// mutations by other steps.
30 tmp,
31 /// The operations will not be performed against a freshly created
32 /// directory, but instead act against a temporary directory.
33 mutate: std.Build.LazyPath,
34};
35
36pub const Embed = Configuration.Step.WriteFile.Embed;
37
38pub const Copy = struct {
39 sub_path: Configuration.String,
40 src_file: std.Build.LazyPath,
41};
42
43pub const Directory = struct {
44 sub_path: Configuration.String,
45 src_path: std.Build.LazyPath,
46 exclude_extensions: Configuration.OptionalStringList,
47 include_extensions: Configuration.OptionalStringList,
48};
49
50pub fn create(owner: *std.Build) *WriteFile {
51 const graph = owner.graph;
52 const wf = graph.create(WriteFile);
53 wf.* = .{
54 .step = .init(.{
55 .tag = base_tag,
56 .name = "WriteFile",
57 .owner = owner,
58 }),
59 .generated_directory = graph.addGeneratedFile(&wf.step),
60 };
61 return wf;
62}
63
64/// Writes `contents` to a file at `sub_path` relative to the output
65/// directory.
66///
67/// `sub_path` may be a basename, or it may include subdirectories, which are
68/// created as needed.
69pub fn add(wf: *WriteFile, sub_path: []const u8, contents: []const u8) std.Build.LazyPath {
70 const graph = wf.step.owner.graph;
71 const wc = &graph.wip_configuration;
72 const arena = graph.arena;
73
74 wf.embeds.append(arena, .{
75 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
76 .contents = wc.addBytes(contents) catch @panic("OOM"),
77 }) catch @panic("OOM");
78
79 wf.maybeUpdateName();
80
81 return .{
82 .generated = .{
83 .index = wf.generated_directory,
84 .sub_path = graph.dupeString(sub_path),
85 },
86 };
87}
88
89/// Copies the provided file to `sub_path` relative to the output directory.
90///
91/// `sub_path` may be a basename, or it may include subdirectories, which are
92/// created as needed.
93pub fn addCopyFile(wf: *WriteFile, src_file: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath {
94 const graph = wf.step.owner.graph;
95 const wc = &graph.wip_configuration;
96 const arena = graph.arena;
97
98 wf.copies.append(arena, .{
99 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
100 .src_file = src_file.dupe(graph),
101 }) catch @panic("OOM");
102
103 wf.maybeUpdateName();
104
105 src_file.addStepDependencies(&wf.step);
106
107 return .{ .generated = .{
108 .index = wf.generated_directory,
109 .sub_path = graph.dupePath(sub_path),
110 } };
111}
112
113pub const CopyDirectoryOptions = struct {
114 /// File paths that end in any of these suffixes will be excluded from copying.
115 exclude_extensions: []const []const u8 = &.{},
116 /// Only file paths that end in any of these suffixes will be included in copying.
117 /// `null` means that all suffixes will be included.
118 /// `exclude_extensions` takes precedence over `include_extensions`.
119 include_extensions: ?[]const []const u8 = null,
120};
121
122/// Copy files matching the specified exclude/include patterns to the specified
123/// subdirectory relative to this step's generated directory.
124///
125/// The returned value is a lazy path to the generated subdirectory.
126pub fn addCopyDirectory(
127 wf: *WriteFile,
128 src_path: std.Build.LazyPath,
129 sub_path: []const u8,
130 options: CopyDirectoryOptions,
131) std.Build.LazyPath {
132 const graph = wf.step.owner.graph;
133 const wc = &graph.wip_configuration;
134 const arena = graph.arena;
135
136 wf.directories.append(arena, .{
137 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
138 .src_path = src_path.dupe(graph),
139 .exclude_extensions = if (options.exclude_extensions.len != 0)
140 .init(wc.addStringList(options.exclude_extensions) catch @panic("OOM"))
141 else
142 .none,
143 .include_extensions = if (options.include_extensions) |list|
144 .init(wc.addStringList(list) catch @panic("OOM"))
145 else
146 .none,
147 }) catch @panic("OOM");
148
149 wf.maybeUpdateName();
150
151 src_path.addStepDependencies(&wf.step);
152
153 return .{
154 .generated = .{
155 .index = wf.generated_directory,
156 .sub_path = graph.dupePath(sub_path),
157 },
158 };
159}
160
161/// Returns a `LazyPath` representing the base directory that contains all the
162/// files from this `WriteFile`.
163pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath {
164 return .{ .generated = .{ .index = wf.generated_directory } };
165}
166
167fn maybeUpdateName(wf: *WriteFile) void {
168 const graph = wf.step.owner.graph;
169 const wc = &graph.wip_configuration;
170 const files_count = wf.embeds.items.len + wf.copies.items.len;
171 if (files_count == 1 and wf.directories.items.len == 0) {
172 // First time adding a file; update name.
173 const sub_path = if (wf.embeds.items.len == 1) wf.embeds.items[0].sub_path else wf.copies.items[0].sub_path;
174 if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
175 wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wc.stringSlice(sub_path)});
176 }
177 } else if (wf.directories.items.len == 1 and files_count == 0) {
178 // First time adding a directory; update name.
179 const dir_name = wc.stringSlice(wf.directories.items[0].sub_path);
180 if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
181 wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{dir_name});
182 }
183 }
184}