summaryrefslogtreecommitdiff
path: root/build.zig
diff options
context:
space:
mode:
Diffstat (limited to 'build.zig')
-rw-r--r--build.zig181
1 files changed, 133 insertions, 48 deletions
diff --git a/build.zig b/build.zig
index 5fa3081a..2f9d085d 100644
--- a/build.zig
+++ b/build.zig
@@ -154,28 +154,6 @@ pub fn build(b: *std.Build) void {
}
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 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(.{
- .target = b.graph.host,
- .optimize = .ReleaseSafe,
- .root_source_file = b.path("tools/embed_zig_sources.zig"),
- }),
- });
- const run_source_gen = b.addRunArtifact(source_gen);
- run_source_gen.addDirectoryArg(b.path("."));
- const embedded_sources = run_source_gen.addOutputFileArg("embedded_sources.zig");
- // The tracked set can change without any already-known input changing, so
- // 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 working-tree .zig files").dependOn(&inspect_sources.step);
-
snap_build.addWeb(b);
if (is_web and !is_web_target) return failBuild(b, web_step, "-Dplatform=web requires -Dtarget=wasm32-freestanding");
@@ -212,6 +190,40 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
});
+ // `pardes-isolate`: a THIRD compilation of the same graph whose only
+ // difference is one comptime bool. It cannot be the same binary with a
+ // flag, because the point is that the isolated build does not CONTAIN the
+ // filesystem: look.zig's libc paths compile away, so no runtime mistake
+ // can reach a disk that a `--flag` build still links.
+ //
+ // Only the tty platform has one. gui adds a GPU it would still need, and
+ // web/macos are libraries whose host owns main().
+ const isolated_mod: ?*std.Build.Module = if (platform == .tty) b.createModule(.{
+ .target = target,
+ .optimize = optimize,
+ .root_source_file = b.path("src/main.zig"),
+ .link_libc = true,
+ }) else null;
+
+ // Every module that compiles src/pardes.zig, so its wiring is applied once
+ // per dependency instead of once per module per dependency.
+ var core_mods_buf: [3]*std.Build.Module = .{ root_mod, hx_core_mod, undefined };
+ var core_mods_len: usize = 2;
+ if (isolated_mod) |m| {
+ core_mods_buf[2] = m;
+ core_mods_len = 3;
+ }
+ const core_mods = core_mods_buf[0..core_mods_len];
+
+ // The conditional dependencies, hoisted so one `wireCore` call at the end
+ // can name them all. The unconditional ones are ordinary consts below.
+ var zls_mod: ?*std.Build.Module = null;
+ var ts_mod: ?*std.Build.Module = null;
+ var ts_queries_opts: ?*std.Build.Step.Options = null;
+ var mupdf_core_mod: ?*std.Build.Module = null;
+ var ghostty_core_vt: ?*std.Build.Module = null;
+ var ts_libs: std.ArrayList(*std.Build.Step.Compile) = .empty;
+
// THEMES. tools/gen_themes.zig turns each vendored helix .toml / zed .json
// into one .zig file per theme plus a list.zig that imports them all;
// src/pardes.zig folds that list into the ring beside the three it ships.
@@ -243,8 +255,6 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
.root_source_file = themes_out.path(b, "list.zig"),
});
- root_mod.addImport("generated_themes", themes_mod);
- hx_core_mod.addImport("generated_themes", themes_mod);
// forkpty: in libc proper on glibc>=2.34 and darwin; the BSDs keep it in libutil
if (target.result.os.tag == .freebsd or target.result.os.tag == .netbsd or target.result.os.tag == .openbsd)
@@ -273,6 +283,9 @@ pub fn build(b: *std.Build) void {
// names: the .zon pins a commit, and a status screen that cannot say WHICH
// analyser answered is not worth opening.
opts.addOption([]const u8, "zls_version", if (zls_backend) zls_version else "none");
+ // Mirrors build.zig.zon's `.version`; kept in sync by hand because a typed
+ // @import of the .zon rejects its other fields on this Zig 0.16 build.
+ opts.addOption([]const u8, "version", "0.0.1");
// The stdlib this binary was compiled against, so `gd` on `std.mem.count`
// can open the same mem.zig the compiler used. ZLS resolves `@import("std")`
// through `zig_lib_dir` and nothing else; without it every std symbol is a
@@ -299,8 +312,7 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
.@"version-string" = @as([]const u8, zls_version),
});
- root_mod.addImport("zls", zls_dep.module("zls"));
- hx_core_mod.addImport("zls", zls_dep.module("zls"));
+ zls_mod = zls_dep.module("zls");
}
// Tracy zones (src/tracy.zig): compile the client into the binary only when
@@ -354,8 +366,7 @@ pub fn build(b: *std.Build) void {
.flags = &.{ "-std=c11", "-DNDEBUG=1" },
});
}
- root_mod.addImport("tree-sitter", tree_sitter_mod);
- hx_core_mod.addImport("tree-sitter", tree_sitter_mod);
+ ts_mod = tree_sitter_mod;
const ts_queries = b.addOptions();
inline for (grammar_manifest.all) |g| {
@@ -386,12 +397,10 @@ pub fn build(b: *std.Build) void {
if (g.scanner) lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/scanner.c"), .flags = &ts_cflags });
lib.root_module.addIncludePath(dep.path(g.src));
if (is_web) lib.root_module.addIncludePath(b.path("src/web/libc/include"));
- root_mod.linkLibrary(lib);
- hx_core_mod.linkLibrary(lib);
+ ts_libs.append(b.allocator, lib) catch @panic("OOM");
}
}
- root_mod.addOptions("ts_queries", ts_queries);
- hx_core_mod.addOptions("ts_queries", ts_queries);
+ ts_queries_opts = ts_queries;
}
// zstbi (C stb_image): image pane decode. The freestanding core has no C
@@ -403,16 +412,12 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
.root_source_file = b.path("src/web/zstbi.zig"),
});
- root_mod.addImport("zstbi", zstbi_mod);
- hx_core_mod.addImport("zstbi", zstbi_mod);
// mvzr: the regex engine behind `s` / `S` (helix select/split on a regex).
// A bytecode VM that compiles a RUNTIME pattern into a fixed-size struct
// with no allocator at all, which is exactly the shape an interactive
// prompt needs — see applySelRegex in src/pardes.zig.
const mvzr_mod = b.dependency("mvzr", .{ .target = target, .optimize = optimize }).module("mvzr");
- root_mod.addImport("mvzr", mvzr_mod);
- hx_core_mod.addImport("mvzr", mvzr_mod);
// MuPDF is the default native PDF engine, but remains a genuinely optional
// dependency: with -Dmupdf=false (and by default on web), the lazy source
@@ -439,8 +444,7 @@ pub fn build(b: *std.Build) void {
.file = b.path("src/pdf_bridge.c"),
.flags = &.{"-std=gnu11"},
});
- root_mod.addImport("mupdf", mupdf_mod);
- hx_core_mod.addImport("mupdf", mupdf_mod);
+ mupdf_core_mod = mupdf_mod;
// Compile the @cImport + exception bridge and exercise the Zig
// wrapper's caller-owned RGBA boundary alongside the C end-to-end
// document probe below.
@@ -604,15 +608,26 @@ pub fn build(b: *std.Build) void {
const ghostty_vt = dep.module("ghostty-vt");
ghostty_vt_for_snap = ghostty_vt;
ghostty_vt.addImport("uucode", uucode_mod);
- root_mod.addImport("ghostty-vt", ghostty_vt);
- hx_core_mod.addImport("ghostty-vt", ghostty_vt);
+ ghostty_core_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);
- hx_core_mod.addImport("vaxis", vaxis_mod);
- root_mod.addImport("uucode", uucode_mod);
- hx_core_mod.addImport("uucode", uucode_mod);
+
+ // One call per module instead of one line per module per dependency, and
+ // BEFORE the fold below, which reads what these imports put in the table.
+ for (core_mods) |m| wireCore(m, .{
+ .themes = themes_mod,
+ .zls = zls_mod,
+ .tree_sitter = ts_mod,
+ .ts_libs = ts_libs.items,
+ .ts_queries = ts_queries_opts,
+ .zstbi = zstbi_mod,
+ .mvzr = mvzr_mod,
+ .mupdf = mupdf_core_mod,
+ .ghostty_vt = ghostty_core_vt,
+ .vaxis = vaxis_mod,
+ .uucode = uucode_mod,
+ });
// --- the dependency module map, for `gd` on `@import("vaxis")` ---
//
@@ -662,10 +677,12 @@ pub fn build(b: *std.Build) void {
opts.addOption([]const []const u8, "module_names", mod_names);
opts.addOption([]const []const u8, "module_roots", mod_roots);
- 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| {
+ // Attached after the fold above, for the reason `opts` names, and with the
+ // embedded FILES: those are bytes rather than modules, and adding them
+ // earlier would list them in the module map as if they were importable.
+ for (core_mods) |mod| {
+ mod.addOptions("pardes_config", opts);
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 })) },
@@ -673,6 +690,24 @@ pub fn build(b: *std.Build) void {
if (platform == .macos) mod.addAnonymousImport("effect-source-crt.ci.metal", .{
.root_source_file = b.path("shaders/crt.ci.metal"),
});
+ // The virtual filesystem's two repo-root entries: `@embedFile` cannot
+ // reach outside the module's own directory, so they arrive by name.
+ // See src/source_manifest.zig.
+ mod.addAnonymousImport("root-build.zig", .{ .root_source_file = b.path("build.zig") });
+ mod.addAnonymousImport("root-build.zig.zon", .{ .root_source_file = b.path("build.zig.zon") });
+ }
+
+ // The one setting that produces a SECOND executable rather than changing
+ // this one: its own tiny options module, because duplicating `opts` to
+ // flip a single bool would be twenty lines that must then agree forever.
+ const iso_off = b.addOptions();
+ iso_off.addOption(bool, "isolated", false);
+ root_mod.addOptions("pardes_isolation", iso_off);
+ hx_core_mod.addOptions("pardes_isolation", iso_off);
+ if (isolated_mod) |m| {
+ const iso_on = b.addOptions();
+ iso_on.addOption(bool, "isolated", true);
+ m.addOptions("pardes_isolation", iso_on);
}
// SDL3 shell wiring (-Dplatform=gui). SDL3 and FreeType are both built
@@ -723,7 +758,6 @@ pub fn build(b: *std.Build) void {
root_mod.addAnonymousImport("embedded.dump.zon", .{
.root_source_file = if (std.fs.path.isAbsolute(dp)) .{ .cwd_relative = dp } else b.path(dp),
});
- root_mod.addAnonymousImport("embedded_sources", .{ .root_source_file = embedded_sources });
}
if (platform == .web) {
@@ -1009,6 +1043,24 @@ pub fn build(b: *std.Build) void {
b.installArtifact(exe);
const run = b.addRunArtifact(exe);
b.step("run", "descend to the pardes").dependOn(&run.step);
+ // `pardes-isolate`: the SAME source on the core's own defaults — the
+ // embedded source filesystem, the in-process clipboard, silent ptys.
+ // Its host keeps only the terminal it draws on, so nothing is forked
+ // and there is no filesystem in the binary to reach. A pardes that can
+ // only read itself.
+ if (isolated_mod) |iso| {
+ const iso_exe = b.addExecutable(.{
+ .name = "pardes-isolate",
+ .linkage = if (static) .static else null,
+ .root_module = iso,
+ });
+ b.installArtifact(iso_exe);
+ const run_iso = b.addRunArtifact(iso_exe);
+ // Something to look at on arrival, read out of the binary itself.
+ if (b.args) |args| run_iso.addArgs(args) else run_iso.addArg("src/pardes.zig");
+ b.step("run-isolated", "descend to a pardes with no host but the terminal")
+ .dependOn(&run_iso.step);
+ }
web_step.dependOn(&b.addFail("web needs -Dplatform=web -Dtarget=wasm32-freestanding -Ddump=<dump.zon>").step);
// snapshot parity suite: scripted input traces in a pty, captured grids
@@ -1309,3 +1361,36 @@ fn vendoredThemes(b: *std.Build, io: std.Io) []const []const u8 {
}.lt);
return names;
}
+
+/// Everything every compilation of src/pardes.zig needs, in one call per module
+/// rather than a line per module beside every dependency — which is the shape
+/// that let a third module quietly miss two of them.
+///
+/// Fields are in the order these used to be attached in, because that order IS
+/// the module map `Debug` prints (import_table iterates by insertion), and a
+/// refactor that renumbers a user-visible list changed something.
+fn wireCore(m: *std.Build.Module, d: struct {
+ themes: ?*std.Build.Module = null,
+ zls: ?*std.Build.Module = null,
+ tree_sitter: ?*std.Build.Module = null,
+ ts_libs: []const *std.Build.Step.Compile = &.{},
+ ts_queries: ?*std.Build.Step.Options = null,
+ zstbi: ?*std.Build.Module = null,
+ mvzr: ?*std.Build.Module = null,
+ mupdf: ?*std.Build.Module = null,
+ ghostty_vt: ?*std.Build.Module = null,
+ vaxis: ?*std.Build.Module = null,
+ uucode: ?*std.Build.Module = null,
+}) void {
+ if (d.themes) |x| m.addImport("generated_themes", x);
+ if (d.zls) |x| m.addImport("zls", x);
+ if (d.tree_sitter) |x| m.addImport("tree-sitter", x);
+ for (d.ts_libs) |lib| m.linkLibrary(lib);
+ if (d.ts_queries) |x| m.addOptions("ts_queries", x);
+ if (d.zstbi) |x| m.addImport("zstbi", x);
+ if (d.mvzr) |x| m.addImport("mvzr", x);
+ if (d.mupdf) |x| m.addImport("mupdf", x);
+ if (d.ghostty_vt) |x| m.addImport("ghostty-vt", x);
+ if (d.vaxis) |x| m.addImport("vaxis", x);
+ if (d.uucode) |x| m.addImport("uucode", x);
+}