Skip to content

Commit

Permalink
feat!: move to Run Step-based architecture
Browse files Browse the repository at this point in the history
this change should hopefully be more future-proof: in light of
ziglang/zig#14498, collecting the EmbedFile.zig logic as a Run Step is
likely to be the preferred (indeed, only) way to extend the Build
system in the future. also, by breaking out the work originally in
`make` into a collection of other steps, EmbedFile.zig should
hopefully correctly work with the Zig build syste's caching
system. (Previously, I found that updating file contents were not
reflected in the `@embedFile` calls.)

adds `writeSources`, which creates and returns a named `WriteFile`
step which collects everything the `EmbedFile` step touches and
outputs it into one directory.

also, one can now use the `embed-file` executable standalone if desired.

BREAKING CHANGE: the signatures of `addFile` and `addDirectory` have
changed. taking `sub_path` as an argument was incorrect.
BREAKING CHANGE: `createModule` is removed; instead use
`embed_file.module` directly.
  • Loading branch information
robbielyman committed Jan 2, 2025
1 parent b148d61 commit 3aff56e
Show file tree
Hide file tree
Showing 8 changed files with 486 additions and 432 deletions.
20 changes: 11 additions & 9 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ This is the public API of an =EmbedFile= Step:
embed_file: *EmbedFile,
// this is a path to the parent directory of the file to be added
source: std.Build.LazyPath,
// this is the name of the file as it exists as input
sub_path: []const u8,
// this is the name which will be available when using the resulting Zig module
name: []const u8,
alignment: ?u29,
Expand All @@ -51,8 +49,6 @@ This is the public API of an =EmbedFile= Step:
embed_file: *EmbedFile,
// this is a path to the parent directory of the directory to be added
source: std.Build.LazyPath,
// this is the name of the directory as it exists as input
sub_path: []const u8,
// this allows the user to include or exclude files based on their extensions
options: std.Build.Step.WriteFile.Directory.Options,
// this is the namespace which will be available when using the resulting Zig module
Expand All @@ -68,14 +64,20 @@ This is the public API of an =EmbedFile= Step:
// ...
}

