summaryrefslogtreecommitdiff
path: root/build.zig
diff options
context:
space:
mode:
Diffstat (limited to 'build.zig')
-rw-r--r--build.zig163
1 files changed, 94 insertions, 69 deletions
diff --git a/build.zig b/build.zig
index 1df6e8dd..b1990913 100644
--- a/build.zig
+++ b/build.zig
@@ -21,12 +21,12 @@ pub const macos_min_version: std.SemanticVersion = .{ .major = 13, .minor = 0, .
const zls_version = "0.16.1-dev+3e0d0820";
pub const TreeSitterGrammars = enum { disabled, zig, minimal, full };
-/// The GUI shell's shaders, spelled ONCE. Three things read this list: the
-/// eight SPIR-V imports the gui module embeds, the glslc runs that produce
-/// them, and `zig build shaders`, which writes the same set into the tracked
-/// shaders/prebuilt/. Each name is `shaders/<name>.glsl`, and the `.vert`/
-/// `.frag` in it is also the glslc shader stage — adding a shader is adding a
-/// name here plus the @embedFile in src/gui/gui.zig.
+/// The GUI shell's shaders, spelled ONCE. The runtime SPIR-V imports, their
+/// EffectCode source imports, and `zig build shaders` all read this list. That
+/// last step writes BOTH the bytecode and its exact source snapshot into the
+/// tracked shaders/prebuilt/ pair. Each name is `shaders/<name>.glsl`, and the
+/// `.vert`/`.frag` in it is also the glslc shader stage — adding a shader is
+/// adding a name here plus the @embedFile in src/gui/gui.zig.
const gui_shaders = [_][]const u8{
"ui.vert", "ui.frag",
"overlay.vert", "overlay.frag",
@@ -34,6 +34,10 @@ const gui_shaders = [_][]const u8{
"crt.vert", "crt.frag",
};
+/// Every GUI shader is also a source import for EffectCode. Live builds import
+/// shaders/*.glsl; prebuilt builds import the source snapshot paired with the
+/// committed SPIR-V, so the builtin cannot print code other than what produced
+/// the bytecode that build executes.
pub fn build(b: *std.Build) void {
const platform = b.option(Platform, "platform", "which shell to build (tty, gui, web, macos)") orelse .tty;
// Default target is the Steam Deck (deckcap's trick): x86_64 linux-gnu
@@ -105,16 +109,17 @@ pub fn build(b: *std.Build) void {
// here needs: glslc, which ships with the Vulkan SDK / shaderc and is not
// on a stock machine. It is also the input that changes least often, so
// -Dprebuilt-shaders decouples the two: the SPIR-V compiled from
- // shaders/*.glsl is committed under shaders/prebuilt/, and this flag
- // embeds that copy instead of shelling out. `-Dplatform=gui` then builds
- // with nothing but a C toolchain.
+ // shaders/*.glsl and its exact GLSL snapshot are committed together under
+ // shaders/prebuilt/, and this flag embeds that pair instead of shelling
+ // out. `-Dplatform=gui` then builds with nothing but a C toolchain.
//
// Off by default because it trades a dependency for a freshness problem:
- // with the flag on, the .glsl sources are NOT build inputs, so editing one
- // changes nothing until someone runs `zig build shaders` (see below). The
- // default therefore stays the honest one — compile the shaders that are
- // actually in the tree.
+ // with the flag on, the live .glsl sources are NOT build inputs, so editing
+ // one changes neither runtime bytecode nor EffectCode until someone runs
+ // `zig build shaders` (see below). The default therefore stays the honest
+ // one — compile the shaders that are actually in the tree.
const prebuilt_shaders = b.option(bool, "prebuilt-shaders", "embed the committed shaders/prebuilt/*.spv instead of running glslc (-Dplatform=gui without a Vulkan SDK)") orelse false;
+ const gui_shader_sources_prebuilt = platform == .gui and prebuilt_shaders;
// The browser shell is a freestanding wasm core plus ordinary web files.
// JavaScript owns the loop and IO; HTML/CSS own rendering.
@@ -132,23 +137,28 @@ pub fn build(b: *std.Build) void {
if (!enable_mupdf)
pdf_scroll_bench_step.dependOn(&b.addFail("pdf-scroll-bench is unavailable with -Dmupdf=false").step);
- // The other half of -Dprebuilt-shaders: recompile every shader and write
- // the SPIR-V back into shaders/prebuilt/, which is TRACKED rather than
- // generated-and-gitignored, because a machine without glslc cannot
- // regenerate it. Deliberately independent of -Dplatform: whoever changes a
- // shader runs `zig build shaders` on a machine that has the compiler and
- // commits the diff (`jj diff shaders/prebuilt` says whether the cache had
- // drifted), and no gui build is involved either way.
- const shaders_step = b.step("shaders", "recompile shaders/*.glsl into the tracked shaders/prebuilt/*.spv (glslc; refreshes what -Dprebuilt-shaders embeds)");
+ // The other half of -Dprebuilt-shaders: recompile every shader and update
+ // BOTH tracked artifacts — SPIR-V plus the exact GLSL EffectCode will print.
+ // A machine without glslc can then consume the pair without consulting the
+ // live source. Deliberately independent of -Dplatform: whoever changes a
+ // shader runs this on a machine that has the compiler and commits the diff
+ // (`jj diff shaders/prebuilt` says whether the pair drifted).
+ const shaders_step = b.step("shaders", "refresh paired shaders/prebuilt/*.spv + *.glsl snapshots (glslc)");
const update_shaders = b.addUpdateSourceFiles();
- for (gui_shaders) |name|
+ for (gui_shaders) |name| {
update_shaders.addCopyFileToSource(compileGlsl(b, name), b.fmt("shaders/prebuilt/{s}.spv", .{name}));
+ update_shaders.addCopyFileToSource(
+ b.path(b.fmt("shaders/{s}.glsl", .{name})),
+ b.fmt("shaders/prebuilt/{s}.glsl", .{name}),
+ );
+ }
shaders_step.dependOn(&update_shaders.step);
// Web LOOK has a tiny read-only source filesystem. Generate one Zig module
- // on the host from every tracked .zig path + its current working-tree
- // contents. The generator prefers Git as the tracked-file filter and has a
- // conservative filesystem fallback for source archives without `.git`.
+ // on the host from every tracked or new/nonignored .zig path + its current
+ // working-tree contents. The generator prefers Git as the ignore-aware
+ // path filter and has a conservative filesystem fallback for source
+ // archives without `.git`.
const source_gen = b.addExecutable(.{
.name = "pardes-embed-zig-sources",
.root_module = b.createModule(.{
@@ -164,7 +174,7 @@ pub fn build(b: *std.Build) void {
// discovery intentionally runs whenever a consumer builds it.
run_source_gen.has_side_effects = true;
const inspect_sources = b.addInstallFileWithDir(embedded_sources, .{ .custom = "web" }, "embedded_sources.zig");
- b.step("embed-web-sources", "generate zig-out/web/embedded_sources.zig from tracked .zig files").dependOn(&inspect_sources.step);
+ b.step("embed-web-sources", "generate zig-out/web/embedded_sources.zig from working-tree .zig files").dependOn(&inspect_sources.step);
snap_build.addWeb(b);
@@ -192,7 +202,7 @@ pub fn build(b: *std.Build) void {
.link_libc = !is_web,
});
// helix differential harness (test/hxdiff.zig): a second compilation of
- // the sans-IO core, driven headlessly. Mirrors root_mod's wiring for
+ // the core, driven headlessly. Mirrors root_mod's wiring for
// everything src/pardes.zig pulls in (ghostty-vt, tree-sitter, zstbi, the
// option modules); imported as "pardes" by the harness exe below.
const hx_core_mod = b.createModule(.{
@@ -256,6 +266,9 @@ pub fn build(b: *std.Build) void {
opts.addOption(bool, "enable_tracy", tracy != null);
opts.addOption(bool, "zls_backend", zls_backend);
opts.addOption(bool, "mupdf", enable_mupdf);
+ // Meaningful only for the SDL shell. Keeping the platform condition here
+ // prevents Config/EffectCode from describing tty/macOS/web as "prebuilt".
+ opts.addOption(bool, "gui_shader_sources_prebuilt", gui_shader_sources_prebuilt);
// Which ZLS is compiled in, for `SPC l i`. Kept next to the dependency it
// names: the .zon pins a commit, and a status screen that cannot say WHICH
// analyser answered is not worth opening.
@@ -648,6 +661,16 @@ pub fn build(b: *std.Build) void {
root_mod.addOptions("pardes_config", opts);
hx_core_mod.addOptions("pardes_config", opts);
+ const gui_effect_source_dir = if (gui_shader_sources_prebuilt) "shaders/prebuilt" else "shaders";
+ for ([_]*std.Build.Module{ root_mod, hx_core_mod }) |mod| {
+ if (platform == .gui) for (gui_shaders) |name| mod.addAnonymousImport(
+ b.fmt("effect-source-{s}.glsl", .{name}),
+ .{ .root_source_file = b.path(b.fmt("{s}/{s}.glsl", .{ gui_effect_source_dir, name })) },
+ );
+ if (platform == .macos) mod.addAnonymousImport("effect-source-crt.ci.metal", .{
+ .root_source_file = b.path("shaders/crt.ci.metal"),
+ });
+ }
// SDL3 shell wiring (-Dplatform=gui). SDL3 and FreeType are both built
// from source as static libraries; gui.zig @cImports their headers plus
@@ -733,7 +756,7 @@ pub fn build(b: *std.Build) void {
// one. Deliberately NOT here: an xcframework, lipo and an Xcode
// project. The first two are for a UNIVERSAL binary and this ships
// arm64; the third is a second build system to keep in step for what
- // a plist and three install steps already do. ghostty's
+ // a plist and a few install steps already do. ghostty's
// src/build/GhosttyXCFramework.zig is the map if that day comes.
const header = b.addTranslateC(.{
.root_source_file = b.path("src/macos/pardes.h"),
@@ -781,7 +804,7 @@ pub fn build(b: *std.Build) void {
//
// A bundle is a directory with a plist, a binary and an icon in it, so
// it is assembled HERE rather than by a script the build shells out to.
- // Every input is a file the graph knows — the archive, the three Swift
+ // Every input is a file the graph knows — the archive, the four Swift
// sources, the header, the plist, the icon generator — which is what
// makes the app rebuild when one of them moves and stay untouched when
// none does. The script this replaced took an install PREFIX and
@@ -824,10 +847,28 @@ pub fn build(b: *std.Build) void {
link.addFileArg(b.path("src/macos/pardes.h"));
link.addArg("-o");
const app_bin = link.addOutputFileArg("pardes");
- for ([_][]const u8{ "main.swift", "AppDelegate.swift", "PardesView.swift" }) |src|
+ for ([_][]const u8{
+ "main.swift",
+ "AppDelegate.swift",
+ "PardesView.swift",
+ "ScenePostprocessor.swift",
+ "FileWatcher.swift",
+ }) |src|
link.addFileArg(b.path(b.fmt("src/macos/Sources/{s}", .{src})));
link.addFileArg(lib_archive);
- link.addArgs(&.{ "-lc++", "-framework", "AppKit", "-framework", "CoreText", "-framework", "CoreGraphics" });
+ link.addArgs(&.{
+ "-lc++",
+ "-framework",
+ "AppKit",
+ "-framework",
+ "CoreText",
+ "-framework",
+ "CoreGraphics",
+ "-framework",
+ "CoreImage",
+ "-framework",
+ "Metal",
+ });
// The icon is generated, not committed: the mark is drawn out of
// the same palette PardesView.swift renders cells with, so a colour
@@ -854,6 +895,14 @@ pub fn build(b: *std.Build) void {
const install_app_bin = b.addInstallFileWithDir(app_bin, .{ .custom = "pardes.app/Contents/MacOS" }, "pardes");
const install_plist = b.addInstallFileWithDir(plist_out, .{ .custom = "pardes.app/Contents" }, "Info.plist");
+ // Runtime Metal compilation keeps this exact source shared with
+ // EffectCode's build-time embedding; no generated Swift literal
+ // or second shader copy can drift from what the app executes.
+ const install_scene_kernel = b.addInstallFileWithDir(
+ b.path("shaders/crt.ci.metal"),
+ .{ .custom = "pardes.app/Contents/Resources" },
+ "crt.ci.metal",
+ );
// CFBundleIconFile names this without its extension; without both
// halves the Dock shows the generic blank page.
const install_icon = b.addInstallFileWithDir(icon_dir.path(b, "pardes.icns"), .{ .custom = "pardes.app/Contents/Resources" }, "pardes.icns");
@@ -865,7 +914,7 @@ pub fn build(b: *std.Build) void {
// asking for — both are notarization's requirements rather than a
// signature's, and --timestamp on an ad-hoc signature is an error.
//
- // It signs the DIRECTORY, so it must follow all three installs: a
+ // It signs the DIRECTORY, so it must follow all four installs: a
// signature taken before the icon lands is a signature the icon
// then breaks. Always runs, because the bundle it edits lives
// outside the cache and zig cannot know what is in it.
@@ -876,6 +925,7 @@ pub fn build(b: *std.Build) void {
sign.step.dependOn(&install_app_bin.step);
sign.step.dependOn(&install_plist.step);
sign.step.dependOn(&install_icon.step);
+ sign.step.dependOn(&install_scene_kernel.step);
app_step.dependOn(&sign.step);
// ...and the thing you hand someone. UDZO is the compressed
@@ -935,11 +985,10 @@ pub fn build(b: *std.Build) void {
e2e_step.dependOn(&b.addFail("macos-e2e needs a Darwin target; drop -Dtarget= or pass -Dtarget=native").step);
// Same step name the other native platforms use, because in this
- // configuration theirs is not declared: the ABI guard and the core's
- // own inline tests are what `-Dplatform=macos` has to keep green.
- const unit_step = b.step("unit-test", "run the C-ABI guard and the core unit tests");
+ // configuration theirs is not declared. macos.zig imports the core, so
+ // this one aggregate reaches both the C-ABI guard and the core tests.
+ const unit_step = b.step("unit-test", "run the C-ABI guard and core unit tests");
unit_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = root_mod })).step);
- unit_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = hx_core_mod })).step);
web_step.dependOn(&b.addFail("web needs -Dplatform=web -Dtarget=wasm32-freestanding -Ddump=<dump.zon>").step);
} else {
// binary name: the native-shaped linux-x86_64 tty build stays `pardes`
@@ -1010,7 +1059,7 @@ pub fn build(b: *std.Build) void {
pdf_harness_step.dependOn(&b.addFail("pdf-harness is unavailable with -Dmupdf=false").step);
}
- // helix differential harness: drive the sans-IO core headlessly over
+ // helix differential harness: drive the core headlessly over
// JSON-Lines cases, one contract result line per case on stdout —
// the pardes half of the pardes-vs-helix diff (helix's hx-harness on
// its pardes-harness branch speaks the same contract).
@@ -1101,23 +1150,7 @@ pub fn build(b: *std.Build) void {
run_perf.setCwd(b.path("."));
b.step("perf", "large-file / long-line latency table (-- [--json] [--reps N] [--base old.json])").dependOn(&run_perf.step);
- // modal.zig is pure std — its inline unit tests run here
- const unit = b.addTest(.{ .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("src/modal.zig"),
- }) });
- const unit_step = b.step("unit-test", "run the modal.zig + crt.zig + deck replay + core unit tests");
- unit_step.dependOn(&b.addRunArtifact(unit).step);
- // Native config path/loading is std-only and has its own hermetic
- // filesystem/environment tests. The core receives bytes through
- // Options, so this IO module stays outside the core module graph.
- const user_config_test = b.addTest(.{ .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("src/user_config.zig"),
- }) });
- unit_step.dependOn(&b.addRunArtifact(user_config_test).step);
+ const unit_step = b.step("unit-test", "run native shell and module unit tests");
// Secure tempfile creation is native-shell IO, isolated from the core
// and tested as its own libc-linked module.
const temp_file_test = b.addTest(.{ .root_module = b.createModule(.{
@@ -1180,21 +1213,13 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("src/gui/deck.zig"),
}));
unit_step.dependOn(&b.addRunArtifact(deck_test).step);
- // the core's own inline tests (config.zig's wheel drift guard). It
- // links the SAME module hxdiff does rather than a fourth hand-wired
- // copy of ghostty-vt/tree-sitter/zstbi — a second wiring of that graph
- // is a second thing to keep in step.
- const core_test = b.addTest(.{ .root_module = hx_core_mod });
- unit_step.dependOn(&b.addRunArtifact(core_test).step);
- // gui.zig's own inline tests (the fractional-scroll motion).
- // Only reachable with -Dplatform=gui, and it links the SAME module
- // the gui exe does
- // for the same reason core_test does: SDL, the shaders and the font
- // are already wired onto it, and a second wiring is a second thing to
- // keep in step.
- if (platform == .gui) {
- const gui_test = b.addTest(.{ .root_module = root_mod });
- unit_step.dependOn(&b.addRunArtifact(gui_test).step);
+ // Shell-specific inline tests. main() imports a shell inside its
+ // runtime switch, which test analysis never enters. main.zig's test
+ // block names the selected shell, user config, and platform-only
+ // helpers; its ordinary core import reaches pardes.zig and modal.zig.
+ if (platform == .tty or platform == .gui) {
+ const shell_test = b.addTest(.{ .root_module = root_mod });
+ unit_step.dependOn(&b.addRunArtifact(shell_test).step);
}
}
}