da8bfebc36
ci / gate (push) Successful in 2m33s
Records the CI/release pipeline as ADR-ci-001 and relocates the nix-flake ADR from main's ADR-0049 to ADR-ci-002 (content unchanged, history note added). Both live in docs/ci/adr/ with a README index — a dated, ci-segmented namespace disjoint from main's integer ADR sequence, the same split the website subproject uses to avoid cross-branch number collisions. Drops the ADR-0049 entry from docs/adr/README. ci-001 covers the runner model, the baked nix CI image, the clippy+test gate, the static-musl release on tag, trigger hygiene, auth, and scope.
136 lines
6.9 KiB
Markdown
136 lines
6.9 KiB
Markdown
# ADR-ci-002: Nix flake for a reproducible dev + build environment
|
|
|
|
## Status
|
|
|
|
**Accepted (2026-06-12).** Implemented the same day on the `ci` branch:
|
|
`flake.nix`, `flake.lock`, `rust-toolchain.toml`, `.envrc`. Verified
|
|
end-to-end before acceptance — `nix develop` provides the pinned
|
|
toolchain; `nix build .#default` produces a working binary; `cargo
|
|
clippy --all-targets -- -D warnings` is clean and `cargo test` is
|
|
**2424 passed / 0 failed / 1 ignored** (the ignored item is the
|
|
intentional ```` ```ignore ```` doctest at `src/friendly/mod.rs:21`),
|
|
all run *through the flake*. This ADR is the dev/build-environment
|
|
foundation; the CI **pipeline** that consumes it (runner model, image,
|
|
gate, release) is **ADR-ci-001**.
|
|
|
|
> **History.** Created as **ADR-0049** in `main`'s integer ADR namespace
|
|
> (`docs/adr/`); moved here to **ADR-ci-002** on 2026-06-12 to keep the
|
|
> CI/dev-env decisions out of `main`'s sequence and end the cross-branch
|
|
> number collision (`main` independently reaches for the next integer too —
|
|
> the same problem the website subproject hit). Content is otherwise
|
|
> unchanged. See ADR-0000 "Numbering discipline".
|
|
|
|
## Context
|
|
|
|
The project is near feature-complete and CI is finally being set up
|
|
(`requirements.md` **TT5**, **CI** in the deferred list). CI must not
|
|
depend on whatever Rust/toolchain happens to be installed on the build
|
|
machine — that is neither reproducible nor honest about what the build
|
|
needs.
|
|
|
|
The sibling project **datamage** already solved this with a Nix flake
|
|
(its ADR 0046): the flake is the single, version-pinned declaration of
|
|
the toolchain, and both the dev shell and CI go through it so they
|
|
cannot drift. We adopt the same pattern here. Ours is dramatically
|
|
simpler than datamage's — this is a pure-Rust TUI with no Tauri /
|
|
WebKitGTK / Node / WASM surface — so the flake carries almost no system
|
|
dependencies.
|
|
|
|
Two build facts drove the (tiny) dependency set, confirmed from
|
|
`Cargo.lock`:
|
|
|
|
- **`libsqlite3-sys` is built with `bundled`** → SQLite is compiled
|
|
from vendored C, which needs a C compiler. `nixpkgs`' `stdenv`
|
|
provides one automatically; nothing is declared for it.
|
|
- **`arboard`'s clipboard backend is `x11rb`** — a pure-Rust socket
|
|
XCB client that links *no* C X11 libraries. So no X11/`pkg-config`
|
|
system inputs are needed to build or test. A live X server is only
|
|
required at *runtime* to actually copy; headless sessions fall back
|
|
to OSC 52.
|
|
|
|
## Decision
|
|
|
|
Adopt a **Nix flake** at the repository root as the canonical
|
|
declaration of the dev *and* build environment.
|
|
|
|
- **`flake.nix`** exposes two outputs (user-chosen 2026-06-12 over a
|
|
dev-shell-only variant):
|
|
- **`devShells.default`** — the pinned Rust toolchain (from
|
|
`rust-toolchain.toml` via `rust-overlay`) plus `cargo-sweep` for
|
|
the `target/` build-hygiene discipline (CLAUDE.md / the datamage
|
|
ADR 0050 equivalent).
|
|
- **`packages.default`** (= `packages.rdbms-playground`) — a
|
|
`rustPlatform.buildRustPackage` that produces the binary
|
|
reproducibly from the pinned toolchain and the committed
|
|
`Cargo.lock` (`cargoLock.lockFile` → `importCargoLock`, which
|
|
fetches each dependency by its lockfile checksum: offline,
|
|
deterministic, no `cargoHash` to churn). `nix build` yields the
|
|
artifact CI's gate/release can consume.
|
|
- **`rust-toolchain.toml`** pins an **exact stable release**
|
|
(`1.95.0`), not the floating `stable` channel, so `nix flake update`
|
|
cannot surprise-bump Rust into new clippy lints that would fail the
|
|
`-D warnings` gate (same reasoning as datamage ADR 0046). Components:
|
|
`rustfmt` + `clippy`. No coverage/WASM tooling and no
|
|
cross-compilation targets yet — those are added when the release
|
|
matrix needs them, not before.
|
|
- **`flake.lock`** pins every input (`nixpkgs` `nixos-26.05`,
|
|
`rust-overlay`, `flake-utils`) to a commit, making the env
|
|
bit-reproducible.
|
|
- **`.envrc`** contains `use flake` for direnv auto-activation, kept
|
|
for parity with datamage even though direnv is not installed on the
|
|
current dev VM (entry is via `nix develop`).
|
|
- **`packages.default` sets `doCheck = false`.** The test suite is
|
|
*not* run during `nix build` — the Nix build sandbox has no `HOME`
|
|
and no X server, which fights the project-directory / clipboard
|
|
paths the tests touch. Tests run as their own CI stage via
|
|
`nix develop -c cargo test`, keeping "build the artifact" and "run
|
|
the suite" cleanly separate.
|
|
- **The package version is read from `Cargo.toml`** via
|
|
`builtins.fromTOML`, so it never drifts from the crate metadata.
|
|
|
|
## Consequences
|
|
|
|
- **One toolchain definition.** Dev and CI share the exact pinned
|
|
toolchain; they cannot drift. New contributors run `nix develop`
|
|
(or get auto-activation via direnv) and have the same Rust as CI.
|
|
- **D2 (static binary) is unaffected and still pending.** The
|
|
`nix build` artifact links the Nix-store glibc *dynamically* — it is
|
|
a reproducible build/test artifact, **not** the single static
|
|
release binary D2 calls for. Release binaries will target a static
|
|
toolchain (e.g. `x86_64-unknown-linux-musl`) in the forthcoming CI
|
|
release work; that is a release-step concern, not a dev-shell one.
|
|
- **`fmt` is deliberately *not* gated yet.** The tree is not clean
|
|
under stock `rustfmt` (~100 files would change; no `rustfmt.toml` is
|
|
committed and the code was shaped by something other than default
|
|
`rustfmt`). Reformatting churns blame across every file and would
|
|
conflict with the in-flight website branch and ongoing `main` work,
|
|
so — user decision 2026-06-12 — the `fmt` gate is left out for now
|
|
and revisited on `main`. The CI gate is `clippy` + `test`.
|
|
- **Engine-name posture (CLAUDE.md) is respected.** The flake's
|
|
comments may name SQLite/`rusqlite` where technically necessary
|
|
(build-input rationale); no user-facing string is affected.
|
|
|
|
## Alternatives considered
|
|
|
|
- **Dev-shell only (no build package).** Matches datamage exactly; CI
|
|
would `cargo build` inside `nix develop -c`. Rejected (user choice):
|
|
a `nix build` package gives a reproducible release artifact straight
|
|
from the pinned toolchain, which the release job wants.
|
|
- **A standard `rust:1.95` image in CI, flake for dev only.** Simpler
|
|
in CI (no nix-in-CI caching to solve), but it is a *second* place
|
|
that defines the toolchain — exactly the drift this ADR exists to
|
|
prevent. Rejected for the unified-env goal; the nix-in-CI caching
|
|
cost is solved in the CI pipeline work instead.
|
|
- **`rustup` on the build machine.** The status quo CI would replace —
|
|
non-reproducible, machine-dependent, the thing we are eliminating.
|
|
|
|
## Relationship to other decisions
|
|
|
|
- Mirrors **datamage ADR 0046** (nix flake dev env) and its build
|
|
hygiene companion. This is the rdbms-playground analogue, scoped to
|
|
a pure-Rust project.
|
|
- Feeds **ADR-ci-001** (the CI + release pipeline), which consumes this
|
|
flake for `requirements.md` **TT5** (CI runs the tiers) and the
|
|
**D1/D2/D3** distribution items (the release uses a static musl target
|
|
built through this flake).
|