-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathbuild.zig
More file actions
317 lines (276 loc) · 14.8 KB
/
build.zig
File metadata and controls
317 lines (276 loc) · 14.8 KB
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
const std = @import("std");
// Shared SQLite compile flags (DRY across per-arch and root builds)
const sqlite_flags = [_][]const u8{
"-DSQLITE_THREADSAFE=1",
"-DSQLITE_OMIT_LOAD_EXTENSION",
"-DSQLITE_DEFAULT_SYNCHRONOUS=1",
"-DSQLITE_ENABLE_FTS5",
"-DSQLITE_ENABLE_JSON1",
"-DSQLITE_DQS=0",
// Prevent unresolved UBSan symbols when linking with Apple clang/Xcode.
// Zig's toolchain may auto-link UBSan, but Xcode does not.
"-fno-sanitize=undefined",
"-fno-sanitize=integer",
};
fn addShellStep(
b: *std.Build,
name: []const u8,
description: []const u8,
script: []const u8,
) *std.Build.Step {
const step = b.step(name, description);
const cmd = b.addSystemCommand(&.{ "sh", "-c", script });
step.dependOn(&cmd.step);
return step;
}
// Build libsmithers (static) for a specific macOS CPU arch and return both the
// smithers library and the per-arch sqlite static library artifacts. Used by
// the xcframework packaging pipeline.
fn buildLibSmithersForArch(
b: *std.Build,
cpu_arch: std.Target.Cpu.Arch,
optimize: std.builtin.OptimizeMode,
) struct { lib: *std.Build.Step.Compile, sqlite: *std.Build.Step.Compile } {
const resolved = b.resolveTargetQuery(.{
.cpu_arch = cpu_arch,
.os_tag = .macos,
.os_version_min = .{ .semver = .{ .major = 14, .minor = 0, .patch = 0 } },
});
// Per-arch build options (match root module defaults)
const arch_build_opts = b.addOptions();
arch_build_opts.addOption(bool, "enable_http_server_tests", false);
arch_build_opts.addOption(bool, "enable_storage_module", false);
// smithers module (per-arch) — unique name per arch to avoid collisions
const arch_name = switch (cpu_arch) {
.aarch64 => "smithers-arch-aarch64",
.x86_64 => "smithers-arch-x86_64",
else => "smithers-arch",
};
const arch_mod = b.addModule(arch_name, .{
.root_source_file = b.path("src/lib.zig"),
.target = resolved,
.optimize = optimize,
});
arch_mod.addOptions("build_options", arch_build_opts);
arch_mod.addIncludePath(b.path("pkg/sqlite"));
// Optional zap dependency for http_server when enabled
const zap_dep = b.dependency("zap", .{ .target = resolved, .optimize = optimize });
arch_mod.addImport("zap", zap_dep.module("zap"));
// Per-arch sqlite static library
const sqlite_lib = b.addLibrary(.{
.name = "sqlite3",
.root_module = b.createModule(.{ .target = resolved, .optimize = optimize }),
.linkage = .static,
.use_llvm = true,
});
sqlite_lib.addIncludePath(b.path("pkg/sqlite"));
sqlite_lib.addCSourceFile(.{ .file = b.path("pkg/sqlite/sqlite3.c"), .flags = &sqlite_flags });
sqlite_lib.linkLibC();
// smithers static lib (per-arch). Root module = arch_mod (no redundant self-import).
const lib = b.addLibrary(.{ .name = "smithers", .root_module = arch_mod, .linkage = .static, .use_llvm = true });
lib.bundle_compiler_rt = true;
lib.bundle_ubsan_rt = true;
lib.addIncludePath(b.path("pkg/sqlite"));
lib.linkLibC();
lib.linkLibrary(sqlite_lib);
return .{ .lib = lib, .sqlite = sqlite_lib };
}
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Build options consumed by src/lib.zig
const build_opts = b.addOptions();
build_opts.addOption(bool, "enable_http_server_tests", false);
build_opts.addOption(bool, "enable_storage_module", false);
// Root smithers module
const mod = b.addModule("smithers", .{
.root_source_file = b.path("src/lib.zig"),
.target = target,
});
mod.addOptions("build_options", build_opts);
mod.addIncludePath(b.path("pkg/sqlite"));
// Wire zap dependency for modules that need it (http_server). Gated by
// build option in src/lib.zig, so simply exposing the import is safe.
const zap_dep_root = b.dependency("zap", .{ .target = target, .optimize = optimize });
mod.addImport("zap", zap_dep_root.module("zap"));
// CLI executable
const exe = b.addExecutable(.{
.name = "smithers-ctl",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{.{ .name = "smithers", .module = mod }},
}),
});
exe.root_module.addImport("zap", zap_dep_root.module("zap"));
// Vendored SQLite amalgamation (native) as static library
const sqlite_lib = b.addLibrary(.{
.name = "sqlite3",
.root_module = b.createModule(.{ .target = target, .optimize = optimize }),
.linkage = .static,
.use_llvm = true,
});
sqlite_lib.addIncludePath(b.path("pkg/sqlite"));
sqlite_lib.addCSourceFile(.{ .file = b.path("pkg/sqlite/sqlite3.c"), .flags = &sqlite_flags });
sqlite_lib.linkLibC();
exe.addIncludePath(b.path("pkg/sqlite"));
exe.linkLibrary(sqlite_lib);
b.installArtifact(exe);
// Run step
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
// Tests for root module and CLI module
const mod_tests = b.addTest(.{ .root_module = mod });
mod_tests.addIncludePath(b.path("pkg/sqlite"));
mod_tests.linkLibrary(sqlite_lib);
const run_mod_tests = b.addRunArtifact(mod_tests);
const exe_tests = b.addTest(.{ .root_module = exe.root_module });
exe_tests.addIncludePath(b.path("pkg/sqlite"));
exe_tests.linkLibrary(sqlite_lib);
const run_exe_tests = b.addRunArtifact(exe_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
test_step.dependOn(&run_exe_tests.step);
// Integration/build gates (non-skippable; fail with actionable errors)
const web_install_step = addShellStep(
b,
"web-install",
"Install web dependencies",
"if [ ! -d web ]; then echo 'ERROR: web/ not found. Expected web app at ./web.' >&2; exit 1; fi; if ! command -v pnpm >/dev/null 2>&1; then echo 'ERROR: pnpm not found. Install: npm install -g pnpm' >&2; exit 1; fi; cd web && pnpm install --frozen-lockfile",
);
const web_step = addShellStep(
b,
"web",
"Build web app",
"if [ ! -d web ]; then echo 'ERROR: web/ not found. Expected web app at ./web.' >&2; exit 1; fi; if ! command -v pnpm >/dev/null 2>&1; then echo 'ERROR: pnpm not found. Install: npm install -g pnpm' >&2; exit 1; fi; cd web && pnpm build",
);
web_step.dependOn(web_install_step);
const playwright_step = addShellStep(
b,
"playwright",
"Run Playwright e2e",
"if [ ! -d web ]; then echo 'ERROR: web/ not found. Expected web app at ./web.' >&2; exit 1; fi; if ! command -v pnpm >/dev/null 2>&1; then echo 'ERROR: pnpm not found. Install: npm install -g pnpm' >&2; exit 1; fi; cd web && pnpm exec playwright test",
);
playwright_step.dependOn(web_install_step);
const codex_step = addShellStep(
b,
"codex",
"Build codex submodule",
"if [ ! -d submodules/codex ]; then echo 'ERROR: submodules/codex not found. Run: git submodule update --init --recursive submodules/codex' >&2; exit 1; fi; cd submodules/codex && zig build",
);
const jj_step = addShellStep(
b,
"jj",
"Build jj submodule",
"if [ ! -d submodules/jj ]; then echo 'ERROR: submodules/jj not found. Run: git submodule update --init --recursive submodules/jj' >&2; exit 1; fi; cd submodules/jj && zig build",
);
const xcode_test_step = b.step("xcode-test", "Run Xcode tests");
const xcode_test_cmd = b.addSystemCommand(&.{
"sh",
"-c",
"if [ ! -d macos ]; then echo 'ERROR: macos/ not found. Xcode project is required for xcode-test.' >&2; exit 1; fi; rm -rf .build/xcode/tests.xcresult; xcodebuild test -project macos/Smithers.xcodeproj -scheme Smithers -destination 'platform=macOS' -parallel-testing-enabled NO -maximum-parallel-testing-workers 1 -derivedDataPath .build/xcode -resultBundlePath .build/xcode/tests.xcresult",
});
const ui_test_step = addShellStep(
b,
"ui-test",
"Run XCUITests",
"if [ ! -d macos ]; then echo 'ERROR: macos/ not found. Xcode project is required for ui-test.' >&2; exit 1; fi; xcodebuild test -project macos/Smithers.xcodeproj -scheme Smithers -only-testing:SmithersUITests -parallel-testing-enabled NO -maximum-parallel-testing-workers 1",
);
_ = ui_test_step;
// Top-level xcframework step (wired later to concrete commands)
const xc_step = b.step("xcframework", "Build SmithersKit.xcframework (macOS arm64 + x86_64)");
// Ensure xcodebuild commands run after xcframework is produced.
xcode_test_cmd.step.dependOn(xc_step);
xcode_test_step.dependOn(&xcode_test_cmd.step);
// Non-test Xcode build step (fallback when tests are flaky in CI)
const xcode_build_step = b.step("xcode-build", "Build Xcode app (no tests)");
const xcode_build_cmd = b.addSystemCommand(&.{
"sh",
"-c",
"if [ ! -d macos ]; then echo 'ERROR: macos/ not found. Xcode project is required for xcode-build.' >&2; exit 1; fi; xcodebuild -project macos/Smithers.xcodeproj -scheme Smithers -destination 'platform=macOS' -derivedDataPath .build/xcode build",
});
xcode_build_cmd.step.dependOn(xc_step);
xcode_build_step.dependOn(&xcode_build_cmd.step);
// Dev step: ensure xcframework exists before launching Xcode
const dev_step = b.step("dev", "Build everything + launch");
dev_step.dependOn(b.getInstallStep());
dev_step.dependOn(web_step);
dev_step.dependOn(codex_step);
dev_step.dependOn(jj_step);
dev_step.dependOn(xc_step);
const xcode_build = b.addSystemCommand(&.{
"sh",
"-c",
"if [ ! -d macos ]; then echo 'ERROR: macos/ not found. Xcode project is required for dev.' >&2; exit 1; fi; xcodebuild -project macos/Smithers.xcodeproj -scheme Smithers build && if [ -d .build/xcode/Build/Products/Debug/Smithers.app ]; then open .build/xcode/Build/Products/Debug/Smithers.app; else echo \"build succeeded; app not found at .build/xcode/Build/Products/Debug/Smithers.app\"; fi",
});
dev_step.dependOn(&xcode_build.step);
// Format & lint steps
const fmt_check = b.addFmt(.{ .paths = &.{"."}, .check = true });
const fmt_check_step = b.step("fmt-check", "Check Zig code formatting");
fmt_check_step.dependOn(&fmt_check.step);
const prettier_check_step = addShellStep(b, "prettier-check", "Check formatting with prettier (fails if missing)", "if ! command -v prettier >/dev/null 2>&1; then echo 'ERROR: prettier not found. Install: npm install -g prettier' >&2; exit 1; fi; prettier --check .");
const typos_check_step = addShellStep(b, "typos-check", "Run spell checker (fails if missing)", "if ! command -v typos >/dev/null 2>&1; then echo 'ERROR: typos not found. Install: brew install typos-cli' >&2; exit 1; fi; typos");
const shellcheck_step = addShellStep(b, "shellcheck", "Lint shell scripts (fails if missing)", "if ! command -v shellcheck >/dev/null 2>&1; then echo 'ERROR: shellcheck not found. Install: brew install shellcheck' >&2; exit 1; fi; find . -type d \\( -name .git -o -name .build -o -name .zig-cache -o -name .zig-cache-local -o -name node_modules -o -name dist -o -name zig-out -o -name submodules \\) -prune -o -type f \\( -name '*.sh' -o -name '*.bash' \\) -exec shellcheck --severity=warning {} +");
const gate_regression_step = addShellStep(b, "gate-regression", "Run gate regression tests", "if [ ! -x tests/lint_tool_gate_test.sh ] || [ ! -x tests/web_guard_test.sh ] || [ ! -x tests/failing_gate_steps_test.sh ]; then echo 'ERROR: gate regression scripts missing or not executable in tests/.' >&2; exit 1; fi; tests/lint_tool_gate_test.sh && tests/web_guard_test.sh && tests/failing_gate_steps_test.sh");
const all_step = b.step("all", "Run ALL checks (build + test + format + lint)");
all_step.dependOn(b.getInstallStep());
all_step.dependOn(test_step);
all_step.dependOn(fmt_check_step);
all_step.dependOn(prettier_check_step);
all_step.dependOn(typos_check_step);
all_step.dependOn(shellcheck_step);
all_step.dependOn(web_step);
all_step.dependOn(playwright_step);
all_step.dependOn(gate_regression_step);
// `xcode-test` consumes `xcframework`; keep a single producer path.
all_step.dependOn(xcode_test_step);
// --- xcframework pipeline ---
// Build per-arch static libraries, merge with libtool, create universal .a, then package.
const arm = buildLibSmithersForArch(b, .aarch64, optimize);
const x86 = buildLibSmithersForArch(b, .x86_64, optimize);
// Ensure required Apple toolchain commands exist
const xc_tools_check = b.addSystemCommand(&.{
"sh",
"-c",
"for t in libtool lipo xcodebuild; do if ! command -v $t >/dev/null 2>&1; then echo 'xcframework: missing' $t >&2; exit 1; fi; done",
});
const lt_arm = b.addSystemCommand(&.{ "libtool", "-static", "-o" });
const arm_merged = lt_arm.addOutputFileArg("libsmithers-merged-arm64.a");
lt_arm.addFileArg(arm.lib.getEmittedBin());
lt_arm.addFileArg(arm.sqlite.getEmittedBin());
lt_arm.step.dependOn(&xc_tools_check.step);
const lt_x86 = b.addSystemCommand(&.{ "libtool", "-static", "-o" });
const x86_merged = lt_x86.addOutputFileArg("libsmithers-merged-x86_64.a");
lt_x86.addFileArg(x86.lib.getEmittedBin());
lt_x86.addFileArg(x86.sqlite.getEmittedBin());
lt_x86.step.dependOn(&xc_tools_check.step);
const lipo_cmd = b.addSystemCommand(&.{ "lipo", "-create", "-output" });
const universal_out = lipo_cmd.addOutputFileArg("libsmithers.a");
lipo_cmd.addFileArg(arm_merged);
lipo_cmd.addFileArg(x86_merged);
lipo_cmd.step.dependOn(<_arm.step);
lipo_cmd.step.dependOn(<_x86.step);
lipo_cmd.step.dependOn(&xc_tools_check.step);
const xcfw_output_path = "dist/SmithersKit.xcframework";
const mkdist_cmd = b.addSystemCommand(&.{ "mkdir", "-p", "dist" });
mkdist_cmd.has_side_effects = true;
const rm_cmd = b.addSystemCommand(&.{ "rm", "-rf", xcfw_output_path });
rm_cmd.has_side_effects = true;
const xcf_cmd = b.addSystemCommand(&.{ "xcodebuild", "-create-xcframework", "-library" });
xcf_cmd.addFileArg(universal_out);
xcf_cmd.addArgs(&.{ "-headers", "include", "-output", xcfw_output_path });
xcf_cmd.has_side_effects = true;
xcf_cmd.expectExitCode(0);
_ = xcf_cmd.captureStdOut();
_ = xcf_cmd.captureStdErr();
xcf_cmd.step.dependOn(&lipo_cmd.step);
xcf_cmd.step.dependOn(&mkdist_cmd.step);
xcf_cmd.step.dependOn(&rm_cmd.step);
xcf_cmd.step.dependOn(&xc_tools_check.step);
// Wire pipeline to the top-level step
xc_step.dependOn(&xcf_cmd.step);
}