Build in the review tool.

This commit is contained in:
Greyson Parrelli
2026-08-24 09:24:01 -04:00
parent 4eef6a28d3
commit 407dd81512
55 changed files with 12932 additions and 40 deletions
+94
View File
@@ -47,6 +47,40 @@ pub fn build(b: *std.Build) void {
.root_source_file = icons_gresource,
});
// The review pane's UI. It is a React app (see `web/`), built by Vite here so
// the bundle embedded in the binary can never be older than the source it
// came from, and embedded rather than installed alongside so a review pane
// is a web view pointed at this process and nothing else.
//
// The four filenames are pinned in `web/vite.config.ts` precisely so they
// can be named at compile time; `src/review/assets.zig` is the other half of
// that agreement.
const web_build = b.addSystemCommand(&.{ "npm", "run", "--silent", "build", "--" });
web_build.setCwd(b.path("web"));
web_build.setName("vite build (review UI)");
web_build.addArg("--outDir");
const web_dist = web_build.addOutputDirectoryArg("dist");
web_build.addArg("--emptyOutDir");
// Vite is not told what its inputs are, so the Run step has to be. Without
// this the bundle is cached against its argv alone and editing the UI would
// rebuild nothing; with it, `zig build` after a `.tsx` edit does the right
// thing and `zig build` after a `.zig` edit does not re-run npm.
addWebInputs(b, web_build);
exe.root_module.addAnonymousImport("review-index.html", .{
.root_source_file = web_dist.path(b, "index.html"),
});
exe.root_module.addAnonymousImport("review-app.js", .{
.root_source_file = web_dist.path(b, "assets/app.js"),
});
exe.root_module.addAnonymousImport("review-app.css", .{
.root_source_file = web_dist.path(b, "assets/app.css"),
});
exe.root_module.addAnonymousImport("review-favicon.svg", .{
.root_source_file = web_dist.path(b, "favicon.svg"),
});
const gobject_imports = .{
.{ "adw", "adw1" },
.{ "cairo", "cairo1" },
@@ -149,6 +183,32 @@ pub fn build(b: *std.Build) void {
// table and a search over it, and it imports nothing at all. It cannot hang
// off the root above because a test binary has exactly one root, and
// Layouts.zig has no reason to reach for the emoji table.
// The review server's two testable halves. `git.zig` is git's own output
// formats and the rule for picking a base ref — text in, text out. `Store.zig`
// is the review file, whose round trip is the one piece of this that has to
// survive the process. Neither reaches GTK, so both run without a display;
// the HTTP layer above them wants a socket and a browser, which is a
// different kind of test than this project has.
const git_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/review/git.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
test_step.dependOn(&b.addRunArtifact(git_tests).step);
const store_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/review/Store.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
test_step.dependOn(&b.addRunArtifact(store_tests).step);
const emoji_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/emoji.zig"),
@@ -158,3 +218,37 @@ pub fn build(b: *std.Build) void {
});
test_step.dependOn(&b.addRunArtifact(emoji_tests).step);
}
/// Declare every file the Vite build reads as an input of that build step.
///
/// Zig hashes a `Run` step's file *arguments*, but a directory passed as an
/// argument is hashed by path and not by contents — so the source tree has to be
/// enumerated here, at configure time, for the cache to be honest about when the
/// bundle is stale.
///
/// `node_modules` is deliberately not walked. It is tens of thousands of files
/// whose contents are already pinned by `package-lock.json`, which *is* listed.
fn addWebInputs(b: *std.Build, run: *std.Build.Step.Run) void {
for ([_][]const u8{
"web/index.html",
"web/package.json",
"web/package-lock.json",
"web/tsconfig.json",
"web/vite.config.ts",
}) |file| {
run.addFileInput(b.path(file));
}
const io = b.graph.io;
for ([_][]const u8{ "web/src", "web/public" }) |root| {
var dir = b.build_root.handle.openDir(io, root, .{ .iterate = true }) catch continue;
defer dir.close(io);
var walker = dir.walk(b.allocator) catch continue;
defer walker.deinit();
while (walker.next(io) catch null) |entry| {
if (entry.kind != .file) continue;
run.addFileInput(b.path(b.pathJoin(&.{ root, entry.path })));
}
}
}