Commit e16db29887

Carl Åstholm <carl@astholm.se>
2024-03-03 17:14:02
Implement `WriteFile.addCopyDirectory`
1 parent 2c7be4f
Changed files (2)
lib
lib/std/Build/Step/Compile.zig
@@ -493,7 +493,10 @@ fn addHeaderInstallationToIncludeTree(cs: *Compile, installation: HeaderInstalla
             _ = wf.addCopyFile(file.source, file.dest_rel_path);
         },
         .directory => |dir| {
-            _ = dir; // TODO
+            _ = wf.addCopyDirectory(dir.source, dir.dest_rel_path, .{
+                .exclude_extensions = dir.options.exclude_extensions,
+                .include_extensions = dir.options.include_extensions,
+            });
         },
     };
 }
lib/std/Build/Step/WriteFile.zig
@@ -15,9 +15,11 @@ const ArrayList = std.ArrayList;
 const WriteFile = @This();
 
 step: Step,
-/// The elements here are pointers because we need stable pointers for the
-/// GeneratedFile field.
+
+// The elements here are pointers because we need stable pointers for the GeneratedFile field.
 files: std.ArrayListUnmanaged(*File),
+directories: std.ArrayListUnmanaged(*Directory),
+
 output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
 generated_directory: std.Build.GeneratedFile,
 
@@ -33,6 +35,33 @@ pub const File = struct {
     }
 };
 
+pub const Directory = struct {
+    source: std.Build.LazyPath,
+    sub_path: []const u8,
+    options: Options,
+    generated_dir: std.Build.GeneratedFile,
+
+    pub const Options = struct {
+        /// File paths that end in any of these suffixes will be excluded from copying.
+        exclude_extensions: []const []const u8 = &.{},
+        /// Only file paths that end in any of these suffixes will be included in copying.
+        /// `null` means that all suffixes will be included.
+        /// `exclude_extensions` takes precedence over `include_extensions`.
+        include_extensions: ?[]const []const u8 = &.{".h"},
+
+        pub fn dupe(self: Options, b: *std.Build) Options {
+            return .{
+                .exclude_extensions = b.dupeStrings(self.exclude_extensions),
+                .include_extensions = if (self.include_extensions) |incs| b.dupeStrings(incs) else null,
+            };
+        }
+    };
+
+    pub fn getPath(self: *Directory) std.Build.LazyPath {
+        return .{ .generated = &self.generated_dir };
+    }
+};
+
 pub const OutputSourceFile = struct {
     contents: Contents,
     sub_path: []const u8,
@@ -53,6 +82,7 @@ pub fn create(owner: *std.Build) *WriteFile {
             .makeFn = make,
         }),
         .files = .{},
+        .directories = .{},
         .output_source_files = .{},
         .generated_directory = .{ .step = &wf.step },
     };
@@ -96,6 +126,28 @@ pub fn addCopyFile(wf: *WriteFile, source: std.Build.LazyPath, sub_path: []const
     return file.getPath();
 }
 
+pub fn addCopyDirectory(
+    wf: *WriteFile,
+    source: std.Build.LazyPath,
+    sub_path: []const u8,
+    options: Directory.Options,
+) std.Build.LazyPath {
+    const b = wf.step.owner;
+    const gpa = b.allocator;
+    const dir = gpa.create(Directory) catch @panic("OOM");
+    dir.* = .{
+        .source = source.dupe(b),
+        .sub_path = b.dupePath(sub_path),
+        .options = options.dupe(b),
+        .generated_dir = .{ .step = &wf.step },
+    };
+    wf.directories.append(gpa, dir) catch @panic("OOM");
+
+    wf.maybeUpdateName();
+    source.addStepDependencies(&wf.step);
+    return dir.getPath();
+}
+
 /// A path relative to the package root.
 /// Be careful with this because it updates source files. This should not be
 /// 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 {
 }
 
 fn maybeUpdateName(wf: *WriteFile) void {
-    if (wf.files.items.len == 1) {
+    if (wf.files.items.len == 1 and wf.directories.items.len == 0) {
         // First time adding a file; update name.
         if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
             wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path});
         }
+    } else if (wf.directories.items.len == 1 and wf.files.items.len == 0) {
+        // First time adding a directory; update name.
+        if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
+            wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.directories.items[0].sub_path});
+        }
     }
 }
 
@@ -209,6 +266,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
             },
         }
     }
+    for (wf.directories.items) |dir| {
+        man.hash.addBytes(dir.source.getPath2(b, step));
+        man.hash.addBytes(dir.sub_path);
+        for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext);
+        if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc);
+    }
 
     if (try step.cacheHit(&man)) {
         const digest = man.final();
@@ -233,6 +296,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
     };
     defer cache_dir.close();
 
+    const cwd = fs.cwd();
+
     for (wf.files.items) |file| {
         if (fs.path.dirname(file.sub_path)) |dirname| {
             cache_dir.makePath(dirname) catch |err| {
@@ -252,7 +317,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
             .copy => |file_source| {
                 const source_path = file_source.getPath(b);
                 const prev_status = fs.Dir.updateFile(
-                    fs.cwd(),
+                    cwd,
                     source_path,
                     cache_dir,
                     file.sub_path,
@@ -279,6 +344,64 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
             cache_path, file.sub_path,
         });
     }
+    for (wf.directories.items) |dir| {
+        const full_src_dir_path = dir.source.getPath2(b, step);
+        const dest_dirname = dir.sub_path;
+
+        if (dest_dirname.len != 0) {
+            cache_dir.makePath(dest_dirname) catch |err| {
+                return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{
+                    b.cache_root, cache_path, fs.path.sep, dest_dirname, @errorName(err),
+                });
+            };
+        }
+
+        var src_dir = b.build_root.handle.openDir(full_src_dir_path, .{ .iterate = true }) catch |err| {
+            return step.fail("unable to open source directory '{s}': {s}", .{
+                full_src_dir_path, @errorName(err),
+            });
+        };
+        defer src_dir.close();
+
+        var it = try src_dir.walk(b.allocator);
+        next_entry: while (try it.next()) |entry| {
+            for (dir.options.exclude_extensions) |ext| {
+                if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry;
+            }
+            if (dir.options.include_extensions) |incs| {
+                for (incs) |inc| {
+                    if (std.mem.endsWith(u8, entry.path, inc)) break;
+                } else {
+                    continue :next_entry;
+                }
+            }
+            const full_src_entry_path = b.pathJoin(&.{ full_src_dir_path, entry.path });
+            const dest_path = b.pathJoin(&.{ dest_dirname, entry.path });
+            switch (entry.kind) {
+                .directory => try cache_dir.makePath(dest_path),
+                .file => {
+                    const prev_status = fs.Dir.updateFile(
+                        cwd,
+                        full_src_entry_path,
+                        cache_dir,
+                        dest_path,
+                        .{},
+                    ) catch |err| {
+                        return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{
+                            full_src_entry_path,
+                            b.cache_root,
+                            cache_path,
+                            fs.path.sep,
+                            dest_path,
+                            @errorName(err),
+                        });
+                    };
+                    _ = prev_status;
+                },
+                else => continue,
+            }
+        }
+    }
 
     try step.writeManifest(&man);
 }