| ... | ... | @@ -15,9 +15,11 @@ const ArrayList = std.ArrayList; |
| 15 | 15 | const WriteFile = @This(); |
| 16 | 16 | |
| 17 | 17 | step: Step, |
| 18 | | /// The elements here are pointers because we need stable pointers for the |
| 19 | | /// GeneratedFile field. |
| 18 | |
| 19 | // The elements here are pointers because we need stable pointers for the GeneratedFile field. |
| 20 | 20 | files: std.ArrayListUnmanaged(*File), |
| 21 | directories: std.ArrayListUnmanaged(*Directory), |
| 22 | |
| 21 | 23 | output_source_files: std.ArrayListUnmanaged(OutputSourceFile), |
| 22 | 24 | generated_directory: std.Build.GeneratedFile, |
| 23 | 25 | |
| ... | ... | @@ -33,6 +35,33 @@ pub const File = struct { |
| 33 | 35 | } |
| 34 | 36 | }; |
| 35 | 37 | |
| 38 | pub const Directory = struct { |
| 39 | source: std.Build.LazyPath, |
| 40 | sub_path: []const u8, |
| 41 | options: Options, |
| 42 | generated_dir: std.Build.GeneratedFile, |
| 43 | |
| 44 | pub const Options = struct { |
| 45 | /// File paths that end in any of these suffixes will be excluded from copying. |
| 46 | exclude_extensions: []const []const u8 = &.{}, |
| 47 | /// Only file paths that end in any of these suffixes will be included in copying. |
| 48 | /// `null` means that all suffixes will be included. |
| 49 | /// `exclude_extensions` takes precedence over `include_extensions`. |
| 50 | include_extensions: ?[]const []const u8 = &.{".h"}, |
| 51 | |
| 52 | pub fn dupe(self: Options, b: *std.Build) Options { |
| 53 | return .{ |
| 54 | .exclude_extensions = b.dupeStrings(self.exclude_extensions), |
| 55 | .include_extensions = if (self.include_extensions) |incs| b.dupeStrings(incs) else null, |
| 56 | }; |
| 57 | } |
| 58 | }; |
| 59 | |
| 60 | pub fn getPath(self: *Directory) std.Build.LazyPath { |
| 61 | return .{ .generated = &self.generated_dir }; |
| 62 | } |
| 63 | }; |
| 64 | |
| 36 | 65 | pub const OutputSourceFile = struct { |
| 37 | 66 | contents: Contents, |
| 38 | 67 | sub_path: []const u8, |
| ... | ... | @@ -53,6 +82,7 @@ pub fn create(owner: *std.Build) *WriteFile { |
| 53 | 82 | .makeFn = make, |
| 54 | 83 | }), |
| 55 | 84 | .files = .{}, |
| 85 | .directories = .{}, |
| 56 | 86 | .output_source_files = .{}, |
| 57 | 87 | .generated_directory = .{ .step = &wf.step }, |
| 58 | 88 | }; |
| ... | ... | @@ -96,6 +126,28 @@ pub fn addCopyFile(wf: *WriteFile, source: std.Build.LazyPath, sub_path: []const |
| 96 | 126 | return file.getPath(); |
| 97 | 127 | } |
| 98 | 128 | |
| 129 | pub fn addCopyDirectory( |
| 130 | wf: *WriteFile, |
| 131 | source: std.Build.LazyPath, |
| 132 | sub_path: []const u8, |
| 133 | options: Directory.Options, |
| 134 | ) std.Build.LazyPath { |
| 135 | const b = wf.step.owner; |
| 136 | const gpa = b.allocator; |
| 137 | const dir = gpa.create(Directory) catch @panic("OOM"); |
| 138 | dir.* = .{ |
| 139 | .source = source.dupe(b), |
| 140 | .sub_path = b.dupePath(sub_path), |
| 141 | .options = options.dupe(b), |
| 142 | .generated_dir = .{ .step = &wf.step }, |
| 143 | }; |
| 144 | wf.directories.append(gpa, dir) catch @panic("OOM"); |
| 145 | |
| 146 | wf.maybeUpdateName(); |
| 147 | source.addStepDependencies(&wf.step); |
| 148 | return dir.getPath(); |
| 149 | } |
| 150 | |
| 99 | 151 | /// A path relative to the package root. |
| 100 | 152 | /// Be careful with this because it updates source files. This should not be |
| 101 | 153 | /// used as part of the normal build process, but as a utility occasionally |
| ... | ... | @@ -130,11 +182,16 @@ pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath { |
| 130 | 182 | } |
| 131 | 183 | |
| 132 | 184 | fn maybeUpdateName(wf: *WriteFile) void { |
| 133 | | if (wf.files.items.len == 1) { |
| 185 | if (wf.files.items.len == 1 and wf.directories.items.len == 0) { |
| 134 | 186 | // First time adding a file; update name. |
| 135 | 187 | if (std.mem.eql(u8, wf.step.name, "WriteFile")) { |
| 136 | 188 | wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path}); |
| 137 | 189 | } |
| 190 | } else if (wf.directories.items.len == 1 and wf.files.items.len == 0) { |
| 191 | // First time adding a directory; update name. |
| 192 | if (std.mem.eql(u8, wf.step.name, "WriteFile")) { |
| 193 | wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.directories.items[0].sub_path}); |
| 194 | } |
| 138 | 195 | } |
| 139 | 196 | } |
| 140 | 197 | |
| ... | ... | @@ -209,6 +266,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { |
| 209 | 266 | }, |
| 210 | 267 | } |
| 211 | 268 | } |
| 269 | for (wf.directories.items) |dir| { |
| 270 | man.hash.addBytes(dir.source.getPath2(b, step)); |
| 271 | man.hash.addBytes(dir.sub_path); |
| 272 | for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext); |
| 273 | if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc); |
| 274 | } |
| 212 | 275 | |
| 213 | 276 | if (try step.cacheHit(&man)) { |
| 214 | 277 | const digest = man.final(); |
| ... | ... | @@ -233,6 +296,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { |
| 233 | 296 | }; |
| 234 | 297 | defer cache_dir.close(); |
| 235 | 298 | |
| 299 | const cwd = fs.cwd(); |
| 300 | |
| 236 | 301 | for (wf.files.items) |file| { |
| 237 | 302 | if (fs.path.dirname(file.sub_path)) |dirname| { |
| 238 | 303 | cache_dir.makePath(dirname) catch |err| { |
| ... | ... | @@ -252,7 +317,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { |
| 252 | 317 | .copy => |file_source| { |
| 253 | 318 | const source_path = file_source.getPath(b); |
| 254 | 319 | const prev_status = fs.Dir.updateFile( |
| 255 | | fs.cwd(), |
| 320 | cwd, |
| 256 | 321 | source_path, |
| 257 | 322 | cache_dir, |
| 258 | 323 | file.sub_path, |
| ... | ... | @@ -279,6 +344,64 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { |
| 279 | 344 | cache_path, file.sub_path, |
| 280 | 345 | }); |
| 281 | 346 | } |
| 347 | for (wf.directories.items) |dir| { |
| 348 | const full_src_dir_path = dir.source.getPath2(b, step); |
| 349 | const dest_dirname = dir.sub_path; |
| 350 | |
| 351 | if (dest_dirname.len != 0) { |
| 352 | cache_dir.makePath(dest_dirname) catch |err| { |
| 353 | return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{ |
| 354 | b.cache_root, cache_path, fs.path.sep, dest_dirname, @errorName(err), |
| 355 | }); |
| 356 | }; |
| 357 | } |
| 358 | |
| 359 | var src_dir = b.build_root.handle.openDir(full_src_dir_path, .{ .iterate = true }) catch |err| { |
| 360 | return step.fail("unable to open source directory '{s}': {s}", .{ |
| 361 | full_src_dir_path, @errorName(err), |
| 362 | }); |
| 363 | }; |
| 364 | defer src_dir.close(); |
| 365 | |
| 366 | var it = try src_dir.walk(b.allocator); |
| 367 | next_entry: while (try it.next()) |entry| { |
| 368 | for (dir.options.exclude_extensions) |ext| { |
| 369 | if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry; |
| 370 | } |
| 371 | if (dir.options.include_extensions) |incs| { |
| 372 | for (incs) |inc| { |
| 373 | if (std.mem.endsWith(u8, entry.path, inc)) break; |
| 374 | } else { |
| 375 | continue :next_entry; |
| 376 | } |
| 377 | } |
| 378 | const full_src_entry_path = b.pathJoin(&.{ full_src_dir_path, entry.path }); |
| 379 | const dest_path = b.pathJoin(&.{ dest_dirname, entry.path }); |
| 380 | switch (entry.kind) { |
| 381 | .directory => try cache_dir.makePath(dest_path), |
| 382 | .file => { |
| 383 | const prev_status = fs.Dir.updateFile( |
| 384 | cwd, |
| 385 | full_src_entry_path, |
| 386 | cache_dir, |
| 387 | dest_path, |
| 388 | .{}, |
| 389 | ) catch |err| { |
| 390 | return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{ |
| 391 | full_src_entry_path, |
| 392 | b.cache_root, |
| 393 | cache_path, |
| 394 | fs.path.sep, |
| 395 | dest_path, |
| 396 | @errorName(err), |
| 397 | }); |
| 398 | }; |
| 399 | _ = prev_status; |
| 400 | }, |
| 401 | else => continue, |
| 402 | } |
| 403 | } |
| 404 | } |
| 282 | 405 | |
| 283 | 406 | try step.writeManifest(&man); |
| 284 | 407 | } |