summaryrefslogtreecommitdiff
path: root/build.zig
diff options
context:
space:
mode:
Diffstat (limited to 'build.zig')
-rw-r--r--build.zig327
1 files changed, 250 insertions, 77 deletions
diff --git a/build.zig b/build.zig
index 14fefaf5..1b9a4351 100644
--- a/build.zig
+++ b/build.zig
@@ -1,10 +1,14 @@
const std = @import("std");
const AppKind = enum { main, replay };
+const GhosttyArtifact = enum { ghostty, lib };
pub fn build(b: *std.Build) void {
- const optimize = b.standardOptimizeOption(.{});
+ const requested_optimize = b.standardOptimizeOption(.{});
const target = b.standardTargetOptions(.{});
+ const is_emscripten = target.result.os.tag == .emscripten;
+ const optimize = if (is_emscripten) .ReleaseSmall else requested_optimize;
+ const emscripten_sysroot = if (is_emscripten) detectEmscriptenSysroot(b) else null;
// build-time IO, only used to slurp the grammars' highlights.scm queries.
var threaded: std.Io.Threaded = .init(b.allocator, .{});
@@ -24,6 +28,25 @@ pub fn build(b: *std.Build) void {
// 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 syntax_highlighting = !is_emscripten;
+ 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 files = b.addWriteFiles();
+ break :blk files.add("emscripten-libc.txt", b.fmt(
+ \\include_dir={s}/include
+ \\sys_include_dir={s}/include
+ \\crt_dir={s}/lib/wasm32-emscripten
+ \\msvc_lib_dir=
+ \\kernel32_lib_dir=
+ \\gcc_dir=
+ \\
+ , .{ sysroot, sysroot, sysroot }));
+ } else null;
const exe = b.addExecutable(.{
.name = "pardes",
@@ -45,6 +68,10 @@ pub fn build(b: *std.Build) void {
.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,
@@ -58,11 +85,21 @@ pub fn build(b: *std.Build) void {
const main_app_config = b.addOptions();
main_app_config.addOption(AppKind, "app_kind", .main);
+ main_app_config.addOption(bool, "syntax_highlighting", syntax_highlighting);
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.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
@@ -100,7 +137,7 @@ pub fn build(b: *std.Build) void {
// 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 = false,
+ .simd = !is_emscripten,
})) |dep| {
// Accessing ghostty's modules runs its build(), which (because vaxis is
// a shared dependency, deduplicated to the same module) wires ghostty's
@@ -108,6 +145,23 @@ pub fn build(b: *std.Build) void {
// with ours — order matters, so this must happen after this point.
const ghostty_vt = dep.module("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
@@ -124,7 +178,7 @@ pub fn build(b: *std.Build) void {
// `"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) b.path("ui_sdl.zig") else b.path("ui_vaxis.zig");
+ const ui_root = if (sdl_backend) b.path("ui_sdl.zig") else b.path("ui_vaxis.zig");
const ui_mod = b.createModule(.{
.target = target,
.optimize = optimize,
@@ -134,19 +188,30 @@ pub fn build(b: *std.Build) void {
exe.root_module.addImport("ui", ui_mod);
replay.root_module.addImport("ui", ui_mod);
- // SDL3 GPU backend wiring (`-Dsdl`). The sdl package builds SDL3 from source
+ // 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 DejaVuSansMono font. All gated on the flag so a plain `zig build`
// (vaxis) never fetches or compiles SDL.
- if (sdl) {
- const sdl_dep = b.lazyDependency("sdl", .{
- .target = target,
- .optimize = optimize,
- }) orelse return; // not fetched (offline?) — skip SDL wiring
+ 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;
// stb_truetype: the header (declarations, via include path) + the impl TU
@@ -156,13 +221,18 @@ pub fn build(b: *std.Build) void {
ui_mod.addCSourceFile(.{ .file = b.path("vendor/stb/stb_truetype_impl.c") });
ui_mod.addCSourceFile(.{ .file = b.path("vendor/stb/font.c") });
- // 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"),
- });
+ 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") });
+ } 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"),
+ });
+ }
}
// zstbi: C (stb_image)-backed image decoder for the image-viewer panes. We use
@@ -170,73 +240,77 @@ pub fn build(b: *std.Build) void {
// 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_mod = b.dependency("zstbi", .{ .target = target, .optimize = optimize }).module("root");
+ 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);
- // 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 });
- exe.root_module.addImport("tree-sitter", tree_sitter.module("tree_sitter"));
- replay.root_module.addImport("tree-sitter", tree_sitter.module("tree_sitter"));
+ 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");
+ exe.root_module.addImport("tree-sitter", tree_sitter_mod);
+ replay.root_module.addImport("tree-sitter", tree_sitter_mod);
- const Grammar = struct {
- name: []const u8,
- dep: []const u8,
- src: []const u8 = "src",
- scanner: bool = false,
- query: []const u8 = "queries/highlights.scm",
- local_query: ?[]const u8 = null,
- };
- const grammars = [_]Grammar{
- .{ .name = "ada", .dep = "ts_ada" },
- .{ .name = "bash", .dep = "ts_bash", .scanner = true },
- .{ .name = "c", .dep = "ts_c" },
- .{ .name = "c_sharp", .dep = "ts_c_sharp", .scanner = true },
- .{ .name = "clojure", .dep = "ts_clojure" },
- .{ .name = "cpp", .dep = "ts_cpp", .scanner = true },
- .{ .name = "css", .dep = "ts_css", .scanner = true },
- .{ .name = "elixir", .dep = "ts_elixir", .scanner = true },
- .{ .name = "erlang", .dep = "ts_erlang", .scanner = true },
- .{ .name = "fortran", .dep = "ts_fortran", .scanner = true },
- .{ .name = "go", .dep = "ts_go" },
- .{ .name = "haskell", .dep = "ts_haskell", .scanner = true },
- .{ .name = "html", .dep = "ts_html", .scanner = true },
- .{ .name = "java", .dep = "ts_java" },
- .{ .name = "javascript", .dep = "ts_javascript", .scanner = true },
- .{ .name = "json", .dep = "ts_json" },
- .{ .name = "kotlin", .dep = "ts_kotlin", .scanner = true },
- .{ .name = "ocaml", .dep = "ts_ocaml", .src = "grammars/ocaml/src", .scanner = true },
- .{ .name = "pascal", .dep = "ts_pascal" },
- .{ .name = "php", .dep = "ts_php", .src = "php/src", .scanner = true },
- .{ .name = "powershell", .dep = "ts_powershell", .scanner = true },
- .{ .name = "python", .dep = "ts_python", .scanner = true },
- .{ .name = "ruby", .dep = "ts_ruby", .scanner = true },
- .{ .name = "rust", .dep = "ts_rust", .scanner = true },
- .{ .name = "scala", .dep = "ts_scala", .scanner = true },
- .{ .name = "zig", .dep = "ts_zig" },
- };
- const ts_queries = b.addOptions();
- inline for (grammars) |g| {
- const dep = b.dependency(g.dep, .{});
- const query_path = if (g.local_query) |p| b.path(p) else 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);
+ const Grammar = struct {
+ name: []const u8,
+ dep: []const u8,
+ src: []const u8 = "src",
+ scanner: bool = false,
+ query: []const u8 = "queries/highlights.scm",
+ local_query: ?[]const u8 = null,
+ };
+ const grammars = [_]Grammar{
+ .{ .name = "ada", .dep = "ts_ada" },
+ .{ .name = "bash", .dep = "ts_bash", .scanner = true },
+ .{ .name = "c", .dep = "ts_c" },
+ .{ .name = "c_sharp", .dep = "ts_c_sharp", .scanner = true },
+ .{ .name = "clojure", .dep = "ts_clojure" },
+ .{ .name = "cpp", .dep = "ts_cpp", .scanner = true },
+ .{ .name = "css", .dep = "ts_css", .scanner = true },
+ .{ .name = "elixir", .dep = "ts_elixir", .scanner = true },
+ .{ .name = "erlang", .dep = "ts_erlang", .scanner = true },
+ .{ .name = "fortran", .dep = "ts_fortran", .scanner = true },
+ .{ .name = "go", .dep = "ts_go" },
+ .{ .name = "haskell", .dep = "ts_haskell", .scanner = true },
+ .{ .name = "html", .dep = "ts_html", .scanner = true },
+ .{ .name = "java", .dep = "ts_java" },
+ .{ .name = "javascript", .dep = "ts_javascript", .scanner = true },
+ .{ .name = "json", .dep = "ts_json" },
+ .{ .name = "kotlin", .dep = "ts_kotlin", .scanner = true },
+ .{ .name = "ocaml", .dep = "ts_ocaml", .src = "grammars/ocaml/src", .scanner = true },
+ .{ .name = "pascal", .dep = "ts_pascal" },
+ .{ .name = "php", .dep = "ts_php", .src = "php/src", .scanner = true },
+ .{ .name = "powershell", .dep = "ts_powershell", .scanner = true },
+ .{ .name = "python", .dep = "ts_python", .scanner = true },
+ .{ .name = "ruby", .dep = "ts_ruby", .scanner = true },
+ .{ .name = "rust", .dep = "ts_rust", .scanner = true },
+ .{ .name = "scala", .dep = "ts_scala", .scanner = true },
+ .{ .name = "zig", .dep = "ts_zig" },
+ };
+ const ts_queries = b.addOptions();
+ inline for (grammars) |g| {
+ const dep = b.dependency(g.dep, .{});
+ const query_path = if (g.local_query) |p| b.path(p) else 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);
- const lib = b.addLibrary(.{
- .name = "tree-sitter-" ++ g.name,
- .root_module = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true }),
- .linkage = .static,
- });
- 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);
+ const lib = b.addLibrary(.{
+ .name = "tree-sitter-" ++ g.name,
+ .root_module = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true }),
+ .linkage = .static,
+ });
+ 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);
+ }
+ exe.root_module.addOptions("ts_queries", ts_queries);
+ replay.root_module.addOptions("ts_queries", ts_queries);
}
- exe.root_module.addOptions("ts_queries", ts_queries);
- replay.root_module.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`.
@@ -253,6 +327,85 @@ pub fn build(b: *std.Build) void {
const replay_step = b.step("replay", "replay a pardes dump: zig build replay -- <dump.zon>");
replay_step.dependOn(&run_replay.step);
+ const replay_wasm_step = b.step("replay-wasm", "build browser SDL replay (requires -Dtarget=wasm32-emscripten -Dreplay-dump=<dump.zon>)");
+ if (!is_emscripten) {
+ replay_wasm_step.dependOn(&b.addFail("replay-wasm requires -Dtarget=wasm32-emscripten").step);
+ } else if (replay_dump == null) {
+ replay_wasm_step.dependOn(&b.addFail("replay-wasm requires -Dreplay-dump=<dump.zon>").step);
+ } else if (emscripten_sysroot == null) {
+ replay_wasm_step.dependOn(&b.addFail("replay-wasm requires an Emscripten sysroot; pass --sysroot <emscripten>/cache/sysroot or set EM_CACHE/EMSDK").step);
+ } else {
+ 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 replay-wasm 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());
+ 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",
+ "-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_wasm_step.dependOn(&b.addInstallFileWithDir(replay_js, .{ .custom = "web" }, "pardes-replay.js").step);
+ replay_wasm_step.dependOn(&b.addInstallFileWithDir(replay_js.dirname().path(b, "pardes-replay.wasm"), .{ .custom = "web" }, "pardes-replay.wasm").step);
+
+ const web = b.addWriteFiles();
+ const html = web.add("replay.html",
+ \\<!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; min-width: 100%; min-height: 100%; background: #121212; }
+ \\ body { overflow: auto; }
+ \\ canvas { display: block; }
+ \\ </style>
+ \\</head>
+ \\<body>
+ \\ <canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
+ \\ <script>
+ \\ window.Module = { canvas: document.getElementById('canvas') };
+ \\ </script>
+ \\ <script src="pardes-replay.js"></script>
+ \\</body>
+ \\</html>
+ );
+ replay_wasm_step.dependOn(&b.addInstallFileWithDir(html, .{ .custom = "web" }, "replay.html").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);
+ } 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.step.dependOn(replay_wasm_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.
@@ -380,3 +533,23 @@ fn compileGlsl(b: *std.Build, src: []const u8, stage: []const u8, out: []const u
cmd.addArg("-o");
return cmd.addOutputFileArg(out);
}
+
+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" });
+ }
+ return null;
+}
+
+fn envPath(b: *std.Build, name: []const u8) ?[]const u8 {
+ return b.graph.environ_map.get(name);
+}