const std = @import("std"); const UpdateChannel = enum { stable, dev }; const WasmSurface = enum { none, core, term,}; const PgsoArtifact = enum { fx, file_index, ui_activity, approval_review,}; const NapiSurface = enum { none, core,}; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const pgso_artifact = b.option( PgsoArtifact, "pgso-artifact", "Emit ReleaseSafe LLVM bitcode for one PGO/PGSO artifact", ); const wasm_surface = b.option( WasmSurface, "wasm-surface", "Build a WASI WebAssembly surface for JavaScript hosts (core or term)", ) orelse .none; const napi_surface = b.option( NapiSurface, "napi-surface", "Build a Node-API addon surface (core)", ) orelse .none; const git_commit = readGitCommit(b); const app_version = readAppVersion(b); const update_channel = b.option(UpdateChannel, "update-channel", "Build update channel (stable or dev)") orelse .stable; const build_options = b.addOptions(); build_options.addOption([]const u8, "git_commit", git_commit); build_options.addOption([]const u8, "app_version", app_version); build_options.addOption([]const u8, "update_channel", @tagName(update_channel)); build_options.addOption(WasmSurface, "wasm_surface", .none); const exe = b.addExecutable(.{ .name = "fx", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, .link_libc = true, .stack_check = false, .stack_protector = false, .omit_frame_pointer = true, .unwind_tables = .none, .error_tracing = false, .strip = optimize != .Debug, }), }); exe.root_module.addImport("build_options", build_options.createModule()); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run fx"); run_step.dependOn(&run_cmd.step); const exe_tests = b.addTest(.{ .root_module = exe.root_module, }); const run_exe_tests = b.addRunArtifact(exe_tests); run_exe_tests.step.dependOn(b.getInstallStep()); run_exe_tests.setEnvironmentVariable( "FX_TEST_PRODUCT_EXE", b.getInstallPath(.bin, "fx"), ); const test_step = b.step("test", "Run tests"); test_step.dependOn(&run_exe_tests.step); if (wasm_surface != .none) { addWasmArtifact(b, wasm_surface, git_commit, app_version, update_channel); } if (napi_surface != .none) { addNapiArtifact(b, napi_surface, target, git_commit, app_version, update_channel); } const mcp_test_exports = b.createModule(.{ .root_source_file = b.path("src/mcp_test_exports.zig"), .target = target, .optimize = optimize, .link_libc = true, }); mcp_test_exports.addImport("build_options", build_options.createModule()); const json_schema_corpus = b.addExecutable(.{ .name = "json-schema-corpus", .root_module = b.createModule(.{ .root_source_file = b.path("tests/json-schema/corpus_runner.zig"), .target = target, .optimize = optimize, .link_libc = true, }), }); json_schema_corpus.root_module.addImport( "mcp_test_exports", mcp_test_exports, ); const run_json_schema_corpus = b.addRunArtifact(json_schema_corpus); if (b.args) |args| run_json_schema_corpus.addArgs(args); const json_schema_corpus_step = b.step( "run-json-schema-corpus", "Run the pinned JSON Schema Test Suite corpus", ); json_schema_corpus_step.dependOn(&run_json_schema_corpus.step); const mcp_dispatcher_e2e = b.addExecutable(.{ .name = "mcp-stdio-dispatcher-driver", .root_module = b.createModule(.{ .root_source_file = b.path( "tests/e2e/fixtures/mcp-stdio-dispatcher-driver.zig", ), .target = target, .optimize = optimize, .link_libc = true, }), }); mcp_dispatcher_e2e.root_module.addImport( "mcp_test_exports", mcp_test_exports, ); const run_mcp_dispatcher_e2e = b.addRunArtifact(mcp_dispatcher_e2e); if (b.args) |args| run_mcp_dispatcher_e2e.addArgs(args); const mcp_dispatcher_e2e_step = b.step( "run-mcp-stdio-dispatcher-e2e", "Run the MCP stdio dispatcher E2E driver", ); mcp_dispatcher_e2e_step.dependOn(&run_mcp_dispatcher_e2e.step); // --- file_index search benchmark --- const benchmark_exports_mod = b.createModule(.{ .root_source_file = b.path("src/benchmark_exports.zig"), .target = target, .optimize = optimize, .link_libc = true, }); const file_index_bench = b.addExecutable(.{ .name = "file-index-bench", .root_module = b.createModule(.{ .root_source_file = b.path("benchmarks/file_index_bench.zig"), .target = target, .optimize = optimize, .link_libc = true, }), }); file_index_bench.root_module.addImport("file_index", benchmark_exports_mod); const install_bench = b.addInstallArtifact(file_index_bench, .{}); const bench_step = b.step("bench-file-index", "Build file_index search benchmark"); bench_step.dependOn(&install_bench.step); const run_bench = b.addRunArtifact(file_index_bench); run_bench.step.dependOn(&install_bench.step); if (b.args) |args| run_bench.addArgs(args); const run_bench_step = b.step("run-bench-file-index", "Build and run file_index search benchmark"); run_bench_step.dependOn(&run_bench.step); // --- UI activity progress benchmark --- const ui_activity_bench = b.addExecutable(.{ .name = "ui-activity-progress-bench", .root_module = b.createModule(.{ .root_source_file = b.path("benchmarks/activity_progress.zig"), .target = target, .optimize = optimize, .link_libc = true, }), }); ui_activity_bench.root_module.addImport( "benchmark_exports", benchmark_exports_mod, ); const install_ui_activity_bench = b.addInstallArtifact(ui_activity_bench, .{}); const ui_activity_bench_step = b.step( "bench-ui-activity", "Build the UI activity progress benchmark", ); ui_activity_bench_step.dependOn(&install_ui_activity_bench.step); const run_ui_activity_bench = b.addRunArtifact(ui_activity_bench); run_ui_activity_bench.step.dependOn(&install_ui_activity_bench.step); const run_ui_activity_bench_step = b.step( "run-bench-ui-activity", "Build and run the UI activity progress benchmark", ); run_ui_activity_bench_step.dependOn(&run_ui_activity_bench.step); const ui_activity_bench_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("benchmarks/activity_progress.zig"), .target = target, .optimize = optimize, }), }); ui_activity_bench_tests.root_module.addImport( "benchmark_exports", benchmark_exports_mod, ); const run_ui_activity_bench_tests = b.addRunArtifact(ui_activity_bench_tests); test_step.dependOn(&run_ui_activity_bench_tests.step); const test_ui_activity_bench_step = b.step( "test-ui-activity-benchmark", "Run UI activity benchmark policy tests", ); test_ui_activity_bench_step.dependOn(&run_ui_activity_bench_tests.step); // --- file-diff approval review benchmark --- const approval_review_bench = b.addExecutable(.{ .name = "approval-review-bench", .root_module = b.createModule(.{ .root_source_file = b.path("benchmarks/approval_review.zig"), .target = target, .optimize = optimize, .link_libc = true, }), }); approval_review_bench.root_module.addImport( "benchmark_exports", benchmark_exports_mod, ); const install_approval_review_bench = b.addInstallArtifact( approval_review_bench, .{}, ); const approval_review_bench_step = b.step( "bench-approval-review", "Build the file-diff approval review benchmark", ); approval_review_bench_step.dependOn(&install_approval_review_bench.step); const run_approval_review_bench = b.addRunArtifact(approval_review_bench); run_approval_review_bench.step.dependOn(&install_approval_review_bench.step); if (b.args) |args| run_approval_review_bench.addArgs(args); const run_approval_review_bench_step = b.step( "run-bench-approval-review", "Build and run the file-diff approval review benchmark", ); run_approval_review_bench_step.dependOn(&run_approval_review_bench.step); const approval_review_bench_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("benchmarks/approval_review.zig"), .target = target, .optimize = optimize, }), }); approval_review_bench_tests.root_module.addImport( "benchmark_exports", benchmark_exports_mod, ); const run_approval_review_bench_tests = b.addRunArtifact( approval_review_bench_tests, ); const test_approval_review_bench_step = b.step( "test-approval-review-benchmark", "Run file-diff approval review benchmark qualification tests", ); test_approval_review_bench_step.dependOn( &run_approval_review_bench_tests.step, ); const pgso_ir_step = b.step( "pgso-ir", "Emit selected ReleaseSafe LLVM bitcode for PGO/PGSO qualification", ); if (pgso_artifact) |artifact| { const selected: *std.Build.Step.Compile = switch (artifact) { .fx => exe, .file_index => file_index_bench, .ui_activity => ui_activity_bench, .approval_review => approval_review_bench, }; const output_name = switch (artifact) { .fx => "pgso/fx.bc", .file_index => "pgso/file-index.bc", .ui_activity => "pgso/ui-activity.bc", .approval_review => "pgso/approval-review.bc", }; const install_ir = b.addInstallFile( selected.getEmittedLlvmBc(), output_name, ); pgso_ir_step.dependOn(&install_ir.step); } else { const missing_artifact = b.addFail( "pgso-ir requires -Dpgso-artifact", ); pgso_ir_step.dependOn(&missing_artifact.step); }} fn addWasmArtifact( b: *std.Build, surface: WasmSurface, git_commit: []const u8, app_version: []const u8, update_channel: UpdateChannel,) void { const wasm_target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .wasi, }); const name = switch (surface) { .core => "fx-core", .term => "fx-term", .none => unreachable, }; const description = switch (surface) { .core => "Build the headless fx WebAssembly artifact", .term => "Build the terminal fx WebAssembly artifact", .none => unreachable, }; const wasm_options = b.addOptions(); wasm_options.addOption([]const u8, "git_commit", git_commit); wasm_options.addOption([]const u8, "app_version", app_version); wasm_options.addOption([]const u8, "update_channel", @tagName(update_channel)); wasm_options.addOption(WasmSurface, "wasm_surface", surface); const wasm_root = switch (surface) { .core => "src/wasm_core_main.zig", .term => "src/wasm_term_main.zig", .none => unreachable, }; const wasm_exe = b.addExecutable(.{ .name = name, .root_module = b.createModule(.{ .root_source_file = b.path(wasm_root), .target = wasm_target, .optimize = .ReleaseSmall, .single_threaded = true, .link_libc = true, .stack_check = false, .stack_protector = false, .omit_frame_pointer = true, .unwind_tables = .none, .error_tracing = false, .strip = true, }), }); wasm_exe.root_module.addImport("build_options", wasm_options.createModule()); const install_wasm = b.addInstallArtifact(wasm_exe, .{}); const wasm_step = b.step(name ++ "-wasm", description); wasm_step.dependOn(&install_wasm.step); b.getInstallStep().dependOn(&install_wasm.step);} fn addNapiArtifact( b: *std.Build, surface: NapiSurface, target: std.Build.ResolvedTarget, git_commit: []const u8, app_version: []const u8, update_channel: UpdateChannel,) void { const napi_options = b.addOptions(); napi_options.addOption([]const u8, "git_commit", git_commit); napi_options.addOption([]const u8, "app_version", app_version); napi_options.addOption([]const u8, "update_channel", @tagName(update_channel)); napi_options.addOption(NapiSurface, "napi_surface", surface); const root = switch (surface) { .core => "src/napi_core_main.zig", .none => unreachable, }; const lib = b.addLibrary(.{ .name = "libfx", .linkage = .dynamic, .root_module = b.createModule(.{ .root_source_file = b.path(root), .target = target, .optimize = .ReleaseSafe, .link_libc = true, .strip = true, }), }); lib.root_module.addImport("build_options", napi_options.createModule()); const node_include = b.option( []const u8, "node-include-dir", "Directory containing node_api.h for the N-API addon", ) orelse discoverNodeIncludeDir(b); lib.root_module.addSystemIncludePath(.{ .cwd_relative = node_include }); lib.linker_allow_shlib_undefined = true; const install = b.addInstallArtifact(lib, .{ .dest_sub_path = "libfx.node" }); const step = b.step("libfx-napi", "Build the libfx Node-API core addon"); step.dependOn(&install.step); b.getInstallStep().dependOn(&install.step);} fn discoverNodeIncludeDir(b: *std.Build) []const u8 { var code: u8 = 0; const out = b.runAllowFail( &.{ "node", "-p", "require('node:path').join(require('node:path').dirname(process.execPath), '..', 'include', 'node')" }, &code, .ignore, ) catch std.process.fatal("Node.js is required to locate node_api.h; pass -Dnode-include-dir=<path>", .{}); if (code != 0) std.process.fatal("could not locate node_api.h; pass -Dnode-include-dir=<path>", .{}); const trimmed = std.mem.trim(u8, out, " \t\r\n"); return b.allocator.dupe(u8, trimmed) catch std.process.fatal("could not allocate Node include path", .{});} fn readGitCommit(b: *std.Build) []const u8 { var code: u8 = 0; const out = b.runAllowFail( &.{ "git", "rev-parse", "--short=12", "HEAD" }, &code, .ignore, ) catch return "unknown"; if (code != 0) return "unknown"; const trimmed = std.mem.trim(u8, out, " \t\r\n"); return b.allocator.dupe(u8, trimmed) catch "unknown";} fn readAppVersion(b: *std.Build) []const u8 { const bytes = std.Io.Dir.cwd().readFileAlloc(b.graph.io, "src/main.zig", b.allocator, .limited(1024 * 1024)) catch @panic("could not read src/main.zig to resolve app version"); defer b.allocator.free(bytes); const prefix = "pub const version = \""; const start = (std.mem.find(u8, bytes, prefix) orelse @panic("could not find pub const version in src/main.zig")) + prefix.len; const end_rel = std.mem.findScalar(u8, bytes[start..], '"') orelse @panic("could not parse pub const version in src/main.zig"); return b.allocator.dupe(u8, bytes[start .. start + end_rel]) catch @panic("could not allocate app version");}
const std = @import("std");
const UpdateChannel = enum { stable, dev };
const WasmSurface = enum {
none,
core,
term,
};
const PgsoArtifact = enum {
fx,
file_index,
ui_activity,
approval_review,
};
const NapiSurface = enum {
none,
core,
};
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const pgso_artifact = b.option(
PgsoArtifact,
"pgso-artifact",
"Emit ReleaseSafe LLVM bitcode for one PGO/PGSO artifact",
);
const wasm_surface = b.option(
WasmSurface,
"wasm-surface",
"Build a WASI WebAssembly surface for JavaScript hosts (core or term)",
) orelse .none;
const napi_surface = b.option(
NapiSurface,
"napi-surface",
"Build a Node-API addon surface (core)",
) orelse .none;
const git_commit = readGitCommit(b);
const app_version = readAppVersion(b);
const update_channel = b.option(UpdateChannel, "update-channel", "Build update channel (stable or dev)") orelse .stable;
const build_options = b.addOptions();
build_options.addOption([]const u8, "git_commit", git_commit);
build_options.addOption([]const u8, "app_version", app_version);
build_options.addOption([]const u8, "update_channel", @tagName(update_channel));
build_options.addOption(WasmSurface, "wasm_surface", .none);
const exe = b.addExecutable(.{
.name = "fx",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.stack_check = false,
.stack_protector = false,
.omit_frame_pointer = true,
.unwind_tables = .none,
.error_tracing = false,
.strip = optimize != .Debug,
}),
});
exe.root_module.addImport("build_options", build_options.createModule());
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run fx");
run_step.dependOn(&run_cmd.step);
const exe_tests = b.addTest(.{
.root_module = exe.root_module,
});
const run_exe_tests = b.addRunArtifact(exe_tests);
run_exe_tests.step.dependOn(b.getInstallStep());
run_exe_tests.setEnvironmentVariable(
"FX_TEST_PRODUCT_EXE",
b.getInstallPath(.bin, "fx"),
);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_exe_tests.step);
if (wasm_surface != .none) {
addWasmArtifact(b, wasm_surface, git_commit, app_version, update_channel);
}
if (napi_surface != .none) {
addNapiArtifact(b, napi_surface, target, git_commit, app_version, update_channel);
}
const mcp_test_exports = b.createModule(.{
.root_source_file = b.path("src/mcp_test_exports.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
mcp_test_exports.addImport("build_options", build_options.createModule());
const json_schema_corpus = b.addExecutable(.{
.name = "json-schema-corpus",
.root_module = b.createModule(.{
.root_source_file = b.path("tests/json-schema/corpus_runner.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
json_schema_corpus.root_module.addImport(
"mcp_test_exports",
mcp_test_exports,
);
const run_json_schema_corpus = b.addRunArtifact(json_schema_corpus);
if (b.args) |args| run_json_schema_corpus.addArgs(args);
const json_schema_corpus_step = b.step(
"run-json-schema-corpus",
"Run the pinned JSON Schema Test Suite corpus",
);
json_schema_corpus_step.dependOn(&run_json_schema_corpus.step);
const mcp_dispatcher_e2e = b.addExecutable(.{
.name = "mcp-stdio-dispatcher-driver",
.root_module = b.createModule(.{
.root_source_file = b.path(
"tests/e2e/fixtures/mcp-stdio-dispatcher-driver.zig",
),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
mcp_dispatcher_e2e.root_module.addImport(
"mcp_test_exports",
mcp_test_exports,
);
const run_mcp_dispatcher_e2e = b.addRunArtifact(mcp_dispatcher_e2e);
if (b.args) |args| run_mcp_dispatcher_e2e.addArgs(args);
const mcp_dispatcher_e2e_step = b.step(
"run-mcp-stdio-dispatcher-e2e",
"Run the MCP stdio dispatcher E2E driver",
);
mcp_dispatcher_e2e_step.dependOn(&run_mcp_dispatcher_e2e.step);
// --- file_index search benchmark ---
const benchmark_exports_mod = b.createModule(.{
.root_source_file = b.path("src/benchmark_exports.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
const file_index_bench = b.addExecutable(.{
.name = "file-index-bench",
.root_module = b.createModule(.{
.root_source_file = b.path("benchmarks/file_index_bench.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
file_index_bench.root_module.addImport("file_index", benchmark_exports_mod);
const install_bench = b.addInstallArtifact(file_index_bench, .{});
const bench_step = b.step("bench-file-index", "Build file_index search benchmark");
bench_step.dependOn(&install_bench.step);
const run_bench = b.addRunArtifact(file_index_bench);
run_bench.step.dependOn(&install_bench.step);
if (b.args) |args| run_bench.addArgs(args);
const run_bench_step = b.step("run-bench-file-index", "Build and run file_index search benchmark");
run_bench_step.dependOn(&run_bench.step);
// --- UI activity progress benchmark ---
const ui_activity_bench = b.addExecutable(.{
.name = "ui-activity-progress-bench",
.root_module = b.createModule(.{
.root_source_file = b.path("benchmarks/activity_progress.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
ui_activity_bench.root_module.addImport(
"benchmark_exports",
benchmark_exports_mod,
);
const install_ui_activity_bench = b.addInstallArtifact(ui_activity_bench, .{});
const ui_activity_bench_step = b.step(
"bench-ui-activity",
"Build the UI activity progress benchmark",
);
ui_activity_bench_step.dependOn(&install_ui_activity_bench.step);
const run_ui_activity_bench = b.addRunArtifact(ui_activity_bench);
run_ui_activity_bench.step.dependOn(&install_ui_activity_bench.step);
const run_ui_activity_bench_step = b.step(
"run-bench-ui-activity",
"Build and run the UI activity progress benchmark",
);
run_ui_activity_bench_step.dependOn(&run_ui_activity_bench.step);
const ui_activity_bench_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("benchmarks/activity_progress.zig"),
.target = target,
.optimize = optimize,
}),
});
ui_activity_bench_tests.root_module.addImport(
"benchmark_exports",
benchmark_exports_mod,
);
const run_ui_activity_bench_tests = b.addRunArtifact(ui_activity_bench_tests);
test_step.dependOn(&run_ui_activity_bench_tests.step);
const test_ui_activity_bench_step = b.step(
"test-ui-activity-benchmark",
"Run UI activity benchmark policy tests",
);
test_ui_activity_bench_step.dependOn(&run_ui_activity_bench_tests.step);
// --- file-diff approval review benchmark ---
const approval_review_bench = b.addExecutable(.{
.name = "approval-review-bench",
.root_module = b.createModule(.{
.root_source_file = b.path("benchmarks/approval_review.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
approval_review_bench.root_module.addImport(
"benchmark_exports",
benchmark_exports_mod,
);
const install_approval_review_bench = b.addInstallArtifact(
approval_review_bench,
.{},
);
const approval_review_bench_step = b.step(
"bench-approval-review",
"Build the file-diff approval review benchmark",
);
approval_review_bench_step.dependOn(&install_approval_review_bench.step);
const run_approval_review_bench = b.addRunArtifact(approval_review_bench);
run_approval_review_bench.step.dependOn(&install_approval_review_bench.step);
if (b.args) |args| run_approval_review_bench.addArgs(args);
const run_approval_review_bench_step = b.step(
"run-bench-approval-review",
"Build and run the file-diff approval review benchmark",
);
run_approval_review_bench_step.dependOn(&run_approval_review_bench.step);
const approval_review_bench_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("benchmarks/approval_review.zig"),
.target = target,
.optimize = optimize,
}),
});
approval_review_bench_tests.root_module.addImport(
"benchmark_exports",
benchmark_exports_mod,
);
const run_approval_review_bench_tests = b.addRunArtifact(
approval_review_bench_tests,
);
const test_approval_review_bench_step = b.step(
"test-approval-review-benchmark",
"Run file-diff approval review benchmark qualification tests",
);
test_approval_review_bench_step.dependOn(
&run_approval_review_bench_tests.step,
);
const pgso_ir_step = b.step(
"pgso-ir",
"Emit selected ReleaseSafe LLVM bitcode for PGO/PGSO qualification",
);
if (pgso_artifact) |artifact| {
const selected: *std.Build.Step.Compile = switch (artifact) {
.fx => exe,
.file_index => file_index_bench,
.ui_activity => ui_activity_bench,
.approval_review => approval_review_bench,
};
const output_name = switch (artifact) {
.fx => "pgso/fx.bc",
.file_index => "pgso/file-index.bc",
.ui_activity => "pgso/ui-activity.bc",
.approval_review => "pgso/approval-review.bc",
};
const install_ir = b.addInstallFile(
selected.getEmittedLlvmBc(),
output_name,
);
pgso_ir_step.dependOn(&install_ir.step);
} else {
const missing_artifact = b.addFail(
"pgso-ir requires -Dpgso-artifact",
);
pgso_ir_step.dependOn(&missing_artifact.step);
}
}
fn addWasmArtifact(
b: *std.Build,
surface: WasmSurface,
git_commit: []const u8,
app_version: []const u8,
update_channel: UpdateChannel,
) void {
const wasm_target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.os_tag = .wasi,
});
const name = switch (surface) {
.core => "fx-core",
.term => "fx-term",
.none => unreachable,
};
const description = switch (surface) {
.core => "Build the headless fx WebAssembly artifact",
.term => "Build the terminal fx WebAssembly artifact",
.none => unreachable,
};
const wasm_options = b.addOptions();
wasm_options.addOption([]const u8, "git_commit", git_commit);
wasm_options.addOption([]const u8, "app_version", app_version);
wasm_options.addOption([]const u8, "update_channel", @tagName(update_channel));
wasm_options.addOption(WasmSurface, "wasm_surface", surface);
const wasm_root = switch (surface) {
.core => "src/wasm_core_main.zig",
.term => "src/wasm_term_main.zig",
.none => unreachable,
};
const wasm_exe = b.addExecutable(.{
.name = name,
.root_module = b.createModule(.{
.root_source_file = b.path(wasm_root),
.target = wasm_target,
.optimize = .ReleaseSmall,
.single_threaded = true,
.link_libc = true,
.stack_check = false,
.stack_protector = false,
.omit_frame_pointer = true,
.unwind_tables = .none,
.error_tracing = false,
.strip = true,
}),
});
wasm_exe.root_module.addImport("build_options", wasm_options.createModule());
const install_wasm = b.addInstallArtifact(wasm_exe, .{});
const wasm_step = b.step(name ++ "-wasm", description);
wasm_step.dependOn(&install_wasm.step);
b.getInstallStep().dependOn(&install_wasm.step);
}
fn addNapiArtifact(
b: *std.Build,
surface: NapiSurface,
target: std.Build.ResolvedTarget,
git_commit: []const u8,
app_version: []const u8,
update_channel: UpdateChannel,
) void {
const napi_options = b.addOptions();
napi_options.addOption([]const u8, "git_commit", git_commit);
napi_options.addOption([]const u8, "app_version", app_version);
napi_options.addOption([]const u8, "update_channel", @tagName(update_channel));
napi_options.addOption(NapiSurface, "napi_surface", surface);
const root = switch (surface) {
.core => "src/napi_core_main.zig",
.none => unreachable,
};
const lib = b.addLibrary(.{
.name = "libfx",
.linkage = .dynamic,
.root_module = b.createModule(.{
.root_source_file = b.path(root),
.target = target,
.optimize = .ReleaseSafe,
.link_libc = true,
.strip = true,
}),
});
lib.root_module.addImport("build_options", napi_options.createModule());
const node_include = b.option(
[]const u8,
"node-include-dir",
"Directory containing node_api.h for the N-API addon",
) orelse discoverNodeIncludeDir(b);
lib.root_module.addSystemIncludePath(.{ .cwd_relative = node_include });
lib.linker_allow_shlib_undefined = true;
const install = b.addInstallArtifact(lib, .{ .dest_sub_path = "libfx.node" });
const step = b.step("libfx-napi", "Build the libfx Node-API core addon");
step.dependOn(&install.step);
b.getInstallStep().dependOn(&install.step);
}
fn discoverNodeIncludeDir(b: *std.Build) []const u8 {
var code: u8 = 0;
const out = b.runAllowFail(
&.{ "node", "-p", "require('node:path').join(require('node:path').dirname(process.execPath), '..', 'include', 'node')" },
&code,
.ignore,
) catch std.process.fatal("Node.js is required to locate node_api.h; pass -Dnode-include-dir=<path>", .{});
if (code != 0) std.process.fatal("could not locate node_api.h; pass -Dnode-include-dir=<path>", .{});
const trimmed = std.mem.trim(u8, out, " \t\r\n");
return b.allocator.dupe(u8, trimmed) catch std.process.fatal("could not allocate Node include path", .{});
}
fn readGitCommit(b: *std.Build) []const u8 {
var code: u8 = 0;
const out = b.runAllowFail(
&.{ "git", "rev-parse", "--short=12", "HEAD" },
&code,
.ignore,
) catch return "unknown";
if (code != 0) return "unknown";
const trimmed = std.mem.trim(u8, out, " \t\r\n");
return b.allocator.dupe(u8, trimmed) catch "unknown";
}
fn readAppVersion(b: *std.Build) []const u8 {
const bytes = std.Io.Dir.cwd().readFileAlloc(b.graph.io, "src/main.zig", b.allocator, .limited(1024 * 1024)) catch
@panic("could not read src/main.zig to resolve app version");
defer b.allocator.free(bytes);
const prefix = "pub const version = \"";
const start = (std.mem.find(u8, bytes, prefix) orelse
@panic("could not find pub const version in src/main.zig")) + prefix.len;
const end_rel = std.mem.findScalar(u8, bytes[start..], '"') orelse
@panic("could not parse pub const version in src/main.zig");
return b.allocator.dupe(u8, bytes[start .. start + end_rel]) catch
@panic("could not allocate app version");
}