zig/lib/std/Build/WriteFileStep.zig
Andrew Kelley b29e3fa2cd std.Build: enhancements to ConfigHeaderStep
Breaking API change to std.Build.addConfigHeader. It now uses an options
struct.

Introduce std.Build.CompileStep.installConfigHeader which also accepts
an options struct. This is used to add a generated config file into the
set of installed header files for a particular compilation artifact.

std.Build.ConfigHeaderStep now additionally supports a "blank" style
where a header is generated from scratch. It no longer exposes
`output_dir`. Instead it exposes a FileSource via `output_file`.
It now additionally accepts an `include_path` option which affects the
include path of CompileStep when using the `#include` directive, as well
as affecting the default installation subdirectory for header
installation purposes.

The hash used for the directory to store the generated config file now
includes the contents of the generated file. This fixes possible race
conditions when generating multiple header files simultaneously.

The values hash table is now an array hash map, to preserve order for
the "blank" use case.

I also took the opportunity to remove output_dir from TranslateCStep and
WriteFileStep. This is technically a breaking change, but it was always
naughty to access these fields.
2023-02-05 06:26:30 -07:00

113 lines
3.8 KiB
Zig

const std = @import("../std.zig");
const Step = std.Build.Step;
const fs = std.fs;
const ArrayList = std.ArrayList;
const WriteFileStep = @This();
pub const base_id = .write_file;
step: Step,
builder: *std.Build,
files: std.TailQueue(File),
pub const File = struct {
source: std.Build.GeneratedFile,
basename: []const u8,
bytes: []const u8,
};
pub fn init(builder: *std.Build) WriteFileStep {
return WriteFileStep{
.builder = builder,
.step = Step.init(.write_file, "writefile", builder.allocator, make),
.files = .{},
};
}
pub fn add(self: *WriteFileStep, basename: []const u8, bytes: []const u8) void {
const node = self.builder.allocator.create(std.TailQueue(File).Node) catch @panic("unhandled error");
node.* = .{
.data = .{
.source = std.Build.GeneratedFile{ .step = &self.step },
.basename = self.builder.dupePath(basename),
.bytes = self.builder.dupe(bytes),
},
};
self.files.append(node);
}
/// Gets a file source for the given basename. If the file does not exist, returns `null`.
pub fn getFileSource(step: *WriteFileStep, basename: []const u8) ?std.Build.FileSource {
var it = step.files.first;
while (it) |node| : (it = node.next) {
if (std.mem.eql(u8, node.data.basename, basename))
return std.Build.FileSource{ .generated = &node.data.source };
}
return null;
}
fn make(step: *Step) !void {
const self = @fieldParentPtr(WriteFileStep, "step", step);
// The cache is used here not really as a way to speed things up - because writing
// the data to a file would probably be very fast - but as a way to find a canonical
// location to put build artifacts.
// If, for example, a hard-coded path was used as the location to put WriteFileStep
// files, then two WriteFileSteps executing in parallel might clobber each other.
// TODO port the cache system from the compiler to zig std lib. Until then
// we directly construct the path, and no "cache hit" detection happens;
// the files are always written.
// Note there is similar code over in ConfigHeaderStep.
const Hasher = std.crypto.auth.siphash.SipHash128(1, 3);
// Random bytes to make WriteFileStep unique. Refresh this with
// new random bytes when WriteFileStep implementation is modified
// in a non-backwards-compatible way.
var hash = Hasher.init("eagVR1dYXoE7ARDP");
{
var it = self.files.first;
while (it) |node| : (it = node.next) {
hash.update(node.data.basename);
hash.update(node.data.bytes);
hash.update("|");
}
}
var digest: [16]u8 = undefined;
hash.final(&digest);
var hash_basename: [digest.len * 2]u8 = undefined;
_ = std.fmt.bufPrint(
&hash_basename,
"{s}",
.{std.fmt.fmtSliceHexLower(&digest)},
) catch unreachable;
const output_dir = try fs.path.join(self.builder.allocator, &[_][]const u8{
self.builder.cache_root, "o", &hash_basename,
});
var dir = fs.cwd().makeOpenPath(output_dir, .{}) catch |err| {
std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) });
return err;
};
defer dir.close();
{
var it = self.files.first;
while (it) |node| : (it = node.next) {
dir.writeFile(node.data.basename, node.data.bytes) catch |err| {
std.debug.print("unable to write {s} into {s}: {s}\n", .{
node.data.basename,
output_dir,
@errorName(err),
});
return err;
};
node.data.source.path = try fs.path.join(
self.builder.allocator,
&[_][]const u8{ output_dir, node.data.basename },
);
}
}
}