From 9189740028cd344d15987b53dce87480d23c4376 Mon Sep 17 00:00:00 2001 From: "claude@clouddev1" Date: Fri, 12 Jun 2026 20:35:39 +0000 Subject: [PATCH] build(nix): reproducible dev + build env via a flake (ADR-0049) Root flake with two outputs: devShells.default (pinned 1.95.0 toolchain via rust-toolchain.toml + rust-overlay, plus cargo-sweep) and packages.default (rustPlatform.buildRustPackage from the committed Cargo.lock; doCheck=false). flake.lock pins nixpkgs nixos-26.05 / rust-overlay / flake-utils. .envrc (use flake) for direnv parity. Single source of toolchain for dev and the upcoming CI, so they can't drift. Verified through the flake: nix build yields a working binary, clippy clean, 2424 tests pass / 0 fail / 1 intentional ignored doctest. First step toward requirements.md TT5 + D1/D2/D3. --- .envrc | 1 + .gitignore | 6 + docs/adr/0049-nix-flake-dev-and-build-env.md | 127 +++++++++++++++++++ docs/adr/README.md | 1 + flake.lock | 82 ++++++++++++ flake.nix | 80 ++++++++++++ rust-toolchain.toml | 10 ++ 7 files changed, 307 insertions(+) create mode 100644 .envrc create mode 100644 docs/adr/0049-nix-flake-dev-and-build-env.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 rust-toolchain.toml diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 6b15ae8..5b370c4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,12 @@ /target **/*.rs.bk +# Nix +# `nix build` output symlinks (`result`, `result-`), direnv's cached env +/result +/result-* +.direnv/ + # Snapshot test review files *.snap.new *.pending-snap diff --git a/docs/adr/0049-nix-flake-dev-and-build-env.md b/docs/adr/0049-nix-flake-dev-and-build-env.md new file mode 100644 index 0000000..2c2d9aa --- /dev/null +++ b/docs/adr/0049-nix-flake-dev-and-build-env.md @@ -0,0 +1,127 @@ +# ADR-0049: 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 +half of the CI work; the CI **pipeline** itself (runner wiring, +release matrix) is decided separately as it settles. + +## 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 the CI pipeline work for `requirements.md` **TT5** (CI runs + the tiers) and the **D1/D2/D3** distribution items (the release + matrix consumes `nix build` / a static target). diff --git a/docs/adr/README.md b/docs/adr/README.md index 2e15522..7d3d36f 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -54,3 +54,4 @@ This directory contains the project's ADRs, recorded per - [ADR-0046 — Schema sidebar focus/navigation mode and responsive input & hint layout (UI #20/#21/#23)](0046-sidebar-navigation-and-responsive-input-hint.md) — **Accepted + implemented 2026-06-10, phased A→B→C** (8 commits `9f5f76b`…`22bec61`; closes Gitea **#20** hint jumpiness, **#21** left-column improvements, **#23** long input — all forks user-confirmed, including the persistent show/hide toggle which is **deferred**: the Ctrl-O peek covers #21's "keystroke to show and hide"). Two decisions landed differently from the draft (recorded inline): relationship data on **`App`** not `SchemaCache` (DB2); the nav overlay clears **only the sidebar strip + a one-column gutter**, panels staying visible behind (DC2). Treats the three UI issues as one coupled decision because they share the terminal's width/height budget. **Phase A (input & hint):** the hint panel's height becomes a function of **terminal geometry, fixed between resizes** (not of hint content), eliminating the #20 jump at its source — measured catalog shows ≥ ~54-col right-column width never needs > 2 hint lines, so 3 lines is a rare narrow-terminal-only case; height buckets `H<40` compact (input 1 row + horizontal scroll / hint 2) vs `H≥40` comfortable (input 2 rows soft-wrap / hint 2), output `Min(5)` honoured first under degradation; input gains horizontal scroll (`input_scroll_offset`, single logical `String` — **not** I1 multi-line) and 2-row soft-wrap display when tall, preserving ADR-0027's 6-col indicator reserve. **Phase B (sidebar):** the 26-col Tables column is **kept but made optional and richer** (not deleted — pedagogy wins ties) — **width-derived session-only** visibility (visible iff width > 90 or a Ctrl-O peek is active — no stored field; hides at width ≤ 90 so the 90-col screencasts drop it; ADR-0015 format untouched), plus a **relationships panel** rendered narrow with endpoints broken at the arrow, ellipsized — a **separate sibling panel** that **overrides S2**'s nested-list extension model (relationships are cross-table). the full records live on a new **`App.relationships`** field (revised from the ADR's original `SchemaCache.relationship_details` at implementation — `SchemaCache` is walker-facing and needs only the names, kept in `relationships: Vec`; details are UI-only, so `App` mirrors `app.tables` and avoids ~23 fixture edits), delivered by `Database::read_all_relationships` + an `AppEvent::RelationshipsRefreshed`; the two left panels split vertically with the relationships panel floored at 5 rows ("(none)" when empty) and capped at 50 % of the column (DB4). **Phase C (navigation mode):** **`Ctrl-O`** enters a focus cycle (Input → Tables → Relationships → Input; `Esc` exits) orthogonal to the ADR-0003 input mode — **`Ctrl-B` was rejected on review as the default tmux prefix** (unreachable inside tmux); the focused panel **expands to ~40–50 cols as a `Clear` overlay** (right panels stay unchanging underneath) and scrolls via **Up/Down (line) + PageUp/PageDown (page)** (context-rebind, reusing the output-scroll viewport mechanism), with an accent focus border; all non-nav keys inert in nav mode (and nav keys inert while a modal is open). Forks all user-chosen: keep-optional-richer (vs remove/narrow); navigation-mode (vs modeless modifier scroll); `Ctrl-O` (Ctrl-B rejected = tmux prefix); overlay (vs layout re-split); inert-non-nav-keys; geometry-fixed hint height; `H<40/≥40` thresholds; session-only persistence; Up/Down line-scroll; **separate relationships panel overriding S2**; **no hint-area toggle** (S4's stale "keyboard-toggleable" claim struck — never implemented, unwanted). A pre-build `/runda` DA pass drove these corrections: caught the `Ctrl-B`/tmux collision, the `SchemaCache` retype that would have broken completion, the 2-row-input/indicator placement, the missing nav-mode key disposition + modal gate, and three unreferenced requirements (S1 evolved, S2 overridden, S4 corrected); also cross-checked open issue **#22** (overlay/annotation layer — separate ADR, adjacent). OOS: true multi-line input (I1); readline shortcuts (I1b); cross-session sidebar persistence; output as a third nav focus; relationship search/edit from the panel; hint-area toggle; #22's annotation layer. Accepted consequence: the 90-col visibility threshold makes a terminal's output *narrower* when widened across the boundary (sidebar appears) - [ADR-0047 — Demonstration overlay layer (keystroke badges + step captions)](0047-demonstration-overlay-layer.md) — **Accepted 2026-06-10; implemented 2026-06-11, phased A→B→C (closes Gitea #22)** (commits `f879d54`→`2d0f4b2`; no `requirements.md` item — tracked by issue + ADR per convention; all forks user-confirmed + a pre-build `/runda` pass that produced 10 tightening findings and a whole-implementation `/runda` pass that returned PASS, no blockers). An in-app **demonstration mode** (`--demo` flag / `RDBMS_PLAYGROUND_DEMO` env, **off by default, zero footprint when off**) that renders two transient overlays so `autocast` screencasts — and live teaching, and a future guided-lesson system — can show otherwise-invisible interactions. **Keystroke badges** (`[TAB]`, `[ENTER]`, `[UP]`, …): **automatic, app-detected** over a fixed set of glyph-less keys (the app already sees every key, so it re-records for free), label via a pure `demo_badge_label(&KeyEvent)`; the badge **auto-expires on a ~1.5 s timer** that extends the runtime's existing time-boxed-`recv` arm condition (`debounce.is_armed() || badge_pending`; expiry `Instant` in the runtime, `App.demo_badge` the render mirror — mirroring the `input` vs `input_indicator` split). **Step captions**: a **stealth, control-code-delimited input buffer** toggled by **`Ctrl+]`** (byte `0x1D` → arrives as `Char('5')+CONTROL`, verified against crossterm 0.29 `parse.rs:110-113`; chosen over `Ctrl+!`, which is **not a single ASCII byte so autocast cannot send it** — the same wall as arrow keys, R4) — typed characters accumulate **invisibly** (prompt untouched, no echo/history), `Backspace` edits, other keys inert, a second `Ctrl+]` **commits** to the caption box (empty commit dismisses); lives in pure-sync `App::update()`, **intercepted before the modal gate** so captions/badges work **over the load picker** (the `#24` projects cast). Both render as **floating flat black-on-yellow rectangles** (solid fill, **no border glyphs** — a one-cell text margin, deliberately unlike the app's bordered panels; user decision post-build, `2d0f4b2`) **at the output panel's inner bottom-right**, drawn **last over modals**, badge **stacked above** the caption, **no layout reflow**; caption **word-wraps to ≤ 3 lines** (3–5 rows), badge fixed 3 rows; clamp/skip guard for tiny terminals; a new **`App.last_output_area: Rect`** (set in `render_output_panel`) gives the top-level draw the anchor. Caption persists **until the next keystroke**; badge suppressed while capturing. Forks all user-chosen: `--demo` activation (vs hidden command / chord); automatic badges (vs scripted); stealth buffer (vs typed-command / preloaded-file); floating bottom-right boxes (vs HUD / banner / subtitle); `Ctrl+]` trigger; wrap-to-3-line captions; ~1.5 s badge / next-keystroke caption timing. Tested test-first across Tier 1 (label fn, capture state machine incl. over-modal + demo-off gate, nearest-deadline helper), Tier 2 (insta snapshots: badge/caption/both-stacked at 90×26 light+dark, short-terminal clamp), Tier 3 (`--demo` plumbing, badge set/suppressed, caption-without-input wiring), CLI (`--demo` parse + env fallback) — with an **honest limit** noted: the `tokio` timer wiring inside `run_loop` is exercised via the pure pieces + Tier-3 plumbing, not a standalone integration test of the timeout (same posture as the existing `IndicatorDebounce`). One intentional, user-acknowledged behaviour: `Ctrl-C` is inert while capturing (every non-`Ctrl+]` key is, by spec). Final tally **2290 passing / 0 failing / 0 skipped** (1 long-standing ignored doctest), clippy clean. OOS: scripted/manual badge push; badges for glyph keys; configurable styling/placement; the guided-lesson system itself (own ADR); cross-session/-switch persistence; localised caption content; arrow-only cast interactions (output-pane scroll); wiring the overlays into the website `casts.mjs` scripts (website-branch follow-up). Implementation phased **A** (`--demo` plumbing) → **B** (badges) → **C** (captions) + a flat-rectangle restyle - [ADR-0048 — `seed` fake-data generation command](0048-seed-fake-data-generation.md) — **Accepted 2026-06-11; Phase 1 + Phase 2 implemented 2026-06-11** (Phase 1 commits `202e25a`→`fbd219b`; design settled with the user across an extended fork dialogue, hardened by a pre-build `/runda` pass (six blockers folded in), a post-implementation `/runda` pass (eight gaps closed — FK/shortid determinism so **D4 holds with no exceptions**, plus six untested ADR decisions), and a Phase-2 pre-build `/runda` pass (which caught the no-date-literal-token reality → the D2 quoted-dates amendment), and a post-implementation `/runda` pass (which added a friendly error for a bounded override on a UNIQUE column — see D2); **2400 tests pass, clippy clean**). Closes `requirements.md` **SD1** and the core of **SD2**; closes the `seed` half of **A1**. **Phase 1 shipped:** whole-row `seed [count] [--seed ]` with realistic name-aware generation (the `fake` crate + a type-gated heuristic catalogue, table-context name disambiguation, hand-rolled `product` generator, bounded dates), identifier + constraint uniqueness incl. junction distinct-combos, FK sampling from existing parent rows (empty-parent error), `IN`-CHECK derivation + complex-CHECK advisory, a required-column block guard, `--seed` reproducibility (serial/FK/shortid all deterministic), undo as one batch step, replay as a data write, a capped auto-show preview, the enum/CHECK advisory, and an O(N) single-transaction insert path. **Phase 2 shipped (2026-06-11):** the `set` override clause (D2 — fixed value / pick-list / `as ` / `between` range, **quoted** dates per the D2 amendment, type-aware, override drops the column from the advisory) and the `
.` column-fill form (D1 form 2 — an UPDATE over existing rows, refusing PK/autogen targets, empty-table no-op, FK/unique-respecting, one undo step), with the new `KNOWN_GENERATORS` vocabulary (D9), a range `Generator`, full completion/highlight (`HighlightClass::Function`)/validity (`IdentSource::Generators`)/help/pedagogy wiring, and the D13 advisory's Phase-2/3 wording. Further SD2 increments (custom generators, NULL injection, multi-locale, recursive auto-seed) out of scope. Closes `requirements.md` **SD1** and the core of **SD2**; closes the `seed` half of **A1** (the other being `hint`/**H2**). A dedicated `seed` command (own AST variant + `do_seed` executor, **both modes**) generating **realistic, name-aware** fake data. Two forms: **`seed
[count]`** (new rows, default **20**, capped) and **`seed
.`** (fill a column on existing rows, an UPDATE). Generation adds the **`fake` crate** (v5, English) driven by a **type-gated, token-matched name-heuristic catalogue** (~30 patterns, documented false-positive guards), with **table-context** disambiguating the `name`/`title` family (`products.name`→product, `users.name`→person, `vendors.name`→company), a **hand-rolled `product` generator** (`fake` has no commerce module), **bounded dates** (`date`/`timestamp`/`dob`/`*_at` recognised, recent windows — never "all of history"), the **identifier family** (`id`/`code`/`ref`/`number`, non-FK/non-PK) → **unique sequential**, and **enum-ish names** (`role`/`status`/`type`/…) left generic + a **post-seed Hint advisory** pointing at `set … in (…)`. A **`set` override clause** — `= value` / `in (a,b,c)` / `as ` / `between a and b` (numeric **and** date), reusing ADR-0026 operators — answers the heuristic-miss case. **`--seed `** makes runs reproducible (and enables exact-value tests). **FK** columns sampled uniformly from existing parent rows (**empty parent → friendly error**, no recursion v1); **junction/compound-PK** tables seeded with **distinct combinations**, capped + noted (SD1). A **required-column block guard** refuses rather than NULL-violate a `NOT NULL` column it can't fill (e.g. `NOT NULL blob`). Full ambient wiring (completion incl. a new generator-name vocabulary highlighted as `tok_function`, hints, `help seed`, ADR-0042 near-miss matrix, ADR-0027 validity); **no DSL→SQL teaching echo** (seed is a utility command, not a SQL twin). Honours **X5** — `do_seed` reuses insert/update *mechanics as helpers*, not by emitting `Command::Insert`. Implementation phased: (1) core whole-row seed → (2) `set` overrides → (3) column-fill. Deferred (future SD2): recursive auto-seed, NULL injection, multi-locale, user-defined custom generators, full per-column report +- [ADR-0049 — Nix flake for a reproducible dev + build environment](0049-nix-flake-dev-and-build-env.md) — **Accepted + implemented 2026-06-12** (`ci` branch; first step of the CI work toward `requirements.md` **TT5** + **D1/D2/D3**). Adopts a root **Nix flake** as the single, version-pinned declaration of the dev *and* build toolchain so CI never relies on whatever Rust is installed on the build machine — mirroring **datamage ADR 0046**, but far simpler (pure-Rust TUI: no Tauri/WebKit/Node/WASM). Two outputs (user-chosen over dev-shell-only): **`devShells.default`** (pinned toolchain via `rust-toolchain.toml` + `rust-overlay`, plus `cargo-sweep`) and **`packages.default`** (a `rustPlatform.buildRustPackage` building the binary reproducibly from the committed `Cargo.lock` via `importCargoLock`; `doCheck = false` — the suite runs as its own `nix develop -c cargo test` stage, not in the HOME/X-less build sandbox; version read from `Cargo.toml` via `fromTOML`). Toolchain pinned to **exact `1.95.0`** (not floating `stable`) so `nix flake update` can't surprise-bump clippy lints past the `-D warnings` gate; components `rustfmt` + `clippy`; **no** cross/WASM targets yet (added when the release matrix needs them). System inputs are nearly empty by design — `libsqlite3-sys` `bundled` needs only the stdenv C compiler; `arboard`→`x11rb` is pure-Rust (no C X11 libs, X server only needed at *runtime*, OSC 52 otherwise). `.envrc` (`use flake`) kept for direnv parity though direnv isn't on the current VM. **Verified before acceptance:** `nix develop` toolchain pinned, `nix build` yields a working binary, clippy clean, **2424 tests pass / 0 fail / 1 intentional ignored doctest** — all through the flake. Consequences: the `nix build` artifact is glibc-**dynamic** (a reproducible build/test artifact, **not** the D2 static release binary — release uses a static target like `x86_64-unknown-linux-musl`, deferred to the CI release work); the **`fmt` gate is deliberately left out for now** (user decision — the tree isn't clean under stock `rustfmt`, ~100 files would churn and conflict with the website/`main` work; revisit on `main`), so the gate is **`clippy` + `test`**. Alternatives rejected: dev-shell-only (no reproducible artifact); a standard `rust:1.95` CI image (a second toolchain definition = drift, the very thing this prevents); `rustup` on the build host (non-reproducible — the status quo being eliminated). The CI **pipeline** itself (runner wiring, release matrix) is decided separately as it settles. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..69958c3 --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1780902259, + "narHash": "sha256-q8yYEC5f1mFlQO9RGna4LTc9QrcvWunX6FYp83munkQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "bd0ff2d3eac24699c3664d5966b9ef36f388e2ca", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-26.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781234414, + "narHash": "sha256-HdA+P4fKRGOomkewnI/Tww5Wz4xK1O7+hDO90YAsPB4=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "1d18bfe3de6244c641ca4e8011186d0981b81d76", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..e5f8fd8 --- /dev/null +++ b/flake.nix @@ -0,0 +1,80 @@ +{ + description = "RDBMS Playground — Rust TUI dev environment + reproducible build"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, rust-overlay, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ (import rust-overlay) ]; + }; + + # Single source of the Rust toolchain: the rustup toolchain file. + # rust-overlay provisions the exact channel + components declared there, + # so the dev shell and the build package share one pinned toolchain. + rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; + + # Read the package version straight from Cargo.toml so it never drifts + # from the crate metadata (no hand-maintained duplicate here). + cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml); + + # System build inputs are deliberately tiny — this is a pure-Rust TUI: + # * libsqlite3-sys is built with the `bundled` feature, so SQLite is + # compiled from vendored C. That needs a C compiler, which the + # stdenv provides automatically (no entry required here). + # * arboard's clipboard backend is `x11rb` — a pure-Rust socket XCB + # client. It links no C X11 libraries, so none appear below. A live + # X server is only needed at *runtime* to copy; headless sessions + # fall back to OSC 52. + # If a future dependency introduces a pkg-config / native-lib link, add + # it here (and document why) rather than leaking it into the host env. + nativeBuildInputs = [ ]; + buildInputs = [ ]; + + # `nix build` → the release binary, built reproducibly from the pinned + # toolchain and the committed Cargo.lock (importCargoLock fetches each + # dependency by its lockfile checksum — offline, no cargoHash to churn). + # CI's release job consumes this artifact; the gate's tests run + # separately via `nix develop -c cargo test` (see below), so the package + # build skips the suite — the nix sandbox has no HOME/X server and would + # fight the project-dirs / clipboard paths the tests touch. + rdbms-playground = pkgs.rustPlatform.buildRustPackage { + pname = cargoToml.package.name; + version = cargoToml.package.version; + src = ./.; + cargoLock.lockFile = ./Cargo.lock; + inherit nativeBuildInputs buildInputs; + doCheck = false; + }; + in { + packages.default = rdbms-playground; + packages.rdbms-playground = rdbms-playground; + + devShells.default = pkgs.mkShell { + inherit buildInputs; + nativeBuildInputs = nativeBuildInputs ++ [ + rust + # Dev-disk maintenance: cargo never garbage-collects stale per-hash + # build artifacts, so target/ creeps into the tens of GB (see + # CLAUDE.md "Build hygiene"). cargo-sweep prunes them; run it + # periodically between milestones. + pkgs.cargo-sweep + ]; + + shellHook = '' + echo "RDBMS Playground dev shell ($(uname -s))" + echo " rust: $(rustc --version | cut -d' ' -f1-2)" + echo " cargo: $(cargo --version | cut -d' ' -f1-2)" + ''; + }; + }); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..cabb063 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,10 @@ +[toolchain] +# Pinned to an exact stable release (not the floating "stable" channel) so +# `nix flake update` cannot surprise-bump Rust into new clippy lints that would +# fail the `-D warnings` CI gate. Matches the host toolchain and the datamage +# flake's convention (its ADR 0046). Bump deliberately, in its own commit. +channel = "1.95.0" +# rustfmt + clippy back the `fmt`/`clippy` CI stages; no coverage or WASM +# tooling is needed here (pure-Rust TUI). Cross-compilation targets for the +# eventual D1 release matrix are added when that CI lands, not before. +components = ["rustfmt", "clippy"]