1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
|
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 <openssl/ssl.h>\n#include <openssl/quic.h>\n#include <openssl/err.h>\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);
}
}
|