/// returns a `Module` containing the Zig source file generated from this `EmbedFile`
pub fn createModule(embed_file: *EmbedFile) *std.Build.Module {
/// adds a named WriteFile step that collects all of this EmbedFile's dependencies to write out
pub fn writeSources(embed_file: *EmbedFile, name: []const u8) *std.Build.Step.WriteFile {
// ...
}

/// a `*Module` containing the Zig source file generated from this `EmbedFile`
/// add it to your compilation by passing it `addImport`,
/// e.g. `exe.root_module.addImport("assets", embed_file.module);`
module: *std.Build.Module,
#+end_src

To see an example of correct usage,
you can clone this repository and run =zig build test=.
you can clone this repository and run =zig build test= in the =tests= directory.
The resulting Zig file will be placed in =test-output/module.zig= relative to the build prefix.
(NB: the resulting =module.zig= will not compile,
since its declarations use paths relative to a folder in the cache.)
(NB: the resulting =module.zig= will not compile without =build.zig= logic,
since its declarations use module imports that =EmbedFile= constructs itself.
Of course, you could rewrite or reproduce these imports yourself.)
179 changes: 149 additions & 30 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
const std = @import("std");
const EmbedFile = @This();

pub const EmbedFile = @import("src/EmbedFile.zig");
wf: *std.Build.Step.WriteFile,
contents: std.ArrayListUnmanaged(u8),
module: *std.Build.Module,
named_wfs: std.ArrayListUnmanaged(*std.Build.Step.WriteFile),

/// creates a new EmbedFile step
pub fn create(owner: *std.Build) *EmbedFile {
const embed_file = owner.allocator.create(EmbedFile) catch @panic("OOM");
embed_file.* = .{
.wf = std.Build.Step.WriteFile.create(owner),
.module = owner.createModule(.{}),
.contents = .{},
.named_wfs = .{},
};
const canonicalize = owner.addSystemCommand(&.{ owner.graph.zig_exe, "fmt", "--stdin" });
canonicalize.setStdIn(.{ .lazy_path = embed_file.wf.getDirectory().path(owner, "module.zig") });
const module_file = canonicalize.captureStdOut();
embed_file.module.root_source_file = module_file;
embed_file.contents.appendSlice(owner.allocator,
\\//! This file is automatically generated by EmbedFile.zig!
\\//! EmbedFile.zig is NOT an official part of the Zig build system;
\\//! please report any issues at https://github.com/robbielyman/EmbedFile.zig
\\
\\
) catch @panic("OOM");
embed_file.updateContents();
return embed_file;
}

pub fn addEmbedFiles(b: *std.Build) *EmbedFile {
return EmbedFile.create(b);
Expand All @@ -9,47 +37,138 @@ pub fn addEmbedFiles(b: *std.Build) *EmbedFile {
pub fn addEmbedFile(b: *std.Build, name: []const u8, bytes: []const u8, alignment: ?u29) *EmbedFile {
const ret = EmbedFile.create(b);
ret.add(name, bytes, alignment);
ret.step.name = b.fmt("EmbedFile {s}", .{name});
return ret;
}

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
_ = b.addModule("EmbedFile", .{
.root_source_file = b.path("src/EmbedFile.zig"),
.target = target,
.optimize = optimize,
});

const test_step = b.step("test", "test EmbedFile");
test_step.dependOn(tests(b));
fn updateContents(embed_file: *EmbedFile) void {
if (embed_file.wf.files.items.len == 0) {
_ = embed_file.wf.add("module.zig", embed_file.contents.items);
return;
}
embed_file.wf.files.items[0].contents = .{ .bytes = embed_file.contents.items };
}

fn tests(b: *std.Build) *std.Build.Step {
const test_module = b.addTest(.{
.root_source_file = b.path("src/test.zig"),
const Kind = enum { file, directory };

fn addImport(
embed_file: *EmbedFile,
b: *std.Build,
write_file: *std.Build.Step.WriteFile,
name: []const u8,
kind: Kind,
alignment: ?u29,
) void {
const this_dep = b.dependencyFromBuildZig(@This(), .{
.target = b.graph.host,
.optimize = .Debug,
});
const exe = this_dep.artifact("embed-file");
const run = b.addRunArtifact(exe);
switch (kind) {
.file => run.addFileArg(write_file.getDirectory().path(b, name)),
.directory => run.addDirectoryArg(write_file.getDirectory().path(b, name)),
}
if (alignment) |a| run.addArg(b.fmt("{d}", .{a}));
run.step.dependOn(&write_file.step);
const input = run.captureStdOut();
const fmt = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "--stdin" });
fmt.setStdIn(.{ .lazy_path = input });
const output = fmt.captureStdOut();
const copy_file = b.addWriteFiles();
const module_file = copy_file.addCopyFile(output, "module.zig");
_ = copy_file.addCopyDirectory(write_file.getDirectory(), "", .{});
for (embed_file.named_wfs.items) |wf| {
_ = wf.addCopyDirectory(copy_file.getDirectory(), name, .{});
}
const import_module = b.createModule(.{
.root_source_file = module_file,
});
embed_file.module.addImport(name, import_module);
switch (kind) {
.file => embed_file.contents.writer(b.allocator).print(
\\pub const @"{s}" = @import("{s}").@"{s}";
\\
, .{ name, name, name }) catch @panic("OOM"),
.directory => embed_file.contents.writer(b.allocator).print(
\\pub const @"{s}" = @import("{s}");
\\
, .{ name, name }) catch @panic("OOM"),
}
embed_file.updateContents();
}

/// adds a declaration by name, contents and alignment directly
pub fn add(embed_file: *EmbedFile, name: []const u8, bytes: []const u8, alignment: ?u29) void {
const b = embed_file.module.owner;
const write_file = b.addWriteFile(name, bytes);
embed_file.addImport(b, write_file, name, .file, alignment);
}

/// name is the eventual declaration name to be used,
/// while source is the path as it exists on the file system
pub fn addFile(
embed_file: *EmbedFile,
source: std.Build.LazyPath,
name: []const u8,
alignment: ?u29,
) void {
const b = embed_file.module.owner;
const write_file = b.addWriteFiles();
_ = write_file.add("a.include", "a.a");
_ = write_file.add("b.include", "a.b");
_ = write_file.add("c" ++ std.fs.path.sep_str ++ "a.include", "a.c.a");
_ = write_file.add("d.exclude", "a.d");
_ = write_file.addCopyFile(source, name);
embed_file.addImport(b, write_file, name, .file, alignment);
}

const second_write_file = b.addWriteFile("path.txt", "this is to test addFile");
/// name is the eventual declaration name to be used as a namespace,
/// while source is the path as it exists on the file system
/// files matching the Directory.Options specification
/// are made available as declarations namespaced under "name";
/// the filename (minus any extension) is used as the declaration name
/// alignment, if non-null, is used as the alignment for all sub-declarations
pub fn addDirectory(
embed_file: *EmbedFile,
source: std.Build.LazyPath,
options: std.Build.Step.WriteFile.Directory.Options,
name: []const u8,
alignment: ?u29,
) void {
const b = embed_file.module.owner;
const write_file = b.addWriteFiles();
_ = write_file.addCopyDirectory(source, name, options);
embed_file.addImport(b, write_file, name, .directory, alignment);
}

const embed_file = addEmbedFile(b, "name with spaces", "names can have spaces", null);
embed_file.addDirectory(write_file.getDirectory(), "", .{ .exclude_extensions = &.{".exclude"}, .include_extensions = &.{".include"} }, "a", 16);
embed_file.addFile(second_write_file.getDirectory(), "path.txt", "other", null);
/// returns a `LazyPath` representing the Zig source file generated from this `EmbedFile`
pub fn getSource(embed_file: *EmbedFile) std.Build.LazyPath {
return embed_file.module.root_source_file.?;
}

embed_file.step.dependOn(&write_file.step);
/// adds a named WriteFile step that collects all of this EmbedFile's dependencies to write out
pub fn writeSources(embed_file: *EmbedFile, name: []const u8) *std.Build.Step.WriteFile {
const owner = embed_file.module.owner;
const gpa = owner.allocator;
const new_wf = owner.addNamedWriteFiles(name);
// collect all currently existing dependencies
// addImport will add the rest
var it = embed_file.module.iterateDependencies(null, false);
while (it.next()) |item| if (std.mem.eql(u8, item.name, "root")) {
_ = new_wf.addCopyFile(item.module.root_source_file.?, "module.zig");
} else {
_ = new_wf.addCopyDirectory(item.module.root_source_file.?.dirname(), item.name, .{});
};

test_module.root_module.addImport("assets", embed_file.createModule());
embed_file.named_wfs.append(gpa, new_wf) catch @panic("OOM");
return new_wf;
}

const install_output_file = b.addInstallFileWithDir(embed_file.getSource(), .{ .custom = "test-output" }, "module.zig");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

const run_tests = b.addRunArtifact(test_module);
run_tests.step.dependOn(&install_output_file.step);
return &run_tests.step;
const exe = b.addExecutable(.{
.name = "embed-file",
.target = target,
.optimize = optimize,
.root_source_file = b.path("src/main.zig"),
});
b.installArtifact(exe);
}
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.{
.name = "embed-file",
.version = "0.1.0",
.paths = .{ "build.zig.zon", "build.zig", "LICENSE", "README.org", "src" },
.version = "1.0.0",
.paths = .{ "build.zig.zon", "build.zig", "LICENSE", "README.org", "src/main.zig", "tests" },
.dependencies = .{},
}
Loading

0 comments on commit 3aff56e

Please sign in to comment.