# Everything CI *builds*, as Nix derivations (crane). The script-only checks # (node tests, kb, the checkers, the comment and CI-graph gates, fmt) stay dev # shell steps in ci.yml: they compile nothing, and their `#!/usr/bin/env bash` # shebangs -- which scripts/checkers.sh itself enforces -- cannot run in the # build sandbox, which has no /usr/bin/env. # # Why Nix: the self-hosted runners substitute from titan's harmonia first, and # titan builds gh/main's outputs hourly. Dependencies are separate derivations # (buildDepsOnly, keyed on the Cargo.lock), so a PR that leaves the lock alone # rebuilds only the workspace. { pkgs, craneLib, wasmBindgenCli, src, rev, }: let inherit (pkgs) lib; # Two lockfiles: harness/opfs-contract is its own workspace root. cargoVendorDir = craneLib.vendorMultipleCargoDeps { cargoLockList = [../Cargo.lock ../harness/opfs-contract/Cargo.lock]; }; common = { inherit src cargoVendorDir; pname = "improvise"; version = "0"; strictDeps = true; # The `test` job ran the dev/test profiles, not crane's default release: # debug assertions and overflow checks are part of what it tested. CARGO_PROFILE = ""; }; wasmTarget = "wasm32-unknown-unknown"; # The crates in the browser bundle, checked for wasm32 one at a time: a # single `cargo check -p a -p b` unifies features across them and can hide # a crate that only builds with a sibling's features (improvise-ywd). wasmCrates = [ "improvise-core" "improvise-formula" "improvise-protocol" "improvise-io" "improvise-command" "improvise-wasm-client" "improvise-worker-server" "improvise-gp" # ADR 005 clause 14: nothing depends on it yet, and improvise-core will. "numtower" ]; pFlags = crates: lib.concatMapStringsSep " " (c: "-p ${c}") crates; nativeDeps = craneLib.buildDepsOnly (common // { pname = "improvise-deps"; cargoExtraArgs = "--locked --workspace"; }); wasmCheckDeps = craneLib.buildDepsOnly (common // { pname = "improvise-wasm-check-deps"; cargoExtraArgs = "--locked --target ${wasmTarget} ${pFlags wasmCrates}"; cargoCheckExtraArgs = ""; doCheck = false; }); # What wasm-pack's `cargo build --release --target wasm32` needs. bundleDeps = craneLib.buildDepsOnly (common // { pname = "improvise-bundle-deps"; CARGO_PROFILE = "release"; cargoExtraArgs = "--locked --target ${wasmTarget} ${pFlags ["improvise-wasm-client" "improvise-worker-server"]}"; cargoCheckCommand = "true"; doCheck = false; }); # A derivation that runs cargo commands and only has to succeed. cargoCheck = name: args: craneLib.mkCargoDerivation (common // { pname = "improvise-${name}"; doInstallCargoArtifacts = false; installPhaseCommand = "mkdir -p $out"; } // args); in rec { checks = { clippy = craneLib.cargoClippy (common // { pname = "improvise-clippy"; cargoArtifacts = nativeDeps; cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings"; }); # The evidence gate runs `cargo test --workspace -- --list`, so it runs # here, where the tests are already compiled, not as a separate step. test = craneLib.cargoTest (common // { pname = "improvise-test"; cargoArtifacts = nativeDeps; cargoTestExtraArgs = "--workspace"; nativeBuildInputs = [pkgs.python3 pkgs.git]; # tests/shipped_documents_load.rs walks the git index (`git ls-files`) # and makes scratch repos. The flake source is exactly the tracked # files, so force-adding all of it (but crane's target/) rebuilds the # checkout's index, ignored-but-tracked documents included. preBuild = '' export HOME=$TMPDIR export GIT_AUTHOR_NAME=nix GIT_AUTHOR_EMAIL=nix@localhost export GIT_COMMITTER_NAME=nix GIT_COMMITTER_EMAIL=nix@localhost git init -q . git add --all --force -- . ':!target' ''; postBuild = '' python3 scripts/check-comment-runs.py --check-evidence ''; }); wasm-check = cargoCheck "wasm-check" { cargoArtifacts = wasmCheckDeps; buildPhaseCargoCommand = lib.concatMapStringsSep "\n" (c: '' echo "== ${c}" cargo check --locked -p ${c} --target ${wasmTarget} '') wasmCrates; }; # harness/opfs-contract is its own workspace, so the root clippy never # reaches it; `-p` keeps cargo fmt to this one crate (see ci.yml). harness-lint = cargoCheck "harness-lint" { cargoArtifacts = null; buildPhaseCargoCommand = '' cd harness/opfs-contract cargo fmt -p opfs-contract -- --check cargo clippy --locked --target ${wasmTarget} --all-targets -- -D warnings ''; }; }; packages = { # The dependency builds on their own, for titan's cache timer to keep as a # GC root: the checks' outputs do not reference them, so rooting only the # checks would let the part a PR actually reuses be collected. ci-deps = pkgs.linkFarmFromDrvs "improvise-ci-deps" [nativeDeps wasmCheckDeps bundleDeps]; # harness/build.sh, unchanged: the three wasm bundles and harness/serve. # No network: wasm-bindgen and wasm-opt come from PATH, so wasm-pack # downloads nothing (see wasmBindgenCli in flake.nix). web = craneLib.mkCargoDerivation (common // { pname = "improvise-web"; cargoArtifacts = bundleDeps; doInstallCargoArtifacts = false; nativeBuildInputs = [pkgs.wasm-pack wasmBindgenCli pkgs.binaryen pkgs.nodejs_24]; # web/assemble.sh calls scripts/assemble-stamp.sh, whose # `#!/usr/bin/env bash` has no interpreter in the sandbox. Patching # this private copy is harmless: no checker runs here. postPatch = "patchShebangs scripts web harness"; buildPhaseCargoCommand = '' export HOME=$TMPDIR ./harness/build.sh ''; installPhaseCommand = '' mkdir -p $out cp -r harness/serve $out/harness-serve cp -r web/pkg web/worker-pkg $out/ ''; }); # The Pages tree, stamped with the commit it was built from. Separate from # `web` so the stamp changing every commit does not rebuild the bundles. site = pkgs.runCommand "improvise-site" { nativeBuildInputs = [pkgs.nodejs_24]; } '' cp -r ${src} tree chmod -R u+w tree patchShebangs tree/scripts tree/web cp -r ${packages.web}/pkg ${packages.web}/worker-pkg tree/web/ chmod -R u+w tree/web cd tree ./web/assemble.sh $out ${rev} ''; }; }