Implement WriteFile.addCopyDirectory

This commit is contained in:
Carl Åstholm 2024-03-03 17:14:02 +01:00
parent 2c7be4f8dd
commit e16db29887
2 changed files with 131 additions and 5 deletions

View File

@ -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,
});
},
};
}

View File

@ -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);
}