const std = @import("std"); const version = @import("build.zig.zon").version; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const build_options = b.addOptions(); build_options.addOption([]const u8, "version", version); const websocket = b.dependency("websocket", .{ .target = target, .optimize = optimize, }); const zttp = b.dependency("zttp", .{ .target = target, .optimize = optimize, }); const mod = b.addModule("zat", .{ .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{ .{ .name = "websocket", .module = websocket.module("websocket") }, .{ .name = "zttp", .module = zttp.module("zttp") }, .{ .name = "build_options", .module = build_options.createModule() }, }, }); const tests = b.addTest(.{ .root_module = mod }); // add interop test fixtures (lazy — only fetched when running tests) if (b.lazyDependency("atproto-interop-tests", .{})) |interop| { const interop_files = .{ // syntax fixtures .{ "tid_syntax_valid", "syntax/tid_syntax_valid.txt" }, .{ "tid_syntax_invalid", "syntax/tid_syntax_invalid.txt" }, .{ "did_syntax_valid", "syntax/did_syntax_valid.txt" }, .{ "did_syntax_invalid", "syntax/did_syntax_invalid.txt" }, .{ "handle_syntax_valid", "syntax/handle_syntax_valid.txt" }, .{ "handle_syntax_invalid", "syntax/handle_syntax_invalid.txt" }, .{ "nsid_syntax_valid", "syntax/nsid_syntax_valid.txt" }, .{ "nsid_syntax_invalid", "syntax/nsid_syntax_invalid.txt" }, .{ "recordkey_syntax_valid", "syntax/recordkey_syntax_valid.txt" }, .{ "recordkey_syntax_invalid", "syntax/recordkey_syntax_invalid.txt" }, .{ "aturi_syntax_valid", "syntax/aturi_syntax_valid.txt" }, .{ "aturi_syntax_invalid", "syntax/aturi_syntax_invalid.txt" }, .{ "atidentifier_syntax_valid", "syntax/atidentifier_syntax_valid.txt" }, .{ "atidentifier_syntax_invalid", "syntax/atidentifier_syntax_invalid.txt" }, .{ "cid_syntax_valid", "syntax/cid_syntax_valid.txt" }, .{ "cid_syntax_invalid", "syntax/cid_syntax_invalid.txt" }, .{ "uri_syntax_valid", "syntax/uri_syntax_valid.txt" }, .{ "uri_syntax_invalid", "syntax/uri_syntax_invalid.txt" }, .{ "language_syntax_valid", "syntax/language_syntax_valid.txt" }, .{ "language_syntax_invalid", "syntax/language_syntax_invalid.txt" }, .{ "datetime_syntax_valid", "syntax/datetime_syntax_valid.txt" }, .{ "datetime_syntax_invalid", "syntax/datetime_syntax_invalid.txt" }, .{ "datetime_parse_invalid", "syntax/datetime_parse_invalid.txt" }, // crypto fixtures .{ "signature_fixtures", "crypto/signature-fixtures.json" }, .{ "w3c_didkey_K256", "crypto/w3c_didkey_K256.json" }, .{ "w3c_didkey_P256", "crypto/w3c_didkey_P256.json" }, // data model fixtures .{ "data_model_fixtures", "data-model/data-model-fixtures.json" }, // mst fixtures .{ "mst_key_heights", "mst/key_heights.json" }, .{ "common_prefix", "mst/common_prefix.json" }, .{ "commit_proofs", "firehose/commit-proof-fixtures.json" }, }; inline for (interop_files) |entry| { tests.root_module.addAnonymousImport(entry[0], .{ .root_source_file = interop.path(entry[1]), }); } } const run_tests = b.addRunArtifact(tests); run_tests.skip_foreign_checks = true; const test_step = b.step("test", "run unit tests"); test_step.dependOn(&run_tests.step); if (unreachedTestFiles(b)) |missing| { test_step.dependOn(&b.addFail(missing).step); } // jetstream smoke test const jetstream_smoke = b.addExecutable(.{ .name = "jetstream-smoke", .root_module = b.createModule(.{ .root_source_file = b.path("scripts/jetstream_smoke.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(jetstream_smoke); const run_smoke = b.addRunArtifact(jetstream_smoke); const smoke_step = b.step("smoke", "run jetstream smoke test"); smoke_step.dependOn(&run_smoke.step); // firehose smoke test (CBOR + CAR + CID on live data) const firehose_smoke = b.addExecutable(.{ .name = "firehose-smoke", .root_module = b.createModule(.{ .root_source_file = b.path("scripts/firehose_smoke.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(firehose_smoke); const run_firehose_smoke = b.addRunArtifact(firehose_smoke); const firehose_smoke_step = b.step("firehose-smoke", "run firehose smoke test (CBOR/CAR/CID on live data)"); firehose_smoke_step.dependOn(&run_firehose_smoke.step); // runnable examples (compile-checked usage recipes) const example_parse_identifiers = b.addExecutable(.{ .name = "example-parse-identifiers", .root_module = b.createModule(.{ .root_source_file = b.path("examples/01_parse_identifiers.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(example_parse_identifiers); const run_example_parse_identifiers = b.addRunArtifact(example_parse_identifiers); const example_parse_identifiers_step = b.step("example-parse-identifiers", "parse and validate atproto identifiers"); example_parse_identifiers_step.dependOn(&run_example_parse_identifiers.step); const example_read_a_record = b.addExecutable(.{ .name = "example-read-a-record", .root_module = b.createModule(.{ .root_source_file = b.path("examples/03_read_a_record.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(example_read_a_record); const run_example_read_a_record = b.addRunArtifact(example_read_a_record); const example_read_a_record_step = b.step("example-read-a-record", "read one record from a PDS via getRecord"); example_read_a_record_step.dependOn(&run_example_read_a_record.step); const example_follow_the_firehose = b.addExecutable(.{ .name = "example-follow-the-firehose", .root_module = b.createModule(.{ .root_source_file = b.path("examples/04_follow_the_firehose.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(example_follow_the_firehose); const run_example_follow_the_firehose = b.addRunArtifact(example_follow_the_firehose); const example_follow_the_firehose_step = b.step("example-follow-the-firehose", "consume the firehose into a live language tally"); example_follow_the_firehose_step.dependOn(&run_example_follow_the_firehose.step); const example_walk_a_repo = b.addExecutable(.{ .name = "example-walk-a-repo", .root_module = b.createModule(.{ .root_source_file = b.path("examples/05_walk_a_repo.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(example_walk_a_repo); const run_example_walk_a_repo = b.addRunArtifact(example_walk_a_repo); const example_walk_a_repo_step = b.step("example-walk-a-repo", "walk a repo CAR and tally records per collection"); example_walk_a_repo_step.dependOn(&run_example_walk_a_repo.step); const example_search_your_follows = b.addExecutable(.{ .name = "example-search-your-follows", .root_module = b.createModule(.{ .root_source_file = b.path("examples/06_search_your_follows.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(example_search_your_follows); const run_example_search_your_follows = b.addRunArtifact(example_search_your_follows); const example_search_your_follows_step = b.step("example-search-your-follows", "search posts from the accounts you follow, grouped by author"); example_search_your_follows_step.dependOn(&run_example_search_your_follows.step); const example_resolve_identity = b.addExecutable(.{ .name = "example-resolve-identity", .root_module = b.createModule(.{ .root_source_file = b.path("examples/02_resolve_identity.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(example_resolve_identity); const run_example_resolve_identity = b.addRunArtifact(example_resolve_identity); const example_resolve_identity_step = b.step("example-resolve-identity", "resolve a handle/DID → PDS endpoint + signing key"); example_resolve_identity_step.dependOn(&run_example_resolve_identity.step); const example_star = b.addExecutable(.{ .name = "example-verify-star", .root_module = b.createModule(.{ .root_source_file = b.path("examples/06_verify_star.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(example_star); const run_star = b.addRunArtifact(example_star); if (b.args) |args| run_star.addArgs(args); b.step("example-verify-star", "verify a STAR-lite archive with bounded working memory").dependOn(&run_star.step); // firehose decodeFrame benchmark over atproto-bench fixtures const firehose_decode_bench = b.addExecutable(.{ .name = "firehose-decode-bench", .root_module = b.createModule(.{ .root_source_file = b.path("scripts/firehose_decode_bench.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(firehose_decode_bench); const run_firehose_decode_bench = b.addRunArtifact(firehose_decode_bench); if (b.args) |args| run_firehose_decode_bench.addArgs(args); const firehose_decode_bench_step = b.step("firehose-decode-bench", "benchmark FirehoseClient.decodeFrame over atproto-bench fixtures"); firehose_decode_bench_step.dependOn(&run_firehose_decode_bench.step); // CBOR codec benchmarks const cbor_bench = b.addExecutable(.{ .name = "cbor-bench", .root_module = b.createModule(.{ .root_source_file = b.path("src/internal/repo/cbor_bench.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(cbor_bench); const run_bench = b.addRunArtifact(cbor_bench); const bench_step = b.step("bench", "run CBOR codec benchmarks"); bench_step.dependOn(&run_bench.step); // commit build + sign benchmark (PDS write hot path) const commit_sign_bench = b.addExecutable(.{ .name = "commit-sign-bench", .root_module = b.createModule(.{ .root_source_file = b.path("scripts/commit_sign_bench.zig"), .target = target, .optimize = optimize, .link_libc = true, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(commit_sign_bench); const run_commit_sign_bench = b.addRunArtifact(commit_sign_bench); const commit_sign_bench_step = b.step("commit-sign-bench", "benchmark zat.signCommit across key types"); commit_sign_bench_step.dependOn(&run_commit_sign_bench.step); // publish-docs script (uses zat to publish docs to ATProto) const publish_docs = b.addExecutable(.{ .name = "publish-docs", .root_module = b.createModule(.{ .root_source_file = b.path("scripts/publish-docs.zig"), .target = target, .optimize = optimize, .imports = &.{.{ .name = "zat", .module = mod }}, }), }); b.installArtifact(publish_docs); } /// Zig only runs tests in files the test root actually resolves, so every file /// carrying a `test` block has to be listed in root.zig's `is_test` import /// block. That list is hand-written and silently rotted once already: five /// subsystems drifted out of it, taking 66 tests and two compile errors with /// them. Returns an error message naming any file the list has missed. fn unreachedTestFiles(b: *std.Build) ?[]const u8 { const io = b.graph.io; const gpa = b.allocator; const limit: std.Io.Limit = .limited(4 << 20); const root_dir = b.build_root.handle; const root = root_dir.readFileAlloc(io, "src/root.zig", gpa, limit) catch return null; var src = root_dir.openDir(io, "src", .{ .iterate = true }) catch return null; defer src.close(io); var walker = src.walk(gpa) catch return null; defer walker.deinit(); var missing: std.ArrayList(u8) = .empty; while (walker.next(io) catch return null) |entry| { if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".zig")) continue; if (std.mem.eql(u8, entry.path, "root.zig")) continue; const path = b.pathJoin(&.{ "src", entry.path }); const source = root_dir.readFileAlloc(io, path, gpa, limit) catch continue; if (!declaresTest(source)) continue; const posix = std.mem.replaceOwned(u8, gpa, entry.path, "\\", "/") catch return null; const needle = b.fmt("@import(\"{s}\")", .{posix}); if (std.mem.indexOf(u8, root, needle) == null) { missing.print(gpa, "\n _ = @import(\"{s}\");", .{posix}) catch return null; } } if (missing.items.len == 0) return null; return b.fmt( "these files declare tests that will never run -- add them to the `is_test` block in src/root.zig:{s}", .{missing.items}, ); } fn declaresTest(source: []const u8) bool { var lines = std.mem.splitScalar(u8, source, '\n'); while (lines.next()) |line| { if (std.mem.startsWith(u8, line, "test \"") or std.mem.startsWith(u8, line, "test {")) return true; } return false; }