summaryrefslogtreecommitdiff
path: root/build.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-05 20:21:50 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commitde4def3548a6729b0dfd2120495a61beef8c8c2c (patch)
tree12144f0f64dc96bb4741459c1f9db57f44349330 /build.zig
parent7988d9bc6e31210ff13994f18d5622dd57ba9341 (diff)
downloadpardes-de4def3548a6729b0dfd2120495a61beef8c8c2c.tar.gz
pardes-de4def3548a6729b0dfd2120495a61beef8c8c2c.zip
pardes v2: the rewrite, complete and organized. src/ (core + three shells), test/ (snapshot parity harness + 18 frozen goldens). One sans-IO core, vaxis tty + SDL3 GPU native + wasm web shells, 18/18 parity with the purged prototype, 7.6k lines vs 12.1k. Fix: gui shell pre-sized the core at init so the greet-releasing resize never fired (blank panes until first interaction); live sessions now init at defaults and get the real grid as a resize event (the shell contract, documented on Options).
Diffstat (limited to 'build.zig')
-rw-r--r--build.zig832
1 files changed, 257 insertions, 575 deletions
diff --git a/build.zig b/build.zig
index 8b1b6a3b..22dd7158 100644
--- a/build.zig
+++ b/build.zig
@@ -1,50 +1,34 @@
const std = @import("std");
-const AppKind = enum { main, replay };
+pub const Platform = enum { tty, gui, web };
+pub const TreeSitterGrammars = enum { disabled, minimal, full };
const GhosttyArtifact = enum { ghostty, lib };
-const TreeSitterGrammars = enum { disabled, minimal, full };
-const WebFilter = enum { linear, nearest };
pub fn build(b: *std.Build) void {
- const requested_optimize = b.standardOptimizeOption(.{});
const target = b.standardTargetOptions(.{});
+ const requested_optimize = b.standardOptimizeOption(.{});
+ const platform = b.option(Platform, "platform", "which shell to build (tty, gui, web)") orelse .tty;
+ const static = b.option(bool, "static", "statically link") orelse false;
+ const dump_path = b.option([]const u8, "dump", "dump .zon embedded into the web shell (-Dplatform=web)");
+ const emcc_path = b.option([]const u8, "emcc", "emcc for the web shell's final link") orelse "/usr/lib/emscripten/emcc";
const is_emscripten = target.result.os.tag == .emscripten;
+ // wasm: size is the budget
const optimize = if (is_emscripten) .ReleaseSmall else requested_optimize;
- const emscripten_sysroot = if (is_emscripten) detectEmscriptenSysroot(b) else null;
+ const default_grammars: TreeSitterGrammars = if (is_emscripten) .minimal else .full;
+ const tree_sitter_grammars = b.option(TreeSitterGrammars, "tree-sitter", "tree-sitter grammar set: disabled, minimal, full") orelse default_grammars;
- // build-time IO, only used to slurp the grammars' highlights.scm queries.
- var threaded: std.Io.Threaded = .init(b.allocator, .{});
- defer threaded.deinit();
- const io = threaded.io();
+ // the browser shell: zig object + emcc link + html, installed to zig-out/web
+ const web_step = b.step("web", "build the browser shell into zig-out/web (-Dplatform=web -Dtarget=wasm32-emscripten -Ddump=<dump.zon>)");
- // -Dtrace: make the e2e runner print the captured screen state (after each
- // pump/waitFor and on every assertion) so failures can be inspected live.
- // Usage: `zig build test -Dtrace`.
- const trace = b.option(bool, "trace", "print captured tty/screen state during e2e tests") orelse false;
- const static_link = b.option(bool, "static", "link all build executables and test binaries statically") orelse false;
- const exe_linkage: ?std.builtin.LinkMode = if (static_link) .static else null;
- const strip = b.option(bool, "strip", "strip debug info from the compiled pardes binary") orelse false;
- // -Dsdl: build the SDL3 GPU UI frontend (a windowed cell-grid renderer)
- // instead of the default libvaxis terminal backend. The sdl/stb deps are
- // lazy, so a plain `zig build` never fetches or compiles them; they're only
- // pulled in when this flag is set. Use `zig build test-sdl -Dsdl` for the
- // rendered-image SDL e2e suite.
- const sdl = b.option(bool, "sdl", "build the SDL3 GPU UI frontend instead of the terminal backend") orelse false;
- const sdl_backend = sdl or is_emscripten;
- const replay_dump = b.option([]const u8, "replay-dump", "embed this replay dump .zon into pardes-replay");
- const web_render_scale = b.option(f32, "web-render-scale", "browser replay backing/glyph scale") orelse 1.5;
- if (web_render_scale <= 0) @panic("web-render-scale must be greater than zero");
- const web_filter = b.option(WebFilter, "web-filter", "browser replay filtering: linear, nearest") orelse .linear;
- const default_tree_sitter_grammars: TreeSitterGrammars = if (is_emscripten) .minimal else .full;
- const tree_sitter_grammars = b.option(TreeSitterGrammars, "tree-sitter", "tree-sitter grammar set: disabled, minimal, full") orelse default_tree_sitter_grammars;
- const syntax_highlighting = tree_sitter_grammars != .disabled;
- const syntax_minimal_grammars = tree_sitter_grammars == .minimal or tree_sitter_grammars == .full;
- const syntax_full_grammars = tree_sitter_grammars == .full;
- const emscripten_include: ?std.Build.LazyPath = if (emscripten_sysroot) |sysroot|
- .{ .cwd_relative = b.pathJoin(&.{ sysroot, "include" }) }
- else
- null;
- const emscripten_libc_file: ?std.Build.LazyPath = if (emscripten_sysroot) |sysroot| blk: {
+ const emscripten_sysroot = if (is_emscripten) detectEmscriptenSysroot(b) else null;
+ if (platform == .web and !is_emscripten) return failBuild(b, web_step, "-Dplatform=web requires -Dtarget=wasm32-emscripten");
+ if (platform != .web and is_emscripten) return failBuild(b, web_step, "-Dtarget=wasm32-emscripten requires -Dplatform=web");
+ if (platform == .web and dump_path == null) return failBuild(b, web_step, "-Dplatform=web requires -Ddump=<dump.zon> (the browser has no ptys; state replays from an embedded dump)");
+ if (platform == .web and emscripten_sysroot == null) return failBuild(b, web_step, "no emscripten sysroot found; pass --sysroot <emscripten-cache>/sysroot or set EM_CACHE/EMSDK");
+
+ // emscripten "libc": point zig at the sysroot headers/crt via a libc.txt
+ // (the prototype's WriteFiles trick) — applied to EVERY compile step.
+ const libc_file: ?std.Build.LazyPath = if (emscripten_sysroot) |sysroot| blk: {
const files = b.addWriteFiles();
break :blk files.add("emscripten-libc.txt", b.fmt(
\\include_dir={s}/include
@@ -56,265 +40,49 @@ pub fn build(b: *std.Build) void {
\\
, .{ sysroot, sysroot, sysroot }));
} else null;
+ const emscripten_include: ?std.Build.LazyPath = if (emscripten_sysroot) |sysroot|
+ .{ .cwd_relative = b.pathJoin(&.{ sysroot, "include" }) }
+ else
+ null;
- const exe_name = targetBinaryName(b, "pardes", target.result);
- const replay_name = targetBinaryName(b, "pardes-replay", target.result);
- const exe = b.addExecutable(.{
- .name = exe_name,
- .linkage = exe_linkage,
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("main.zig"),
- .strip = strip,
- .link_libc = true,
- }),
- });
- const replay = b.addExecutable(.{
- .name = replay_name,
- .linkage = exe_linkage,
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("replay.zig"),
- .strip = strip,
- }),
- });
- if (emscripten_libc_file) |libc_file| {
- exe.setLibCFile(libc_file);
- replay.setLibCFile(libc_file);
- }
- const tests = b.addExecutable(.{
- .name = "pardes-e2e",
- .linkage = exe_linkage,
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("tests.zig"),
- .link_libc = true,
- }),
- });
-
- const main_app_config = b.addOptions();
- main_app_config.addOption(AppKind, "app_kind", .main);
- main_app_config.addOption(bool, "syntax_highlighting", syntax_highlighting);
- main_app_config.addOption(bool, "syntax_minimal_grammars", syntax_minimal_grammars);
- main_app_config.addOption(bool, "syntax_full_grammars", syntax_full_grammars);
- exe.root_module.addOptions("app_config", main_app_config);
-
- const replay_app_config = b.addOptions();
- replay_app_config.addOption(AppKind, "app_kind", .replay);
- replay_app_config.addOption(bool, "syntax_highlighting", syntax_highlighting);
- replay_app_config.addOption(bool, "syntax_minimal_grammars", syntax_minimal_grammars);
- replay_app_config.addOption(bool, "syntax_full_grammars", syntax_full_grammars);
- replay.root_module.addOptions("app_config", replay_app_config);
- const replay_config = b.addOptions();
- replay_config.addOption(bool, "embedded_dump", replay_dump != null);
- replay.root_module.addOptions("replay_config", replay_config);
- if (replay_dump) |path| {
- replay.root_module.addAnonymousImport("replay.dump.zon", .{
- .root_source_file = if (std.fs.path.isAbsolute(path)) .{ .cwd_relative = path } else b.path(path),
- });
- }
-
- // ghostty-vt and vaxis both depend on uucode, but Zig 0.16 forbids the
- // same source file from belonging to two modules, so they can't each have
- // their own uucode instance. We build a single uucode module here (with our
- // own uucode_config.zig) and hand it to both.
- //
- // Our config matches ghostty's exactly except the tables are `.unpacked`;
- // see uucode_config.zig for why that's required on Zig 0.16.
- const uucode_config = b.path("uucode_config.zig");
- const uucode_tables = b.dependency("uucode", .{
- .target = target,
- .optimize = optimize,
- .build_config_path = uucode_config,
- }).namedLazyPath("tables.zig");
- const uucode_mod = b.dependency("uucode", .{
- .target = target,
- .optimize = optimize,
- .build_config_path = uucode_config,
- .tables_path = uucode_tables,
- }).module("uucode");
-
- // vaxis is built with `external_uucode = true` so it skips its built-in
- // uucode dependency and lets us wire one in.
- const vaxis_dep = b.dependency("vaxis", .{
- .target = target,
- .optimize = optimize,
- .external_uucode = true,
- });
- const vaxis_mod = vaxis_dep.module("vaxis");
-
- // You'll want to use a lazy dependency here so that ghostty is only
- // downloaded if you actually need it.
- var ghostty_vt_for_tests: ?*std.Build.Module = null;
- const ghostty_simd = switch (target.result.os.tag) {
- .emscripten, .openbsd => false,
- else => true,
- };
- if (b.lazyDependency("ghostty", .{
- // Setting simd to false will force a pure static build that
- // doesn't even require libc, but it has a significant performance
- // penalty. If your embedding app requires libc anyway, you should
- // always keep simd enabled.
- .simd = ghostty_simd,
- })) |dep| {
- // Accessing ghostty's modules runs its build(), which (because vaxis is
- // a shared dependency, deduplicated to the same module) wires ghostty's
- // own uucode into vaxis. We undo that below by overriding both imports
- // with ours — order matters, so this must happen after this point.
- const ghostty_vt = dep.module("ghostty-vt");
- ghostty_vt_for_tests = ghostty_vt;
- ghostty_vt.addImport("uucode", uucode_mod);
- if (is_emscripten) {
- const term_opts = b.addOptions();
- term_opts.addOption(GhosttyArtifact, "artifact", .lib);
- term_opts.addOption(bool, "c_abi", false);
- term_opts.addOption(bool, "oniguruma", false);
- term_opts.addOption(bool, "simd", false);
- term_opts.addOption(bool, "slow_runtime_safety", false);
- term_opts.addOption(bool, "kitty_graphics", false);
- term_opts.addOption(bool, "tmux_control_mode", false);
- term_opts.addOption([]const u8, "version_string", "0.0.0");
- term_opts.addOption(usize, "version_major", 0);
- term_opts.addOption(usize, "version_minor", 0);
- term_opts.addOption(usize, "version_patch", 0);
- term_opts.addOption(?[]const u8, "version_pre", null);
- term_opts.addOption(?[]const u8, "version_build", null);
- ghostty_vt.addOptions("terminal_options", term_opts);
- }
- exe.root_module.addImport("ghostty-vt", ghostty_vt);
- replay.root_module.addImport("ghostty-vt", ghostty_vt);
- // The e2e test harness parses the app's pty output with its own ghostty
- // terminal (reads the 2D screen grid, not raw bytes), so it needs the
- // same module.
- tests.root_module.addImport("ghostty-vt", ghostty_vt);
- }
-
- // Override uucode on vaxis last, so it wins over ghostty's build() above.
- vaxis_mod.addImport("uucode", uucode_mod);
- exe.root_module.addImport("vaxis", vaxis_mod);
+ // build-time IO, only used to slurp the grammars' highlights.scm queries
+ var threaded: std.Io.Threaded = .init(b.allocator, .{});
+ defer threaded.deinit();
+ const io = threaded.io();
- // The UI backend module (`-Dui`). main.zig + image.zig import `"ui"` instead of
- // `"vaxis"` directly; the chosen backend file re-exports the shared UI types
- // (Key/Mouse/Window/Cell/…) and provides the renderer/loop (`Vaxis`/`Tty`/
- // `Loop`/`init`). vaxis = transparent passthrough; sdl = SDL3 GPU frontend.
- const ui_root = if (sdl_backend) b.path("ui_sdl.zig") else b.path("ui_vaxis.zig");
- const ui_mod = b.createModule(.{
+ const root_mod = b.createModule(.{
.target = target,
.optimize = optimize,
- .root_source_file = ui_root,
+ .root_source_file = b.path("src/main.zig"),
+ .link_libc = true,
});
- const ui_sdl_config = b.addOptions();
- ui_sdl_config.addOption(f32, "web_render_scale", web_render_scale);
- ui_sdl_config.addOption(WebFilter, "web_filter", web_filter);
- ui_mod.addOptions("ui_sdl_config", ui_sdl_config);
- ui_mod.addImport("vaxis", vaxis_mod);
- exe.root_module.addImport("ui", ui_mod);
- replay.root_module.addImport("ui", ui_mod);
-
- // SDL3 backend wiring (`-Dsdl`, or Emscripten replay builds). The sdl package builds SDL3 from source
- // as a static lib + emits the SDL3/ headers; we @cImport those headers in
- // ui_sdl.zig, link the lib, and pull in stb_truetype (for the glyph atlas) and
- // the embedded Adwaita Mono font. All gated on the flag so a plain `zig build`
- // (vaxis) never fetches or compiles SDL.
- var sdl_lib_for_wasm: ?*std.Build.Step.Compile = null;
- if (sdl_backend) {
- const sdl_dep = if (is_emscripten and emscripten_include != null)
- b.lazyDependency("sdl", .{
- .target = target,
- .optimize = optimize,
- .system_include_path = emscripten_include.?,
- }) orelse return
- else
- b.lazyDependency("sdl", .{
- .target = target,
- .optimize = optimize,
- }) orelse return; // not fetched (offline?) — skip SDL wiring
- const sdl_lib = sdl_dep.artifact("SDL3");
- if (emscripten_libc_file) |libc_file| sdl_lib.setLibCFile(libc_file);
- if (is_emscripten) sdl_lib_for_wasm = sdl_lib;
- ui_mod.linkLibrary(sdl_lib);
- ui_mod.addIncludePath(sdl_dep.path("include"));
- if (emscripten_include) |include| ui_mod.addSystemIncludePath(include);
- ui_mod.link_libc = true;
+ // static archives the emcc link needs beyond the zig object (grammars, SDL)
+ var wasm_libs: std.ArrayList(std.Build.LazyPath) = .empty;
- // stb_truetype: the header (declarations, via include path) + the impl TU
- // + a small C shim (font.c) so ui_sdl.zig doesn't feed the whole
- // single-header lib to translate-c.
- ui_mod.addIncludePath(b.path("vendor/stb"));
- ui_mod.addCSourceFile(.{ .file = b.path("vendor/stb/stb_truetype_impl.c") });
- ui_mod.addCSourceFile(.{ .file = b.path("vendor/stb/font.c") });
+ const opts = b.addOptions();
+ opts.addOption(Platform, "platform", platform);
+ opts.addOption(bool, "syntax_highlighting", tree_sitter_grammars != .disabled);
+ opts.addOption(bool, "syntax_minimal_grammars", tree_sitter_grammars != .disabled);
+ opts.addOption(bool, "syntax_full_grammars", tree_sitter_grammars == .full);
+ root_mod.addOptions("pardes_config", opts);
- if (is_emscripten) {
- ui_mod.addAnonymousImport("ui.vert.es.glsl", .{ .root_source_file = b.path("shaders/ui.vert.es.glsl") });
- ui_mod.addAnonymousImport("ui.frag.es.glsl", .{ .root_source_file = b.path("shaders/ui.frag.es.glsl") });
- ui_mod.addAnonymousImport("overlay.vert.es.glsl", .{ .root_source_file = b.path("shaders/overlay.vert.es.glsl") });
- ui_mod.addAnonymousImport("overlay.frag.es.glsl", .{ .root_source_file = b.path("shaders/overlay.frag.es.glsl") });
- } else {
- // GLSL -> SPIR-V at build time, embedded into ui_sdl.zig via @embedFile.
- ui_mod.addAnonymousImport("ui.vert.spv", .{
- .root_source_file = compileGlsl(b, "shaders/ui.vert.glsl", "vertex", "ui.vert.spv"),
- });
- ui_mod.addAnonymousImport("ui.frag.spv", .{
- .root_source_file = compileGlsl(b, "shaders/ui.frag.glsl", "fragment", "ui.frag.spv"),
- });
- ui_mod.addAnonymousImport("overlay.vert.spv", .{
- .root_source_file = compileGlsl(b, "shaders/overlay.vert.glsl", "vertex", "overlay.vert.spv"),
- });
- ui_mod.addAnonymousImport("overlay.frag.spv", .{
- .root_source_file = compileGlsl(b, "shaders/overlay.frag.glsl", "fragment", "overlay.frag.spv"),
- });
- }
- }
-
- // zstbi: C (stb_image)-backed image decoder for the image-viewer panes. We use
- // it instead of zigimg because zigimg's pure-Zig decoder crashes the Zig 0.16
- // compiler here; stb_image is plain C (extern fns), so it compiles fine, decodes
- // every common format, and offers downscaling. Its module ("root") carries the
- // C source + include path + libc, so importing it is all that's needed.
- const zstbi_dep = b.dependency("zstbi", .{ .target = target, .optimize = optimize });
- const zstbi_mod = zstbi_dep.module("root");
- exe.root_module.addImport("zstbi", zstbi_mod);
- replay.root_module.addImport("zstbi", zstbi_mod);
-
- const replay_wasm_ready = is_emscripten and replay_dump != null and emscripten_sysroot != null;
- const emcc_for_wasm = if (replay_wasm_ready) blk: {
- const replay_obj = b.addObject(.{
- .name = "pardes-replay",
- .root_module = replay.root_module,
- });
- if (emscripten_libc_file) |libc_file| replay_obj.setLibCFile(libc_file);
-
- const emcc_path = b.option([]const u8, "emcc", "path to emcc for browser replay final link") orelse "/usr/lib/emscripten/emcc";
- const emcc = b.addSystemCommand(&.{emcc_path});
- emcc.addFileArg(replay_obj.getEmittedBinDirectory().path(b, "pardes-replay_zcu.o"));
- emcc.addFileArg(replay_obj.getEmittedBin());
- emcc.addFileArg(b.path("vendor/stb/stb_truetype_impl.c"));
- emcc.addFileArg(b.path("vendor/stb/font.c"));
- emcc.addFileArg(zstbi_dep.path("src/zstbi.c"));
- if (sdl_lib_for_wasm) |sdl_lib| emcc.addFileArg(sdl_lib.getEmittedBin());
- break :blk emcc;
- } else null;
-
- if (syntax_highlighting) {
- // tree-sitter syntax highlighting for file panes: the Zig bindings + C runtime
- // (the `tree-sitter` module), each grammar's parser.c linked as a static lib,
- // and its highlights.scm read at build time into the `ts_queries` options module.
- const tree_sitter = b.dependency("tree_sitter", .{ .target = target, .optimize = optimize });
- const tree_sitter_mod = tree_sitter.module("tree_sitter");
+ // tree-sitter: the zig bindings + C runtime, one static lib per grammar,
+ // and each grammar's highlights.scm slurped at build time into the
+ // ts_queries options module (codegen consumed by comptime in syntax.zig).
+ if (tree_sitter_grammars != .disabled) {
+ const tree_sitter_mod = b.dependency("tree_sitter", .{ .target = target, .optimize = optimize }).module("tree_sitter");
+ // the C runtime rides the module as a static lib; give it the wasm
+ // libc and hand its archive to the emcc link
if (is_emscripten) {
for (tree_sitter_mod.link_objects.items) |link_object| switch (link_object) {
.other_step => |lib| {
- if (emscripten_libc_file) |libc_file| lib.setLibCFile(libc_file);
- if (emcc_for_wasm) |emcc| emcc.addFileArg(lib.getEmittedBin());
+ lib.setLibCFile(libc_file.?);
+ wasm_libs.append(b.allocator, lib.getEmittedBin()) catch @panic("OOM");
},
else => {},
};
}
- exe.root_module.addImport("tree-sitter", tree_sitter_mod);
- replay.root_module.addImport("tree-sitter", tree_sitter_mod);
+ root_mod.addImport("tree-sitter", tree_sitter_mod);
const Grammar = struct {
name: []const u8,
@@ -322,7 +90,6 @@ pub fn build(b: *std.Build) void {
src: []const u8 = "src",
scanner: bool = false,
query: []const u8 = "queries/highlights.scm",
- local_query: ?[]const u8 = null,
minimal: bool = false,
};
const grammars = [_]Grammar{
@@ -357,7 +124,7 @@ pub fn build(b: *std.Build) void {
inline for (grammars) |g| {
if (tree_sitter_grammars == .full or g.minimal) {
const dep = b.dependency(g.dep, .{});
- const query_path = if (g.local_query) |p| b.path(p) else dep.path(g.query);
+ const query_path = dep.path(g.query);
const query = std.Io.Dir.cwd().readFileAlloc(io, query_path.getPath(b), b.allocator, .limited(0x100000)) catch @panic("read " ++ g.name ++ " highlights");
ts_queries.addOption([]const u8, g.name ++ "_highlights", query);
@@ -366,297 +133,238 @@ pub fn build(b: *std.Build) void {
.root_module = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true }),
.linkage = .static,
});
- if (emscripten_libc_file) |libc_file| lib.setLibCFile(libc_file);
+ if (libc_file) |f| lib.setLibCFile(f);
lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/parser.c") });
if (g.scanner) lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/scanner.c") });
lib.root_module.addIncludePath(dep.path(g.src));
- exe.root_module.linkLibrary(lib);
- replay.root_module.linkLibrary(lib);
- if (emcc_for_wasm) |emcc| emcc.addFileArg(lib.getEmittedBin());
+ root_mod.linkLibrary(lib);
+ if (is_emscripten) wasm_libs.append(b.allocator, lib.getEmittedBin()) catch @panic("OOM");
}
}
- exe.root_module.addOptions("ts_queries", ts_queries);
- replay.root_module.addOptions("ts_queries", ts_queries);
+ root_mod.addOptions("ts_queries", ts_queries);
}
- // Install the exe so `zig build` (and `zig build install`) produces it in
- // zig-out/bin for global install, not just as a hidden dep of `run`/`test`.
- b.installArtifact(exe);
- b.installArtifact(replay);
+ // zstbi (C stb_image): image pane decode. zigimg crashes the 0.16 compiler.
+ const zstbi_dep = b.dependency("zstbi", .{ .target = target, .optimize = optimize });
+ root_mod.addImport("zstbi", zstbi_dep.module("root"));
+
+ // ghostty-vt and vaxis both depend on uucode, but Zig forbids one source
+ // file in two modules — build a single uucode (our config, unpacked tables)
+ // and hand it to both. Same trick as the prototype; see uucode_config.zig.
+ const uucode_config = b.path("uucode_config.zig");
+ const uucode_tables = b.dependency("uucode", .{
+ .target = target,
+ .optimize = optimize,
+ .build_config_path = uucode_config,
+ }).namedLazyPath("tables.zig");
+ const uucode_mod = b.dependency("uucode", .{
+ .target = target,
+ .optimize = optimize,
+ .build_config_path = uucode_config,
+ .tables_path = uucode_tables,
+ }).module("uucode");
- const run_exe = b.addRunArtifact(exe);
+ const vaxis_mod = b.dependency("vaxis", .{
+ .target = target,
+ .optimize = optimize,
+ .external_uucode = true,
+ }).module("vaxis");
- const run = b.step("run", "descend to the pardes");
- run.dependOn(&run_exe.step);
+ var ghostty_vt_for_snap: ?*std.Build.Module = null;
+ if (b.lazyDependency("ghostty", .{ .simd = target.result.os.tag != .emscripten })) |dep| {
+ const ghostty_vt = dep.module("ghostty-vt");
+ ghostty_vt_for_snap = ghostty_vt;
+ ghostty_vt.addImport("uucode", uucode_mod);
+ // emscripten: hand ghostty-vt the terminal_options module its build()
+ // would otherwise provide (copied verbatim from the prototype build)
+ if (is_emscripten) {
+ const term_opts = b.addOptions();
+ term_opts.addOption(GhosttyArtifact, "artifact", .lib);
+ term_opts.addOption(bool, "c_abi", false);
+ term_opts.addOption(bool, "oniguruma", false);
+ term_opts.addOption(bool, "simd", false);
+ term_opts.addOption(bool, "slow_runtime_safety", false);
+ term_opts.addOption(bool, "kitty_graphics", false);
+ term_opts.addOption(bool, "tmux_control_mode", false);
+ term_opts.addOption([]const u8, "version_string", "0.0.0");
+ term_opts.addOption(usize, "version_major", 0);
+ term_opts.addOption(usize, "version_minor", 0);
+ term_opts.addOption(usize, "version_patch", 0);
+ term_opts.addOption(?[]const u8, "version_pre", null);
+ term_opts.addOption(?[]const u8, "version_build", null);
+ ghostty_vt.addOptions("terminal_options", term_opts);
+ }
+ root_mod.addImport("ghostty-vt", ghostty_vt);
+ }
+ // override uucode on vaxis LAST so it wins over ghostty's build() wiring.
+ vaxis_mod.addImport("uucode", uucode_mod);
+ root_mod.addImport("vaxis", vaxis_mod);
+ root_mod.addImport("uucode", uucode_mod);
- const replay_step = b.step("replay", if (is_emscripten)
- "build browser SDL replay (requires -Dreplay-dump=<dump.zon>)"
- else
- "replay a pardes dump: zig build replay -- <dump.zon>");
- const replay_wasm_step = b.step("replay-wasm", "compatibility alias for browser replay; use `zig build replay -Dtarget=wasm32-emscripten -Dreplay-dump=<dump.zon>`");
- if (!is_emscripten) {
- const run_replay = b.addRunArtifact(replay);
- if (b.args) |args| run_replay.addArgs(args);
- replay_step.dependOn(&run_replay.step);
- replay_wasm_step.dependOn(&b.addFail("replay-wasm requires -Dtarget=wasm32-emscripten; use `zig build replay` for native replay").step);
- } else if (replay_dump == null) {
- replay_step.dependOn(&b.addFail("replay requires -Dreplay-dump=<dump.zon> when targeting wasm32-emscripten").step);
- replay_wasm_step.dependOn(replay_step);
- } else if (emscripten_sysroot == null) {
- replay_step.dependOn(&b.addFail("replay requires an Emscripten sysroot; pass --sysroot <emscripten>/cache/sysroot or set EM_CACHE/EMSDK").step);
- replay_wasm_step.dependOn(replay_step);
- } else {
- const emcc = emcc_for_wasm.?;
+ // SDL3 shell wiring (-Dplatform=gui and web). The sdl package builds SDL3
+ // from source as a static lib + emits the SDL3/ headers; gui.zig @cImports
+ // those, plus a small stb_truetype C shim for the glyph atlas, and embeds
+ // the SPIR-V compiled at build time from shaders/*.glsl — or, on web, the
+ // GL ES shader sources compiled at runtime by WebGL.
+ if (platform == .gui or platform == .web) {
+ const sdl_dep = if (emscripten_include) |include|
+ b.lazyDependency("sdl", .{ .target = target, .optimize = optimize, .system_include_path = include })
+ else
+ b.lazyDependency("sdl", .{ .target = target, .optimize = optimize });
+ if (sdl_dep) |dep| {
+ const sdl_lib = dep.artifact("SDL3");
+ if (libc_file) |f| sdl_lib.setLibCFile(f);
+ root_mod.linkLibrary(sdl_lib);
+ root_mod.addIncludePath(dep.path("include"));
+ if (emscripten_include) |include| root_mod.addSystemIncludePath(include);
+ if (is_emscripten) wasm_libs.append(b.allocator, sdl_lib.getEmittedBin()) catch @panic("OOM");
+ }
+ root_mod.addIncludePath(b.path("vendor/stb"));
+ root_mod.addCSourceFile(.{ .file = b.path("vendor/stb/stb_truetype_impl.c") });
+ root_mod.addCSourceFile(.{ .file = b.path("vendor/stb/font.c") });
+ // the embedded UI font: provided as a build import since assets/ lives
+ // outside the src/ module root (@embedFile can't escape it)
+ root_mod.addAnonymousImport("AdwaitaMono-Regular.ttf", .{
+ .root_source_file = b.path("assets/AdwaitaMono-Regular.ttf"),
+ });
+ if (platform == .web) {
+ root_mod.addAnonymousImport("ui.vert.es.glsl", .{ .root_source_file = b.path("shaders/ui.vert.es.glsl") });
+ root_mod.addAnonymousImport("ui.frag.es.glsl", .{ .root_source_file = b.path("shaders/ui.frag.es.glsl") });
+ root_mod.addAnonymousImport("overlay.vert.es.glsl", .{ .root_source_file = b.path("shaders/overlay.vert.es.glsl") });
+ root_mod.addAnonymousImport("overlay.frag.es.glsl", .{ .root_source_file = b.path("shaders/overlay.frag.es.glsl") });
+ const dp = dump_path.?;
+ root_mod.addAnonymousImport("embedded.dump.zon", .{
+ .root_source_file = if (std.fs.path.isAbsolute(dp)) .{ .cwd_relative = dp } else b.path(dp),
+ });
+ } else {
+ root_mod.addAnonymousImport("ui.vert.spv", .{
+ .root_source_file = compileGlsl(b, "shaders/ui.vert.glsl", "vertex", "ui.vert.spv"),
+ });
+ root_mod.addAnonymousImport("ui.frag.spv", .{
+ .root_source_file = compileGlsl(b, "shaders/ui.frag.glsl", "fragment", "ui.frag.spv"),
+ });
+ root_mod.addAnonymousImport("overlay.vert.spv", .{
+ .root_source_file = compileGlsl(b, "shaders/overlay.vert.glsl", "vertex", "overlay.vert.spv"),
+ });
+ root_mod.addAnonymousImport("overlay.frag.spv", .{
+ .root_source_file = compileGlsl(b, "shaders/overlay.frag.glsl", "fragment", "overlay.frag.spv"),
+ });
+ }
+ }
+
+ if (platform == .web) {
+ // zig emits an object; emcc does the final link (wasm + js glue),
+ // compiling the C shims itself and pulling in every static archive.
+ const obj = b.addObject(.{ .name = "pardes", .root_module = root_mod });
+ obj.setLibCFile(libc_file.?);
+
+ const emcc = b.addSystemCommand(&.{emcc_path});
+ emcc.addFileArg(obj.getEmittedBinDirectory().path(b, "pardes_zcu.o"));
+ emcc.addFileArg(obj.getEmittedBin());
+ emcc.addFileArg(b.path("vendor/stb/stb_truetype_impl.c"));
+ emcc.addFileArg(b.path("vendor/stb/font.c"));
+ emcc.addFileArg(zstbi_dep.path("src/zstbi.c"));
+ for (wasm_libs.items) |lib| emcc.addFileArg(lib);
emcc.addPrefixedDirectoryArg("-I", b.path("vendor/stb"));
emcc.addPrefixedDirectoryArg("-I", zstbi_dep.path("libs/stbi"));
emcc.addArgs(&.{
"-std=c99",
"-fno-sanitize=undefined",
"-Oz",
- "-sINITIAL_MEMORY=2147483648",
- "-sEXIT_RUNTIME=0",
+ "-sINITIAL_MEMORY=1073741824", // the 512 MiB web heap + code/stack
+ "-sEXIT_RUNTIME=0", // run() returns; the rAF loop keeps living
"-sFULL_ES3=1",
"-sMIN_WEBGL_VERSION=2",
"-sMAX_WEBGL_VERSION=2",
"-sINCOMING_MODULE_JS_API=canvas",
"-o",
});
- const replay_js = emcc.addOutputFileArg("pardes-replay.js");
- replay_step.dependOn(&b.addInstallFileWithDir(replay_js, .{ .custom = "web" }, "pardes-replay.js").step);
- replay_step.dependOn(&b.addInstallFileWithDir(replay_js.dirname().path(b, "pardes-replay.wasm"), .{ .custom = "web" }, "pardes-replay.wasm").step);
-
- const replay_html = std.mem.concat(b.allocator, u8, &.{
- \\<!doctype html>
- \\<html>
- \\<head>
- \\ <meta charset="utf-8">
- \\ <meta name="viewport" content="width=device-width, initial-scale=1">
- \\ <title>pardes replay</title>
- \\ <style>
- \\ html, body { margin: 0; width: 100%; height: 100%; background: #121212; }
- \\ body { overflow: hidden; }
- \\ canvas { display: block; width: 100vw; height: 100vh; outline: none;
- \\ image-rendering: crisp-edges; image-rendering: pixelated;
- \\ -ms-interpolation-mode: nearest-neighbor;
- \\ transform: translateZ(0); backface-visibility: hidden; }
- \\ touch-action: none; }
- \\ </style>
- \\</head>
- \\<body>
- \\ <canvas id="canvas" tabindex="0"></canvas>
- \\ <script>
- \\ const canvas = document.getElementById('canvas');
- \\ canvas.addEventListener('contextmenu', (event) => event.preventDefault());
- \\ canvas.addEventListener('pointerdown', (event) => {
- \\ canvas.focus({ preventScroll: true });
- \\ canvas.setPointerCapture?.(event.pointerId);
- \\ });
- \\ const releasePointer = (event) => {
- \\ if (canvas.hasPointerCapture?.(event.pointerId)) canvas.releasePointerCapture(event.pointerId);
- \\ };
- \\ canvas.addEventListener('pointerup', releasePointer);
- \\ canvas.addEventListener('pointercancel', releasePointer);
- \\ window.Module = { canvas };
- \\ </script>
- \\ <script src="pardes-replay.js"></script>
- \\</body>
- \\</html>
- }) catch @panic("failed to build replay.html");
- const web = b.addWriteFiles();
- const html = web.add("replay.html", replay_html);
- replay_step.dependOn(&b.addInstallFileWithDir(html, .{ .custom = "web" }, "replay.html").step);
- replay_wasm_step.dependOn(replay_step);
- }
-
- const test_browser_step = b.step("test-browser", "run browser SDL replay screenshot e2e (requires -Dtarget=wasm32-emscripten -Dreplay-dump=<dump.zon>)");
- if (!is_emscripten) {
- test_browser_step.dependOn(&b.addFail("test-browser requires -Dtarget=wasm32-emscripten").step);
- } else if (replay_dump == null) {
- test_browser_step.dependOn(&b.addFail("test-browser requires -Dreplay-dump=<dump.zon>").step);
+ const js = emcc.addOutputFileArg("pardes.js");
+ web_step.dependOn(&b.addInstallFileWithDir(js, .{ .custom = "web" }, "pardes.js").step);
+ web_step.dependOn(&b.addInstallFileWithDir(js.dirname().path(b, "pardes.wasm"), .{ .custom = "web" }, "pardes.wasm").step);
+ const html = b.addWriteFiles().add("index.html", html_shell);
+ web_step.dependOn(&b.addInstallFileWithDir(html, .{ .custom = "web" }, "index.html").step);
+ b.getInstallStep().dependOn(web_step);
} else {
- const run_browser_e2e = b.addSystemCommand(&.{ "node", "tests/browser_replay_e2e.mjs" });
- run_browser_e2e.addArg("zig-out/web");
- run_browser_e2e.addArg("zig-out/browser-replay.png");
- run_browser_e2e.addArg(b.fmt("{d}", .{web_render_scale}));
- run_browser_e2e.step.dependOn(replay_step);
- test_browser_step.dependOn(&run_browser_e2e.step);
- }
-
- // tutor_gen: a separate binary that parses tutor.txt and writes
- // tutor_cases.zig (a file of `test` blocks, one per practice block).
- // `zig build tutor-gen` runs it; the unit-test step depends on the output.
- const tutor_gen = b.addExecutable(.{
- .name = "tutor-gen",
- .linkage = exe_linkage,
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("tutor_gen.zig"),
- }),
- });
- const run_tutor_gen = b.addRunArtifact(tutor_gen);
- run_tutor_gen.addArg("tutor.txt");
- const tutor_cases_lazy = run_tutor_gen.addOutputFileArg("tutor_cases.zig");
- const tutor_gen_step = b.step("tutor-gen", "regenerate tutor_cases.zig from tutor.txt");
- tutor_gen_step.dependOn(&run_tutor_gen.step);
-
- const run_tests = b.addRunArtifact(tests);
- run_tests.addArtifactArg(exe);
- run_tests.addArtifactArg(replay);
-
- // Expose build-time flags (e.g. -Dtrace) to the test runner as an importable
- // `config` module. tests.zig reads `config.trace` and hands it to the harness.
- const test_opts = b.addOptions();
- test_opts.addOption(bool, "trace", trace);
- tests.root_module.addOptions("config", test_opts);
-
- const test_step = b.step("test", "run deterministic tests");
- test_step.dependOn(&run_tests.step);
-
- // `test-sdl`: e2e tests for the SDL3 GPU backend. The SDL pardes exe runs in
- // test mode (PARDES_TEST=1): stdin drives app events through a pty, while the
- // real GPU renderer writes the latest framebuffer capture to a temp PPM file.
- // Requires `-Dsdl` so the SDL exe is the one under test.
- const sdl_tests = b.addExecutable(.{
- .name = "pardes-e2e-sdl",
- .linkage = exe_linkage,
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("tests_sdl.zig"),
- .link_libc = true,
- }),
- });
- const run_sdl_tests = b.addRunArtifact(sdl_tests);
- run_sdl_tests.addArtifactArg(exe);
- const sdl_test_opts = b.addOptions();
- sdl_test_opts.addOption(bool, "trace", trace);
- sdl_tests.root_module.addOptions("config", sdl_test_opts);
- const test_sdl_step = b.step("test-sdl", "run SDL3 GPU e2e tests (requires -Dsdl)");
- test_sdl_step.dependOn(&run_sdl_tests.step);
-
- const tty_perf = b.addExecutable(.{
- .name = "pardes-tty-edit-perf",
- .linkage = exe_linkage,
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("tests_tty_perf.zig"),
- .link_libc = true,
- }),
- });
- if (ghostty_vt_for_tests) |ghostty_vt| tty_perf.root_module.addImport("ghostty-vt", ghostty_vt);
- const run_tty_perf = b.addRunArtifact(tty_perf);
- run_tty_perf.addArtifactArg(exe);
- const tty_perf_step = b.step("perf-tty-edit", "measure text editing latency through the vaxis tty backend");
- if (sdl_backend) {
- tty_perf_step.dependOn(&b.addFail("perf-tty-edit measures the terminal backend; run without -Dsdl and without an Emscripten target").step);
- } else {
- tty_perf_step.dependOn(&run_tty_perf.step);
- }
+ const exe = b.addExecutable(.{
+ .name = "pardes",
+ .linkage = if (static) .static else null,
+ .root_module = root_mod,
+ });
+ b.installArtifact(exe);
+ const run = b.addRunArtifact(exe);
+ b.step("run", "descend to the pardes").dependOn(&run.step);
+ web_step.dependOn(&b.addFail("web needs -Dplatform=web -Dtarget=wasm32-emscripten -Ddump=<dump.zon>").step);
- const syntax_perf = b.addExecutable(.{
- .name = "pardes-syntax-perf",
- .linkage = exe_linkage,
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("tests_syntax_perf.zig"),
- .link_libc = true,
- }),
- });
- if (ghostty_vt_for_tests) |ghostty_vt| syntax_perf.root_module.addImport("ghostty-vt", ghostty_vt);
- const run_syntax_perf = b.addRunArtifact(syntax_perf);
- run_syntax_perf.addArtifactArg(exe);
- const syntax_perf_step = b.step("perf-syntax", "measure highlighted file open/edit latency through the vaxis tty backend");
- if (sdl_backend) {
- syntax_perf_step.dependOn(&b.addFail("perf-syntax measures the terminal backend; run without -Dsdl and without an Emscripten target").step);
- } else if (!syntax_highlighting) {
- syntax_perf_step.dependOn(&b.addFail("perf-syntax requires tree-sitter; run with -Dtree-sitter=minimal or -Dtree-sitter=full").step);
- } else {
- syntax_perf_step.dependOn(&run_syntax_perf.step);
+ // snapshot parity suite: scripted input traces in a pty, captured grids
+ // diffed against the goldens in snapshots/ — the frozen record of parity
+ // with the prototype this program replaced. `-- --update` regenerates.
+ const snap = b.addExecutable(.{
+ .name = "pardes-snap",
+ .root_module = b.createModule(.{
+ .target = target,
+ .optimize = optimize,
+ .root_source_file = b.path("test/snapshot.zig"),
+ .link_libc = true,
+ }),
+ });
+ if (ghostty_vt_for_snap) |vt| snap.root_module.addImport("ghostty-vt", vt);
+ b.installArtifact(snap);
+ const run_snap = b.addRunArtifact(snap);
+ run_snap.addArtifactArg(exe);
+ if (b.args) |args| run_snap.addArgs(args);
+ b.step("snap", "run the snapshot parity suite (-- --update regenerates goldens)").dependOn(&run_snap.step);
}
+}
- // Unit tests for the pure modal-editing math (modal.zig has no vaxis/ghostty
- // deps, so `zig test` on it is fast and needs no wired-in modules).
- const unit_tests = b.addTest(.{
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("modal.zig"),
- }),
- });
- unit_tests.linkage = exe_linkage;
- const run_unit = b.addRunArtifact(unit_tests);
- const unit_step = b.step("unit-test", "run modal.zig unit tests");
- unit_step.dependOn(&run_unit.step);
- test_step.dependOn(&run_unit.step);
-
- // petscii.zig is likewise pure (std only) — fold its matcher tests into both
- // the unit-test and the umbrella test step.
- const petscii_tests = b.addTest(.{
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("petscii.zig"),
- }),
- });
- petscii_tests.linkage = exe_linkage;
- const run_petscii = b.addRunArtifact(petscii_tests);
- unit_step.dependOn(&run_petscii.step);
- test_step.dependOn(&run_petscii.step);
-
- const pane_ui_tests = b.addTest(.{
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("pane_ui.zig"),
- }),
- });
- pane_ui_tests.linkage = exe_linkage;
- const run_pane_ui = b.addRunArtifact(pane_ui_tests);
- unit_step.dependOn(&run_pane_ui.step);
- test_step.dependOn(&run_pane_ui.step);
-
- const dump_tests = b.addTest(.{
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("dump.zig"),
- }),
- });
- dump_tests.linkage = exe_linkage;
- const run_dump = b.addRunArtifact(dump_tests);
- unit_step.dependOn(&run_dump.step);
- test_step.dependOn(&run_dump.step);
+// the html shell: a full-viewport canvas wired into Module (from the prototype)
+const html_shell =
+ \\<!doctype html>
+ \\<html>
+ \\<head>
+ \\ <meta charset="utf-8">
+ \\ <meta name="viewport" content="width=device-width, initial-scale=1">
+ \\ <title>pardes</title>
+ \\ <style>
+ \\ html, body { margin: 0; width: 100%; height: 100%; background: #121212; }
+ \\ body { overflow: hidden; }
+ \\ canvas { display: block; width: 100vw; height: 100vh; outline: none;
+ \\ image-rendering: crisp-edges; image-rendering: pixelated;
+ \\ -ms-interpolation-mode: nearest-neighbor;
+ \\ transform: translateZ(0); backface-visibility: hidden;
+ \\ touch-action: none; }
+ \\ </style>
+ \\</head>
+ \\<body>
+ \\ <canvas id="canvas" tabindex="0"></canvas>
+ \\ <script>
+ \\ const canvas = document.getElementById('canvas');
+ \\ canvas.addEventListener('contextmenu', (event) => event.preventDefault());
+ \\ canvas.addEventListener('pointerdown', (event) => {
+ \\ canvas.focus({ preventScroll: true });
+ \\ canvas.setPointerCapture?.(event.pointerId);
+ \\ });
+ \\ const releasePointer = (event) => {
+ \\ if (canvas.hasPointerCapture?.(event.pointerId)) canvas.releasePointerCapture(event.pointerId);
+ \\ };
+ \\ canvas.addEventListener('pointerup', releasePointer);
+ \\ canvas.addEventListener('pointercancel', releasePointer);
+ \\ window.Module = { canvas };
+ \\ </script>
+ \\ <script src="pardes.js"></script>
+ \\</body>
+ \\</html>
+;
- // Tutor practice blocks as unit tests: a second `zig test` target over the
- // generated tutor_cases.zig (which imports modal.zig). Depends on tutor-gen
- // so the file is (re)generated first.
- const tutor_tests = b.addTest(.{
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = tutor_cases_lazy,
- }),
- });
- tutor_tests.linkage = exe_linkage;
- // The generated cases `@import("modal")` (a module name, not a relative
- // path, since the generated file lives in the cache dir, not next to modal.zig).
- const modal_for_tutor = b.createModule(.{
- .target = target,
- .optimize = optimize,
- .root_source_file = b.path("modal.zig"),
- });
- tutor_tests.root_module.addImport("modal", modal_for_tutor);
- const run_tutor = b.addRunArtifact(tutor_tests);
- const tutor_step = b.step("tutor-test", "run the tutor practice-block tests");
- tutor_step.dependOn(&run_tutor.step);
- test_step.dependOn(&run_tutor.step);
+// a bad -D combination: fail both `zig build` and `zig build web` with the why
+fn failBuild(b: *std.Build, web_step: *std.Build.Step, msg: []const u8) void {
+ const fail = &b.addFail(msg).step;
+ web_step.dependOn(fail);
+ b.getInstallStep().dependOn(fail);
}
-// glslc <src> -fshader-stage=<stage> -o <out> -> .spv, returned as a LazyPath to
-// @embedFile. Only used by the SDL3 GPU UI backend (`-Dui=sdl`).
+// glslc <src> -fshader-stage=<stage> -o <out> -> .spv, returned as a LazyPath
+// for @embedFile. Only used by the SDL3 GPU shell (-Dplatform=gui).
fn compileGlsl(b: *std.Build, src: []const u8, stage: []const u8, out: []const u8) std.Build.LazyPath {
const cmd = b.addSystemCommand(&.{ "glslc", b.fmt("-fshader-stage={s}", .{stage}) });
cmd.addFileArg(b.path(src));
@@ -668,38 +376,12 @@ fn detectEmscriptenSysroot(b: *std.Build) ?[]const u8 {
if (b.option([]const u8, "emscripten-sysroot", "path to the Emscripten sysroot")) |path| return path;
if (b.sysroot) |path| return path;
if (envPath(b, "EMSCRIPTEN_SYSROOT")) |path| return path;
- if (envPath(b, "EM_CACHE")) |cache| {
- return b.pathJoin(&.{ cache, "sysroot" });
- }
- if (envPath(b, "EMSDK")) |emsdk| {
- return b.pathJoin(&.{ emsdk, "upstream", "emscripten", "cache", "sysroot" });
- }
- if (envPath(b, "HOME")) |home| {
- return b.pathJoin(&.{ home, ".cache", "emscripten", "sysroot" });
- }
+ if (envPath(b, "EM_CACHE")) |cache| return b.pathJoin(&.{ cache, "sysroot" });
+ if (envPath(b, "EMSDK")) |emsdk| return b.pathJoin(&.{ emsdk, "upstream", "emscripten", "cache", "sysroot" });
+ if (envPath(b, "HOME")) |home| return b.pathJoin(&.{ home, ".cache", "emscripten", "sysroot" });
return null;
}
-fn targetBinaryName(b: *std.Build, base: []const u8, target: std.Target) []const u8 {
- return switch (target.os.tag) {
- .linux, .emscripten => base,
- .macos, .freebsd, .openbsd, .netbsd, .dragonfly => b.fmt("{s}-{s}-{s}", .{
- base,
- @tagName(target.os.tag),
- archName(target.cpu.arch),
- }),
- else => base,
- };
-}
-
-fn archName(arch: std.Target.Cpu.Arch) []const u8 {
- return switch (arch) {
- .x86_64 => "x64",
- .aarch64 => "arm64",
- else => @tagName(arch),
- };
-}
-
fn envPath(b: *std.Build, name: []const u8) ?[]const u8 {
return b.graph.environ_map.get(name);
}