Artifact 4bc7076a0a8ae829e0860d78027c84f78a3cb40fb2733891a16ac1ebed18715d:
- File
build.zig
— part of check-in
[122998a428]
at
2026-07-18 20:12:07
on branch release-0.1
— Add BSD-2-Clause LICENSE, ship it in .paths, drop scratch file, ignore the pattern
A release without a license grants nobody any rights, so this is the legally gating step of the 0.1.0 sequence. zit is an independent reimplementation from the Fossil RFCs rather than a derivation of Fossil source, so the choice was open; BSD-2-Clause matches the Fossil heritage and the systems-Zig ecosystem.
What changed:
- LICENSE at the repo root, BSD 2-Clause, copyright Daniel Mestas 2026. - build.zig.zon .paths now names LICENSE, so the file actually ships inside a fetched package instead of only existing in the checkout. - The leftover commit-description-413190fb92be991f.txt scratch file is gone and .fossil-settings/ignore-glob carries commit-description-* so the class of file cannot drift back in. - src/packaging_test.zig pins the above as executable facts: LICENSE content, LICENSE named inside the manifest's brace-matched .paths block, a line-anchored ignore-glob pin, and a negative-space pin asserting every repo-root path build.zig embeds is also named in .paths. These acceptance criteria are file-structure facts, which rot silently because nothing executes them; the battery alone cannot catch a .paths regression. - build.zig grows build_unit_test_module: the pinned repository files attach as anonymous imports to a test-only compile of root.zig, never to the exported zit module. Attaching them to zit made every consumer's build depend on files outside .paths and a fetched package failed on a missing .fossil-settings/ignore-glob before compiling a line. The negative-space pin exists to catch exactly that recurrence. - docs/RELEASE-0.1.0-PLAN.md, the plan of record for the 0.1.0 release, is committed alongside the first step it describes.
Test evidence: zig build and zig build test green in the working checkout. This commit is additionally gated on a clean-checkout proof — fossil open into a fresh temp directory, then zig build and zig build test there — which is the check that catches a file referenced but never added.
No borrowed techniques in this change, so no attribution is owed. (user: dmestas, size: 12828) [annotate] [blame] [check-ins using] [more...]
const std = @import("std"); /// Config threaded into each per-artifact builder: target/optimize choice plus /// the single `zit` library module every root imports. storage, logstore, /// checkout, and update are folded into that one module (re-exported from /// root.zig), so there are no separate store peers to wire. const Env = struct { b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, mod: *std.Build.Module, }; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); // The zit library: a Zig implementation of the Fossil formats and protocol // as specified by the four RFC drafts in docs/rfcs/. root.zig re-exports the // store layers (storage/logstore/checkout/update) and the Repository facade; // one module is the whole embeddable surface. const mod = b.addModule("zit", .{ .root_source_file = b.path("src/root.zig"), .target = target, }); const env = Env{ .b = b, .target = target, .optimize = optimize, .mod = mod, }; // `zig build test` — runs library + CLI unit tests; each helper below // wires its own artifact's inline tests into this step as it goes. The // store layers' and their sibling tests aggregate into `mod_tests` via // root.zig's test block. const test_step = b.step("test", "Run unit tests"); const mod_tests = b.addTest(.{ .root_module = build_unit_test_module(b, target, optimize) }); test_step.dependOn(&b.addRunArtifact(mod_tests).step); const cli = build_cli(env, test_step); build_e2e(env, cli.exe, test_step); build_fuzz(env); const dst_tests = build_dst(env, test_step); build_bench(env, test_step); build_smoke(env, cli.exe, test_step); build_cov(env, &.{ mod_tests, cli.tests, dst_tests }); } /// The unit-test compile of root.zig. A *separate* module from the exported /// `zit` one, purely so the repository files that src/packaging_test.zig pins /// (LICENSE, the package manifest, the versioned ignore-glob) can be reached /// as anonymous imports without attaching them to the public module. /// /// Attaching them to `zit` itself made every consumer's build depend on files /// outside `build.zig.zon`'s `.paths` — a fetched package then failed with /// `failed to check cache: '.fossil-settings/ignore-glob' file_hash /// FileNotFound` before compiling a line. The need is test-only, so the scope /// is test-only. fn build_unit_test_module( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, ) *std.Build.Module { const test_mod = b.createModule(.{ .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, }); test_mod.addAnonymousImport("license_text", .{ .root_source_file = b.path("LICENSE") }); test_mod.addAnonymousImport("build_manifest", .{ .root_source_file = b.path("build.zig.zon") }); test_mod.addAnonymousImport("ignore_glob", .{ .root_source_file = b.path(".fossil-settings/ignore-glob") }); test_mod.addAnonymousImport("build_source", .{ .root_source_file = b.path("build.zig") }); return test_mod; } /// build_cli's two artifacts: the installed `zit` exe (the e2e harness spawns /// it) and its unit-test compile (the out-of-band cov step traces it). const Cli = struct { exe: *std.Build.Step.Compile, tests: *std.Build.Step.Compile, }; /// The `zit` command-line tool: `zig build` installs it, `zig build run -- /// <args>` runs it, and its own unit tests join `test_step`. fn build_cli(env: Env, test_step: *std.Build.Step) Cli { const exe = env.b.addExecutable(.{ .name = "zit", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/main.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, }, }), }); env.b.installArtifact(exe); // `zig build run -- <args>` const run_step = env.b.step("run", "Run the zit CLI"); const run_cmd = env.b.addRunArtifact(exe); run_step.dependOn(&run_cmd.step); run_cmd.step.dependOn(env.b.getInstallStep()); if (env.b.args) |args| run_cmd.addArgs(args); const exe_tests = env.b.addTest(.{ .root_module = exe.root_module }); test_step.dependOn(&env.b.addRunArtifact(exe_tests).step); return .{ .exe = exe, .tests = exe_tests }; } /// `zig build e2e` — end-to-end tests that spawn the REAL `zit` binary in an /// isolated, pinned environment and read the resulting repository back through /// the library. The exe's path is threaded in as a build option (a `LazyPath`, /// so the binary is always rebuilt before the tests run); the `zit` module is /// imported so the tests can decode `.zit`/`.zit-checkout` state. /// /// The subprocess-driven run stays out of `zig build test` (it spawns the real /// binary ~150 times), but the test binary is *compiled* by `test` so this /// 4.6k-line file cannot silently rot — a type error here fails `test`, at no /// execution cost. fn build_e2e(env: Env, exe: *std.Build.Step.Compile, test_step: *std.Build.Step) void { const opts = env.b.addOptions(); opts.addOptionPath("zit_exe", exe.getEmittedBin()); const e2e_mod = env.b.createModule(.{ .root_source_file = env.b.path("src/e2e.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, .{ .name = "build_options", .module = opts.createModule() }, }, }); const e2e_tests = env.b.addTest(.{ .root_module = e2e_mod }); // Compile-only gate: `test` builds the e2e binary but never runs it. test_step.dependOn(&e2e_tests.step); const e2e_run = env.b.addRunArtifact(e2e_tests); const e2e_step = env.b.step("e2e", "End-to-end tests: spawn the real zit binary"); e2e_step.dependOn(&e2e_run.step); } /// `zig build fuzz -- <target> [--seed N] [--rounds N]` — seeded deterministic /// fuzz targets, driven in a loop by an out-of-band runner; not part of `test`. fn build_fuzz(env: Env) void { const fuzz_exe = env.b.addExecutable(.{ .name = "fuzz", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/fuzz_tests.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, }, }), }); const fuzz_step = env.b.step("fuzz", "Run a seeded fuzz target (zig build fuzz -- <target> --seed N)"); const fuzz_run = env.b.addRunArtifact(fuzz_exe); if (env.b.args) |args| fuzz_run.addArgs(args); fuzz_step.dependOn(&fuzz_run.step); } /// `zig build dst [-- --seed N | --seeds K] [--steps M]` — deterministic /// simulation of the repostate derivation. No args runs the fixed smoke /// sweep (seeds 1..25 x 10_000 steps); deep sweeps pass `--seeds`. /// /// dst.zig's module reaches sim/*.zig (kept out of the public `zit` module); /// running it as a test binary is how those sibling harness files' inline unit /// tests join `zig build test`. fn build_dst(env: Env, test_step: *std.Build.Step) *std.Build.Step.Compile { const dst_exe = env.b.addExecutable(.{ .name = "dst", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/dst.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, }, }), }); const dst_step = env.b.step("dst", "Deterministic simulation (default smoke sweep; zig build dst -- --seed N)"); const dst_run = env.b.addRunArtifact(dst_exe); if (env.b.args) |args| dst_run.addArgs(args); dst_step.dependOn(&dst_run.step); const dst_tests = env.b.addTest(.{ .root_module = dst_exe.root_module }); test_step.dependOn(&env.b.addRunArtifact(dst_tests).step); return dst_tests; } /// `zig build bench -- <put|read|open> [flags]` — LogStore throughput and /// latency measurement. Always ReleaseFast (a benchmark measures optimized /// code, not the top-level optimize choice). The executable reaches the store /// layers through the `zit` module (`zit.logstore`/`zit.storage`); its one pure /// corpus test joins `zig build test` in Debug (no timing loop ever runs under /// `test`). fn build_bench(env: Env, test_step: *std.Build.Step) void { const bench_imports = [_]std.Build.Module.Import{ .{ .name = "zit", .module = env.mod }, }; const bench_exe = env.b.addExecutable(.{ .name = "bench", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/logstore_benchmark.zig"), .target = env.target, .optimize = .ReleaseFast, .imports = &bench_imports, }), }); const bench_step = env.b.step("bench", "LogStore + sync benchmarks (zig build bench -- <put|read|open|sync> [flags])"); const bench_run = env.b.addRunArtifact(bench_exe); if (env.b.args) |args| bench_run.addArgs(args); bench_step.dependOn(&bench_run.step); const bench_tests = env.b.addTest(.{ .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/logstore_benchmark.zig"), .target = env.target, .optimize = env.optimize, .imports = &bench_imports, }) }); test_step.dependOn(&env.b.addRunArtifact(bench_tests).step); } /// `zig build smoke` — the one sanctioned use of the real `fossil` binary: /// confirms zit-produced artifacts/deltas against fossil's own /// parser/hasher/store. Confined to this step; never part of `test` or /// pre-commit. `zig build test` still compiles smoke.zig (catching type /// errors) without running its fossil-driving scenarios. fn build_smoke(env: Env, exe: *std.Build.Step.Compile, test_step: *std.Build.Step) void { // Thread the real `zit` binary's path in as a build option (a `LazyPath`, so // it is always rebuilt first), same as e2e: S5 spawns `zit serve`/`pack` as // subprocesses and clones from them with the real `fossil` client. const opts = env.b.addOptions(); opts.addOptionPath("zit_exe", exe.getEmittedBin()); const smoke_exe = env.b.addExecutable(.{ .name = "smoke", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/smoke.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, .{ .name = "build_options", .module = opts.createModule() }, }, }), }); test_step.dependOn(&smoke_exe.step); const smoke_step = env.b.step("smoke", "Fossil interop smoke check (milestone-manual)"); const smoke_run = env.b.addRunArtifact(smoke_exe); if (env.b.args) |args| smoke_run.addArgs(args); smoke_step.dependOn(&smoke_run.step); } /// Reads kcov's merged `coverage.json` and prints a `src/`-only per-file line /// summary, ascending by coverage so the least-tested regions surface first. const cov_summary_py = \\import json,sys \\d=json.load(open(sys.argv[1])) \\for f in sorted(d["files"],key=lambda f:float(f["percent_covered"])): \\ print(f"{float(f['percent_covered']):6.2f}% {f['covered_lines']:>5}/{f['total_lines']:<5} {f['file'].split('/zit/')[-1]}") \\print(f"total {d['percent_covered']}% html: zig-out/cov/kcov-merged/index.html") ; /// `zig build cov` — merged line coverage over every unit-test binary via /// kcov (`brew install kcov`). Out-of-band: never part of `test`/pre-commit, /// so untested regions surface mechanically without gating a commit. fn build_cov(env: Env, test_bins: []const *std.Build.Step.Compile) void { const b = env.b; const merge = b.addSystemCommand(&.{ "kcov", "--merge" }); const merged = merge.addOutputDirectoryArg("cov"); for (test_bins) |t| { const run = b.addSystemCommand(&.{"kcov"}); run.addArg(b.fmt("--include-pattern={s}/", .{b.pathFromRoot("src")})); merge.addDirectoryArg(run.addOutputDirectoryArg("kcov")); run.addArtifactArg(t); } const install = b.addInstallDirectory(.{ .source_dir = merged, .install_dir = .prefix, .install_subdir = "cov", }); const summary = b.addSystemCommand(&.{ "python3", "-c", cov_summary_py }); summary.addFileArg(merged.path(b, "kcov-merged/coverage.json")); summary.step.dependOn(&install.step); b.step("cov", "Merged kcov line coverage (out-of-band)").dependOn(&summary.step); }