Files
zig/lib/std/Build/Step/WriteFile.zig
2026-05-08 13:18:16 -07:00

185 lines
6.4 KiB
Zig

//! WriteFile is used to create a directory in an appropriate location inside
//! the local cache which has a set of files that have either been generated
//! during the build, or are copied from the source package.
const WriteFile = @This();
const std = @import("std");
const Step = std.Build.Step;
const Configuration = std.Build.Configuration;
step: Step,
embeds: std.ArrayList(Embed) = .empty,
copies: std.ArrayList(Copy) = .empty,
directories: std.ArrayList(Directory) = .empty,
generated_directory: Configuration.GeneratedFileIndex,
mode: Mode = .whole_cached,
pub const base_tag: Step.Tag = .write_file;
pub const Mode = union(enum) {
/// Default mode. Integrates with the cache system. The directory should be
/// read-only during the make phase. Any different inputs result in
/// different "o" subdirectory.
whole_cached,
/// In this mode, the directory will be placed inside "tmp" rather than
/// "o", and caching will be skipped. During the `make` phase, the step
/// will always do all the file system operations, and on successful build
/// completion, the dir will be deleted along with all other tmp
/// directories. The directory is therefore eligible to be used for
/// mutations by other steps.
tmp,
/// The operations will not be performed against a freshly created
/// directory, but instead act against a temporary directory.
mutate: std.Build.LazyPath,
};
pub const Embed = Configuration.Step.WriteFile.Embed;
pub const Copy = struct {
sub_path: Configuration.String,
src_file: std.Build.LazyPath,
};
pub const Directory = struct {
sub_path: Configuration.String,
src_path: std.Build.LazyPath,
exclude_extensions: Configuration.OptionalStringList,
include_extensions: Configuration.OptionalStringList,
};
pub fn create(owner: *std.Build) *WriteFile {
const graph = owner.graph;
const wf = graph.create(WriteFile);
wf.* = .{
.step = .init(.{
.tag = base_tag,
.name = "WriteFile",
.owner = owner,
}),
.generated_directory = graph.addGeneratedFile(&wf.step),
};
return wf;
}
/// Writes `contents` to a file at `sub_path` relative to the output
/// directory.
///
/// `sub_path` may be a basename, or it may include subdirectories, which are
/// created as needed.
pub fn add(wf: *WriteFile, sub_path: []const u8, contents: []const u8) std.Build.LazyPath {
const graph = wf.step.owner.graph;
const wc = &graph.wip_configuration;
const arena = graph.arena;
wf.embeds.append(arena, .{
.sub_path = wc.addString(sub_path) catch @panic("OOM"),
.contents = wc.addBytes(contents) catch @panic("OOM"),
}) catch @panic("OOM");
wf.maybeUpdateName();
return .{
.generated = .{
.index = wf.generated_directory,
.sub_path = graph.dupeString(sub_path),
},
};
}
/// Copies the provided file to `sub_path` relative to the output directory.
///
/// `sub_path` may be a basename, or it may include subdirectories, which are
/// created as needed.
pub fn addCopyFile(wf: *WriteFile, src_file: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath {
const graph = wf.step.owner.graph;
const wc = &graph.wip_configuration;
const arena = graph.arena;
wf.copies.append(arena, .{
.sub_path = wc.addString(sub_path) catch @panic("OOM"),
.src_file = src_file.dupe(graph),
}) catch @panic("OOM");
wf.maybeUpdateName();
src_file.addStepDependencies(&wf.step);
return .{ .generated = .{
.index = wf.generated_directory,
.sub_path = graph.dupePath(sub_path),
} };
}
pub const CopyDirectoryOptions = 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 = null,
};
/// Copy files matching the specified exclude/include patterns to the specified
/// subdirectory relative to this step's generated directory.
///
/// The returned value is a lazy path to the generated subdirectory.
pub fn addCopyDirectory(
wf: *WriteFile,
src_path: std.Build.LazyPath,
sub_path: []const u8,
options: CopyDirectoryOptions,
) std.Build.LazyPath {
const graph = wf.step.owner.graph;
const wc = &graph.wip_configuration;
const arena = graph.arena;
wf.directories.append(arena, .{
.sub_path = wc.addString(sub_path) catch @panic("OOM"),
.src_path = src_path.dupe(graph),
.exclude_extensions = if (options.exclude_extensions.len != 0)
.init(wc.addStringList(options.exclude_extensions) catch @panic("OOM"))
else
.none,
.include_extensions = if (options.include_extensions) |list|
.init(wc.addStringList(list) catch @panic("OOM"))
else
.none,
}) catch @panic("OOM");
wf.maybeUpdateName();
src_path.addStepDependencies(&wf.step);
return .{
.generated = .{
.index = wf.generated_directory,
.sub_path = graph.dupePath(sub_path),
},
};
}
/// Returns a `LazyPath` representing the base directory that contains all the
/// files from this `WriteFile`.
pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath {
return .{ .generated = .{ .index = wf.generated_directory } };
}
fn maybeUpdateName(wf: *WriteFile) void {
const graph = wf.step.owner.graph;
const wc = &graph.wip_configuration;
const files_count = wf.embeds.items.len + wf.copies.items.len;
if (files_count == 1 and wf.directories.items.len == 0) {
// First time adding a file; update name.
const sub_path = if (wf.embeds.items.len == 1) wf.embeds.items[0].sub_path else wf.copies.items[0].sub_path;
if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wc.stringSlice(sub_path)});
}
} else if (wf.directories.items.len == 1 and files_count == 0) {
// First time adding a directory; update name.
const dir_name = wc.stringSlice(wf.directories.items[0].sub_path);
if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{dir_name});
}
}
}