| 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. |
| 4 | const WriteFile = @This(); |
| 5 | |
| 6 | const std = @import("std"); |
| 7 | const Step = std.Build.Step; |
| 8 | const Configuration = std.Build.Configuration; |
| 9 | |
| 10 | step: Step, |
| 11 | embeds: std.ArrayList(Embed) = .empty, |
| 12 | copies: std.ArrayList(Copy) = .empty, |
| 13 | directories: std.ArrayList(Directory) = .empty, |
| 14 | generated_directory: Configuration.GeneratedFileIndex, |
| 15 | mode: Mode = .whole_cached, |
| 16 | |
| 17 | pub const base_tag: Step.Tag = .write_file; |
| 18 | |
| 19 | pub 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 | |
| 36 | pub const Embed = Configuration.Step.WriteFile.Embed; |
| 37 | |
| 38 | pub const Copy = struct { |
| 39 | sub_path: Configuration.String, |
| 40 | src_file: std.Build.LazyPath, |
| 41 | }; |
| 42 | |
| 43 | pub 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 | |
| 50 | pub 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. |
| 69 | pub 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. |
| 93 | pub 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 | |
| 113 | pub 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. |
| 126 | pub 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`. |
| 163 | pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath { |
| 164 | return .{ .generated = .{ .index = wf.generated_directory } }; |
| 165 | } |
| 166 | |
| 167 | fn 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 | } |