const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const module = b.addModule("cloud9", .{ .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, }); const tests = b.addTest(.{ .root_module = module }); b.step("test", "Run protocol and session tests").dependOn(&b.addRunArtifact(tests).step); const wasm_target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }); const wasm_core = b.createModule(.{ .root_source_file = b.path("src/root.zig"), .target = wasm_target, .optimize = optimize, }); const wasm = b.addExecutable(.{ .name = "cloud9", .root_module = b.createModule(.{ .root_source_file = b.path("web/client.zig"), .target = wasm_target, .optimize = optimize, .imports = &.{.{ .name = "cloud9", .module = wasm_core }}, }) }); wasm.entry = .disabled; wasm.rdynamic = true; wasm.export_memory = true; wasm.stack_size = 256 * 1024; wasm.initial_memory = 1024 * 1024; wasm.max_memory = 1024 * 1024; const web = b.step("web", "Install the standalone browser client into zig-out/web"); web.dependOn(&b.addInstallFileWithDir(wasm.getEmittedBin(), .{ .custom = "web/_cloud9" }, "cloud9.wasm").step); for ([_][]const u8{ "index.html", "app.mjs", "client.mjs", "style.css" }) |file| { web.dependOn(&b.addInstallFileWithDir(b.path(b.fmt("web/static/{s}", .{file})), .{ .custom = if (std.mem.eql(u8, file, "index.html")) "web" else "web/_cloud9" }, file).step); } const bridge = b.addExecutable(.{ .name = "9web", .root_module = b.createModule(.{ .root_source_file = b.path("web/main.zig"), .target = target, .optimize = optimize, .imports = &.{.{ .name = "cloud9", .module = module }}, }) }); bridge.root_module.addAnonymousImport("client.wasm", .{ .root_source_file = wasm.getEmittedBin() }); b.installArtifact(bridge); const run_bridge = b.addRunArtifact(bridge); if (b.args) |args| run_bridge.addArgs(args); b.step("serve", "Run the HTTP/WebSocket bridge (--upstream tcp:127.0.0.1:564)").dependOn(&run_bridge.step); const native_http = b.addExecutable(.{ .name = "9web-test", .root_module = b.createModule(.{ .root_source_file = b.path("test/web/native.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "cloud9", .module = module }}, }) }); const http_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("test/http.zig"), .target = target, .optimize = optimize, .imports = &.{.{ .name = "cloud9", .module = module }}, }) }); b.step("http-test", "Test HTTP WebSocket transport framing").dependOn(&b.addRunArtifact(http_tests).step); const mux_mod = b.createModule(.{ .root_source_file = b.path("web/mux.zig"), .target = target, .optimize = optimize, .imports = &.{.{ .name = "cloud9", .module = module }}, }); const mux_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("test/web/mux.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "cloud9", .module = module }, .{ .name = "mux", .module = mux_mod } }, }) }); b.step("mux-test", "Test the 9web multiplexer (shared upstream, fid isolation, reconnect)").dependOn(&b.addRunArtifact(mux_tests).step); const e2e = b.addSystemCommand(&.{ "node", "test/web/e2e.mjs" }); e2e.addArtifactArg(bridge); e2e.addArtifactArg(native_http); b.step("e2e", "Run Chromium through the bridge against a local go9p server").dependOn(&e2e.step); const probe = b.addExecutable(.{ .name = "cloud9-probe", .root_module = b.createModule(.{ .root_source_file = b.path("test/differential/probe.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "cloud9", .module = module }}, }), }); b.installArtifact(probe); const session_probe = b.addExecutable(.{ .name = "cloud9-session-probe", .root_module = b.createModule(.{ .root_source_file = b.path("test/differential/session.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "cloud9", .module = module }}, }), }); const diff = b.addSystemCommand(&.{ "go", "run", "." }); diff.setCwd(b.path("test/differential")); diff.addArg("--probe"); diff.addArtifactArg(probe); diff.addArg("--session-probe"); diff.addArtifactArg(session_probe); if (b.args) |args| diff.addArgs(args); b.step("differential", "Compare valid traffic with pinned 9fans and go9p").dependOn(&diff.step); const transport_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("test/transport.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "cloud9", .module = module }}, }) }); b.step("transport-test", "Test TCP, Unix and std.Io adapters").dependOn(&b.addRunArtifact(transport_tests).step); if (b.option(bool, "quic", "Test QUIC with system OpenSSL 3.6+") orelse false) { const header = b.addWriteFiles().add("openssl.h", "#include \n#include \n#include \n"); const translated = b.addTranslateC(.{ .root_source_file = header, .target = target, .optimize = optimize, .link_libc = true }); const ssl = translated.createModule(); const tests_quic = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("test/quic.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{ .{ .name = "cloud9", .module = module }, .{ .name = "openssl", .module = ssl } }, }) }); tests_quic.root_module.linkSystemLibrary("ssl", .{}); tests_quic.root_module.linkSystemLibrary("crypto", .{}); b.step("quic-test", "Test optional QUIC transport").dependOn(&b.addRunArtifact(tests_quic).step); } const fuzz = b.addExecutable(.{ .name = "cloud9-fuzz", .root_module = b.createModule(.{ .root_source_file = b.path("test/fuzz.zig"), .target = target, .optimize = optimize, .imports = &.{.{ .name = "cloud9", .module = module }}, }) }); const run_fuzz = b.addRunArtifact(fuzz); if (b.args) |args| run_fuzz.addArgs(args); b.step("fuzz", "Run deterministic local decoder and server probes (seed, iterations)").dependOn(&run_fuzz.step); // ------------------------------------------------------------------ // Programs related to cloud9. Each lives in its own directory beside // src/ (9proc/, 9ns/) with a build.zig *fragment* exposing // `add(b, ctx)`; it is imported here and runs with this builder, so its // b.path() calls are relative to this root and its steps are namespaced // by the program's name. New related programs follow the same pattern: // add a directory, a fragment, a toggle here, and the path to // build.zig.zon. // // -D9proc=[bool] library module (any target) + freestanding check; // demo server and Linux suites when the target is Linux // -D9ns=[bool] the FUSE mount CLI (Linux only) // // Both default to true on a Linux target and false elsewhere. Enabled // programs are installed by the plain `zig build` next to 9web // and cloud9-probe. const is_linux = target.result.os.tag == .linux; const want_9proc = b.option(bool, "9proc", "Build the 9proc library module and demo (default: target is Linux)") orelse is_linux; const want_9ns = b.option(bool, "9ns", "Build the 9ns FUSE mount CLI (default: target is Linux; Linux only)") orelse is_linux; const want_9agents = b.option(bool, "9agents", "Build the coding-agents fs daemon (default: target is Linux; Linux only)") orelse is_linux; if (want_9ns and !is_linux) { std.debug.print("error: -D9ns=true needs a Linux target (got {s})\n", .{@tagName(target.result.os.tag)}); std.process.exit(1); } const programs_test = b.step("programs-test", "Run every enabled program's unit tests and checks"); const programs_itest = b.step("programs-itest", "Run every enabled program's integration suites"); const proc_build = @import("9proc/build.zig"); const ns_build = @import("9ns/build.zig"); const proc: ?proc_build.Artifacts = if (want_9proc) proc_build.add(b, .{ .target = target, .optimize = optimize, .cloud9 = module, }) else null; if (proc) |i| { programs_test.dependOn(i.test_step); programs_test.dependOn(i.check_step); if (i.debug_test_step) |s| programs_test.dependOn(s); } // 9web can embed 9proc for self-introspection (--probe). The module is // linked into 9web only when 9proc is enabled (Linux); otherwise the probe // code compiles to a stub selected by the have_probe build option. { const web_opts = b.addOptions(); web_opts.addOption(bool, "have_probe", proc != null); bridge.root_module.addImport("build_options", web_opts.createModule()); if (proc) |i| bridge.root_module.addImport("9proc", i.module); } // The HTTP /fs mapping suite drives 9web against a 9proc-demo upstream. if (proc) |i| if (i.demo) |demo| { const hfs = b.addSystemCommand(&.{ "node", "test/web/http_fs.mjs" }); hfs.addArtifactArg(demo); hfs.addArtifactArg(bridge); b.step("http-fs-test", "Test the HTTP /fs view against a 9proc-demo upstream").dependOn(&hfs.step); }; const ns: ?ns_build.Artifacts = if (want_9ns) ns_build.add(b, .{ .target = target, .optimize = optimize, .cloud9 = module, .proc_demo = if (proc) |i| i.demo else null, }) else null; if (ns) |p| { programs_test.dependOn(p.test_step); programs_itest.dependOn(p.itest_step); } // The coding-agents fs daemon: a read-only 9P view of every harness's // state, posted by default as `agents` (Linux only: it posts into // $XDG_RUNTIME_DIR/9p through cloud9.post). if (want_9agents and !is_linux) { std.debug.print("error: -D9agents=true needs a Linux target (got {s})\n", .{@tagName(target.result.os.tag)}); std.process.exit(1); } const agents_build = @import("9agents/build.zig"); const agents: ?agents_build.Artifacts = if (want_9agents) agents_build.add(b, .{ .target = target, .optimize = optimize, .cloud9 = module, .ns = if (ns) |p| p.exe else null, }) else null; if (agents) |p| { programs_test.dependOn(p.test_step); programs_itest.dependOn(p.itest_step); } // 9proc's end-to-end suites drive the demo through a 9ns mount, // so they are wired once both fragments have run. if (proc) |i| { if (proc_build.addNsTests(b, i, if (ns) |p| p.exe else null)) |s| programs_itest.dependOn(s); } }