merging ci branch
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
# Windows cross-link fix for the D1 release matrix (cargo-zigbuild).
|
||||||
|
#
|
||||||
|
# Rust's std links `-lsynchronization` on Windows (WaitOnAddress-based thread
|
||||||
|
# parking). Rust normally satisfies this from the `self-contained` mingw libs
|
||||||
|
# of its `rust-mingw` component — which rust-overlay does NOT ship — and Zig's
|
||||||
|
# bundled mingw (used by `cargo zigbuild`) doesn't provide `libsynchronization.a`
|
||||||
|
# either. The actual symbols are *forwarded by kernel32* (already linked), so an
|
||||||
|
# empty stub import lib is enough to satisfy the linker. See `ci/winstub/`.
|
||||||
|
#
|
||||||
|
# These sections apply ONLY when building for the Windows targets, so host
|
||||||
|
# builds (the gate's `cargo test`/`clippy`) and the Linux release targets are
|
||||||
|
# unaffected.
|
||||||
|
[target.x86_64-pc-windows-gnu]
|
||||||
|
rustflags = ["-L", "native=ci/winstub"]
|
||||||
|
|
||||||
|
[target.aarch64-pc-windows-gnullvm]
|
||||||
|
rustflags = ["-L", "native=ci/winstub"]
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# CI toolchain image for rdbms-playground.
|
||||||
|
#
|
||||||
|
# Purpose: a SMALL job-container image that
|
||||||
|
# (a) satisfies the Gitea act_runner job-container contract — /bin/sleep (the
|
||||||
|
# keep-alive entrypoint), bash (run: steps), node (JS actions such as
|
||||||
|
# actions/checkout); a bare nixos/nix image has none of these and won't
|
||||||
|
# even start (verified by the ci-probe run: "/bin/sleep: no such file"); and
|
||||||
|
# (b) carries the project's pinned nix toolchain with the flake's devShell
|
||||||
|
# pre-warmed, so CI runs `nix develop -c cargo ...` against a warm store.
|
||||||
|
#
|
||||||
|
# Base: node:22-bookworm-slim. Debian slim already provides bash + coreutils
|
||||||
|
# (sleep); the node tag adds the actions runtime. Far smaller than the
|
||||||
|
# catthehacker runner images (which bundle a whole GitHub-runner emulation we
|
||||||
|
# don't need).
|
||||||
|
FROM node:22-bookworm-slim
|
||||||
|
|
||||||
|
# nix install + flake eval needs these. git because flakes prefer a VCS context
|
||||||
|
# and tools shell out to it. Drop apt lists to keep the layer small.
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
curl xz-utils ca-certificates git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Single-user nix (--no-daemon): store at /nix owned by root, no daemon/systemd
|
||||||
|
# needed — the correct mode for a container. The official installer refuses root
|
||||||
|
# and shells out to `sudo` purely to create /nix; pre-creating it ourselves (we
|
||||||
|
# ARE root) sidesteps both. Enable flakes globally so every nix invocation (and
|
||||||
|
# the runner's steps) get nix-command + flakes without flags.
|
||||||
|
# nix.conf is written FIRST so the installer's own `nix-env` profile step reads
|
||||||
|
# it: `build-users-group =` (empty) makes single-user nix build as the calling
|
||||||
|
# user (root) instead of demanding the nixbld group/users a daemon install would
|
||||||
|
# create; flakes are enabled globally in the same file.
|
||||||
|
RUN mkdir -m 0755 /nix && chown root:root /nix \
|
||||||
|
&& mkdir -p /etc/nix \
|
||||||
|
&& printf 'build-users-group =\nexperimental-features = nix-command flakes\n' > /etc/nix/nix.conf \
|
||||||
|
&& curl --proto '=https' --tlsv1.2 -sSf -L https://nixos.org/nix/install -o /tmp/nix-install.sh \
|
||||||
|
&& sh /tmp/nix-install.sh --no-daemon \
|
||||||
|
&& rm /tmp/nix-install.sh
|
||||||
|
ENV PATH=/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH
|
||||||
|
# We set PATH directly instead of sourcing the profile, so also point nix at the
|
||||||
|
# Debian CA bundle (already installed) for substituter HTTPS — otherwise the
|
||||||
|
# profile-provided NIX_SSL_CERT_FILE is missing and store downloads fail.
|
||||||
|
ENV NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||||
|
|
||||||
|
# Warm the flake's devShell into the store: realizes nixpkgs + the pinned Rust
|
||||||
|
# toolchain (rustc/cargo/clippy/rustfmt) + cargo-sweep. Only the inputs that
|
||||||
|
# determine the shell are copied, so this expensive layer is cached and only
|
||||||
|
# re-runs when the flake or the toolchain pin changes — not on every source edit.
|
||||||
|
# (devShell eval is lazy: packages.default — and thus Cargo.toml/Cargo.lock — is
|
||||||
|
# never forced here, so it needn't be present.)
|
||||||
|
WORKDIR /warm
|
||||||
|
COPY flake.nix flake.lock rust-toolchain.toml ./
|
||||||
|
RUN nix develop -c rustc --version \
|
||||||
|
&& nix develop -c cargo --version \
|
||||||
|
&& nix develop -c cargo clippy --version \
|
||||||
|
&& nix develop -c cargo fmt --version \
|
||||||
|
&& nix develop -c cargo sweep --version
|
||||||
|
WORKDIR /
|
||||||
|
RUN rm -rf /warm
|
||||||
|
|
||||||
|
# FOLLOW-UP optimisation (intentionally NOT done here, see CI notes): cargo
|
||||||
|
# dependency + target caching. Each CI run still compiles the ~296-crate graph
|
||||||
|
# from scratch and pulls crate sources from crates.io. A later pass can bake
|
||||||
|
# `cargo fetch` (offline crate sources) and/or a warmed target dir, or wire
|
||||||
|
# sccache, to cut run time. Correctness/first-green first; speed next.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Builds the nix CI toolchain image (.gitea/ci-image/Dockerfile) and pushes it
|
||||||
|
# to the Gitea registry. The gate (ci.yaml) runs *inside* this image, so this
|
||||||
|
# workflow is the gate's prerequisite. It only needs to run when the image's
|
||||||
|
# inputs change — the Dockerfile, the flake, or the toolchain pin — plus on
|
||||||
|
# manual dispatch.
|
||||||
|
#
|
||||||
|
# DinD pattern: plain docker:27-dind (one of the tested ci-test samples). No
|
||||||
|
# registry proxy here — the runner's containers have direct internet egress
|
||||||
|
# (the ci-probe run cloned github.com and pulled docker.io with no proxy), and
|
||||||
|
# this image's RUN steps fetch from apt + nixos.org, which the proxy isn't
|
||||||
|
# guaranteed to forward. The dind-cached:local + REGISTRY_PROXY_HOST variant is
|
||||||
|
# a later speed optimisation for base-image pull caching, not needed for green.
|
||||||
|
name: build-ci-image
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# Branch pushes only. Tag pushes ignore `paths:` filters and would rebuild
|
||||||
|
# the (unchanged) image on every release tag — `branches: ['**']` excludes
|
||||||
|
# tags, so this runs only when a branch push actually changes an image input.
|
||||||
|
branches: ['**']
|
||||||
|
paths:
|
||||||
|
- '.gitea/ci-image/Dockerfile'
|
||||||
|
- 'flake.nix'
|
||||||
|
- 'flake.lock'
|
||||||
|
- 'rust-toolchain.toml'
|
||||||
|
- '.gitea/workflows/build-ci-image.yaml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ci-public
|
||||||
|
services:
|
||||||
|
docker:
|
||||||
|
image: docker:27-dind
|
||||||
|
options: --privileged
|
||||||
|
env:
|
||||||
|
DOCKER_TLS_CERTDIR: ""
|
||||||
|
env:
|
||||||
|
DOCKER_HOST: tcp://docker:2375
|
||||||
|
IMAGE: git.lazyeval.net/oli/rdbms-playground-ci
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: wait for docker
|
||||||
|
run: until docker version >/dev/null 2>&1; do sleep 1; done
|
||||||
|
- name: registry login
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.REGISTRY_TOKEN }}" \
|
||||||
|
| docker login git.lazyeval.net -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
|
||||||
|
- name: build
|
||||||
|
run: docker build -f .gitea/ci-image/Dockerfile -t "$IMAGE:latest" .
|
||||||
|
- name: push
|
||||||
|
run: docker push "$IMAGE:latest"
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# The CI gate. Runs inside the prebuilt nix toolchain image (built + pushed by
|
||||||
|
# build-ci-image.yaml), so the pinned 1.95.0 toolchain is already warm — steps
|
||||||
|
# just enter the flake devShell and run cargo.
|
||||||
|
#
|
||||||
|
# Gate = clippy + test. fmt is deliberately NOT gated yet (ADR-ci-002: the tree
|
||||||
|
# isn't clean under stock rustfmt; revisit on main). The release job (static
|
||||||
|
# binary for D2) and the platform matrix layer on later, step by step.
|
||||||
|
name: ci
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# Branch pushes only — a tag push hits the same commit the branch push
|
||||||
|
# already gated, so `branches: ['**']` drops the redundant tag-triggered
|
||||||
|
# run (the release workflow owns tags). Pushing commits + a tag together
|
||||||
|
# still gates the commits via the branch push.
|
||||||
|
branches: ['**']
|
||||||
|
# Skip the gate for docs-only changes — markdown can't affect clippy/test.
|
||||||
|
# A push touching code *and* docs still runs (not all files are ignored).
|
||||||
|
# Note: flake/toolchain changes are NOT ignored — they can shift the
|
||||||
|
# toolchain and thus lint/test outcomes.
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- '**/*.md'
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- '**/*.md'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
gate:
|
||||||
|
runs-on: ci-public
|
||||||
|
# Public package → anonymous pull, no credentials needed.
|
||||||
|
container:
|
||||||
|
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: clippy (warnings denied)
|
||||||
|
run: nix develop -c cargo clippy --all-targets -- -D warnings
|
||||||
|
- name: test
|
||||||
|
run: nix develop -c cargo test --no-fail-fast
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# macOS release leg — the two *-apple-darwin binaries, built natively on the
|
||||||
|
# Tart (Apple-Silicon) runner and attached to an existing Gitea release.
|
||||||
|
#
|
||||||
|
# Manual dispatch only: the Mac runner is intermittent, so this is triggered by
|
||||||
|
# hand (with the Mac up) for a given release tag. The 4-target Linux/Windows
|
||||||
|
# release (release.yaml) runs on the tag itself and never waits on the Mac, so a
|
||||||
|
# release always has those four; the macOS two are added by dispatching this.
|
||||||
|
#
|
||||||
|
# NOTE: Gitea exposes workflow_dispatch only for workflows on the DEFAULT branch,
|
||||||
|
# so this becomes triggerable once the CI work is merged to `main`.
|
||||||
|
name: release-macos
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Release tag to build the macOS binaries for and attach to (e.g. v0.1.0)'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-macos:
|
||||||
|
runs-on: macos
|
||||||
|
env:
|
||||||
|
NIX_CONFIG: "experimental-features = nix-command flakes"
|
||||||
|
TAG: ${{ inputs.tag }}
|
||||||
|
# Auto-provided by Gitea Actions; has repo write (release) scope.
|
||||||
|
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
API: ${{ github.server_url }}/api/v1
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.tag }}
|
||||||
|
|
||||||
|
- name: test
|
||||||
|
run: nix develop -c cargo test --no-fail-fast
|
||||||
|
|
||||||
|
- name: build, de-nix, sign, package + publish
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
mkdir -p dist
|
||||||
|
for t in aarch64-apple-darwin x86_64-apple-darwin; do
|
||||||
|
echo "==================== $t ===================="
|
||||||
|
nix develop -c cargo build --release --target "$t"
|
||||||
|
f="target/$t/release/rdbms-playground"
|
||||||
|
|
||||||
|
# Rewrite the nix-store libiconv load path to the system one, then
|
||||||
|
# re-sign ad-hoc (install_name_tool invalidates the signature; arm64
|
||||||
|
# requires a valid one). Guard against any remaining /nix/store dep.
|
||||||
|
for l in $(otool -L "$f" | awk '/\/nix\/store.*libiconv.*dylib/ {print $1}'); do
|
||||||
|
install_name_tool -change "$l" /usr/lib/libiconv.2.dylib "$f"
|
||||||
|
done
|
||||||
|
codesign --force --sign - "$f"
|
||||||
|
if otool -L "$f" | grep -q /nix/store; then
|
||||||
|
echo "ERROR: $t binary links a /nix/store dylib"; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
out="rdbms-playground-$TAG-$t"
|
||||||
|
cp "$f" "dist/$out"
|
||||||
|
( cd dist && shasum -a 256 "$out" > "$out.sha256" ) # macOS: shasum, not sha256sum
|
||||||
|
done
|
||||||
|
ls -l dist
|
||||||
|
|
||||||
|
# Idempotent create-or-get the release (release.yaml likely created it
|
||||||
|
# already from the tag), then upload the two macOS binaries + checksums.
|
||||||
|
created=$(curl -sS -X POST "$API/repos/$REPO/releases" \
|
||||||
|
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"Automated release for $TAG.\"}")
|
||||||
|
id=$(printf '%s' "$created" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const o=JSON.parse(s);process.stdout.write(String(o.id||""))}catch(e){}})')
|
||||||
|
if [ -z "$id" ]; then
|
||||||
|
id=$(curl -sS "$API/repos/$REPO/releases/tags/$TAG" \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{process.stdout.write(String(JSON.parse(s).id))})')
|
||||||
|
fi
|
||||||
|
echo "release id: $id"
|
||||||
|
for fa in dist/*; do
|
||||||
|
name=$(basename "$fa")
|
||||||
|
echo "uploading $name"
|
||||||
|
curl -sS -X POST "$API/repos/$REPO/releases/$id/assets?name=$name" \
|
||||||
|
-H "Authorization: token $TOKEN" -F "attachment=@$fa" > /dev/null
|
||||||
|
done
|
||||||
|
echo "published macOS assets for $TAG"
|
||||||
|
|
||||||
|
- name: prune nix store — keep the last 2 toolchain generations
|
||||||
|
# The runner wipes the workspace each run, so cargo target/ never
|
||||||
|
# accumulates. Bound the persistent nix store by generation: record the
|
||||||
|
# current devShell as a generation of a persistent profile (in $HOME),
|
||||||
|
# keep the 2 newest, reclaim what older ones referenced.
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "--- disk before ---"; df -h / | tail -1
|
||||||
|
P="$HOME/.cache/rdbms-ci/toolchain"
|
||||||
|
nix develop --profile "$P" -c true || true
|
||||||
|
nix-env -p "$P" --delete-generations +2 || true
|
||||||
|
nix-collect-garbage || true
|
||||||
|
echo "--- disk after ---"; df -h / | tail -1
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Release: on a version tag, build the cross-platform binaries and publish them
|
||||||
|
# to a Gitea release with checksums. Runs in the prebuilt CI image, so the
|
||||||
|
# pinned toolchain + the release targets + cargo-zigbuild/zig are already warm.
|
||||||
|
#
|
||||||
|
# Matrix (D1, cross-built from Linux x86_64 via cargo-zigbuild):
|
||||||
|
# x86_64-unknown-linux-musl aarch64-unknown-linux-musl (static, D2)
|
||||||
|
# x86_64-pc-windows-gnu aarch64-pc-windows-gnullvm (standalone .exe)
|
||||||
|
# macOS is deferred — its arboard/AppKit link needs Apple's SDK (see ADR-ci-001).
|
||||||
|
# D3 package-manager manifests layer on later.
|
||||||
|
#
|
||||||
|
# Tests run once (host) before the matrix, so a tag can never publish untested
|
||||||
|
# code, even one pointing at a commit that was never gated on a branch.
|
||||||
|
name: release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ci-public
|
||||||
|
container:
|
||||||
|
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: test
|
||||||
|
run: nix develop -c cargo test --no-fail-fast
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: test
|
||||||
|
runs-on: ci-public
|
||||||
|
container:
|
||||||
|
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
target:
|
||||||
|
- x86_64-unknown-linux-musl
|
||||||
|
- aarch64-unknown-linux-musl
|
||||||
|
- x86_64-pc-windows-gnu
|
||||||
|
- aarch64-pc-windows-gnullvm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
run: nix develop -c cargo zigbuild --release --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: package + publish
|
||||||
|
# Pin bash: the runner defaults scripted steps to dash, which rejects
|
||||||
|
# `set -o pipefail`. bash is in the CI image.
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
TARGET: ${{ matrix.target }}
|
||||||
|
# GITEA_TOKEN is auto-provided with repo write (release) scope.
|
||||||
|
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
API: ${{ github.server_url }}/api/v1
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
TAG: ${{ github.ref_name }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Windows targets produce a .exe; the rest a bare binary.
|
||||||
|
case "$TARGET" in *windows*) EXT=.exe ;; *) EXT= ;; esac
|
||||||
|
BIN="target/$TARGET/release/rdbms-playground$EXT"
|
||||||
|
OUT="rdbms-playground-$TAG-$TARGET$EXT"
|
||||||
|
mkdir -p dist
|
||||||
|
cp "$BIN" "dist/$OUT"
|
||||||
|
( cd dist && sha256sum "$OUT" > "$OUT.sha256" )
|
||||||
|
ls -l dist
|
||||||
|
|
||||||
|
# Create the release for this tag; if a sibling matrix job already
|
||||||
|
# created it, look it up instead (idempotent + race-tolerant).
|
||||||
|
created=$(curl -sS -X POST "$API/repos/$REPO/releases" \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"Automated release for $TAG.\"}")
|
||||||
|
id=$(printf '%s' "$created" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const o=JSON.parse(s);process.stdout.write(String(o.id||""))}catch(e){}})')
|
||||||
|
if [ -z "$id" ]; then
|
||||||
|
id=$(curl -sS "$API/repos/$REPO/releases/tags/$TAG" \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{process.stdout.write(String(JSON.parse(s).id))})')
|
||||||
|
fi
|
||||||
|
echo "release id: $id"
|
||||||
|
|
||||||
|
for f in dist/*; do
|
||||||
|
name=$(basename "$f")
|
||||||
|
echo "uploading $name"
|
||||||
|
curl -sS -X POST "$API/repos/$REPO/releases/$id/assets?name=$name" \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-F "attachment=@$f" > /dev/null
|
||||||
|
done
|
||||||
|
echo "published $TARGET assets for $TAG"
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# Nix
|
||||||
|
# `nix build` output symlinks (`result`, `result-<name>`), direnv's cached env
|
||||||
|
/result
|
||||||
|
/result-*
|
||||||
|
.direnv/
|
||||||
|
|
||||||
# Snapshot test review files
|
# Snapshot test review files
|
||||||
*.snap.new
|
*.snap.new
|
||||||
*.pending-snap
|
*.pending-snap
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ tempfile = "3.27.0"
|
|||||||
incremental = false
|
incremental = false
|
||||||
debug = "line-tables-only"
|
debug = "line-tables-only"
|
||||||
|
|
||||||
|
# Release builds back the distributed binaries (D2: single static binary).
|
||||||
|
# strip = "symbols" drops the symbol table at link time so the shipped artifact
|
||||||
|
# is lean (≈13 MB → 10 MB for the musl build) without a separate strip step.
|
||||||
|
[profile.release]
|
||||||
|
strip = "symbols"
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
unsafe_code = "forbid"
|
unsafe_code = "forbid"
|
||||||
unreachable_pub = "warn"
|
unreachable_pub = "warn"
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# `ci/winstub/` — empty Windows import-lib stub
|
||||||
|
|
||||||
|
`libsynchronization.a` here is an **empty `ar` archive** (8 bytes: `!<arch>\n`),
|
||||||
|
referenced by `.cargo/config.toml` via `-L native=ci/winstub` for the Windows
|
||||||
|
release targets.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The D1 release matrix cross-compiles Windows binaries from Linux with
|
||||||
|
`cargo zigbuild` (see `docs/ci/adr/`). Rust's `std` links `-lsynchronization`
|
||||||
|
for its `WaitOnAddress`-based thread parking. That import library is normally
|
||||||
|
provided by Rust's `rust-mingw` "self-contained" component — which `rust-overlay`
|
||||||
|
does not ship — and Zig's bundled mingw doesn't carry it either, so the link
|
||||||
|
fails with:
|
||||||
|
|
||||||
|
```
|
||||||
|
error: unable to find dynamic system library 'synchronization'
|
||||||
|
```
|
||||||
|
|
||||||
|
The functions it would import (`WaitOnAddress`, `WakeByAddressSingle`,
|
||||||
|
`WakeByAddressAll`) are **forwarded by `kernel32.dll`**, which is already linked,
|
||||||
|
so they resolve at link and run time without a real `synchronization` import
|
||||||
|
library. An **empty** stub is therefore sufficient: it satisfies the `-l`
|
||||||
|
lookup and contributes no symbols.
|
||||||
|
|
||||||
|
## Regenerating
|
||||||
|
|
||||||
|
```
|
||||||
|
zig ar rcs ci/winstub/libsynchronization.a
|
||||||
|
```
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
!<arch>
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
# ADR-ci-001: CI + release pipeline on Gitea Actions
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
**Accepted (2026-06-12); implemented the same day on the `ci` branch.** Every
|
||||||
|
fork below was settled with the user as the pipeline was built, and each stage
|
||||||
|
was verified live before acceptance:
|
||||||
|
|
||||||
|
- a throwaway probe workflow established how the runner executes jobs;
|
||||||
|
- the CI image was built and checked locally (runner contract, warm devShell);
|
||||||
|
- the gate ran green (**clippy clean; 2424 tests pass / 0 fail / 1 intentional
|
||||||
|
ignored doctest**);
|
||||||
|
- the release was exercised end-to-end — tag `v0.0.0-citest2` published a Gitea
|
||||||
|
release carrying the static binary (~10 MB) and its `.sha256`.
|
||||||
|
|
||||||
|
This ADR records the **CI/release pipeline**. The **dev/build environment it
|
||||||
|
runs on** — the nix flake (devShell + reproducible build, pinned Rust 1.95.0)
|
||||||
|
— is **ADR-ci-002** (relocated here from main's ADR-0049); this ADR builds on
|
||||||
|
it rather than restating it.
|
||||||
|
|
||||||
|
> **Namespacing.** Kept in `docs/ci/adr/` (id `ADR-ci-001`), disjoint from
|
||||||
|
> `main`'s integer ADR sequence, mirroring the website subproject's
|
||||||
|
> `docs/website/adr/`. This avoids the cross-branch number collisions that
|
||||||
|
> previously forced website ADRs to be renumbered (see that namespace's
|
||||||
|
> history note and ADR-0000 "Numbering discipline").
|
||||||
|
|
||||||
|
## Amendment — 2026-06-13: D1 matrix (non-macOS)
|
||||||
|
|
||||||
|
§3 (Release) below describes the original **single-target** (x86_64 Linux) job.
|
||||||
|
The release is now a **`test` → `build` matrix** over the four non-macOS D1
|
||||||
|
targets (Linux + Windows × x86_64/aarch64), cross-built with `cargo-zigbuild`.
|
||||||
|
The full decision — tooling, targets, the Windows `synchronization` stub, the
|
||||||
|
matrix shape, and the macOS deferral with its licensing rationale — is recorded
|
||||||
|
in its own record: **[ADR-ci-003](20260613-adr-ci-003.md)**.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The project is near feature-complete and needs CI (`requirements.md` **TT5**;
|
||||||
|
the **CI** item in the deferred list) and a release path for its distributed
|
||||||
|
binaries (**D1**/**D2**/**D3**). The self-hosted Gitea instance
|
||||||
|
(`git.lazyeval.net`) has its Actions runner freshly set up — a first-time
|
||||||
|
in-anger use — with a DinD-capable setup and a reusable `docker-build`
|
||||||
|
template, exercised by a handful of sample workflows.
|
||||||
|
|
||||||
|
The starting constraints, and what the probe found:
|
||||||
|
|
||||||
|
- The runner label is **`ci-public`**. A throwaway probe
|
||||||
|
(`ci-probe.yaml`, since removed) established that **jobs run *inside* a
|
||||||
|
container** — `ghcr.io/catthehacker/ubuntu:act-22.04` by default, as **root**
|
||||||
|
— and therefore the runner *host's* nix is **not** on the steps' PATH
|
||||||
|
(`nix NOT on PATH`, `no /nix`). A custom job `container:` *can* be pulled
|
||||||
|
(it pulled `nixos/nix:latest`), but the runner keeps job containers alive
|
||||||
|
with `entrypoint: /bin/sleep` and runs JS actions (e.g. `actions/checkout`)
|
||||||
|
with `node`, so the container must provide **`sleep` + `bash` + `node`** —
|
||||||
|
a bare `nixos/nix` image has none and fails to start.
|
||||||
|
- The reusable template only does `docker build`; it neither runs a Rust gate
|
||||||
|
nor pushes images nor uploads release assets — so a Rust pipeline can't just
|
||||||
|
call it.
|
||||||
|
- The whole motivation (per the user) is for CI to use the project's **nix
|
||||||
|
flake** for its tools rather than relying on whatever the build machine has
|
||||||
|
— i.e. **one toolchain definition shared by dev and CI**.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### 1. Toolchain delivery — a baked nix CI image
|
||||||
|
|
||||||
|
CI gets its toolchain from a **purpose-built job-container image**, not from
|
||||||
|
host nix and not by installing nix per-job:
|
||||||
|
|
||||||
|
- **Base `node:22-bookworm-slim`.** Debian slim already provides `bash` +
|
||||||
|
coreutils (`sleep`); the `node` tag adds the actions runtime. This satisfies
|
||||||
|
the act_runner job-container contract at a fraction of the size of the
|
||||||
|
catthehacker runner images (chosen on the user's prompt to avoid those
|
||||||
|
multi-GB images), and far more reliably than a bare `nixos/nix` (which can't
|
||||||
|
start). `.gitea/ci-image/Dockerfile`.
|
||||||
|
- **Single-user nix on top**, flakes enabled, with the **flake's devShell
|
||||||
|
pre-warmed** (`nix develop` realizes nixpkgs + the pinned Rust toolchain +
|
||||||
|
`cargo-sweep` + the musl cc into the store). CI then runs `nix develop -c …`
|
||||||
|
against a warm store — the *same* pinned toolchain as dev (ADR-ci-002),
|
||||||
|
reaching a ready toolchain in ~1.4 s.
|
||||||
|
- **Built + pushed by `build-ci-image.yaml`** via the DinD service to the
|
||||||
|
Gitea container registry as `git.lazyeval.net/<owner>/rdbms-playground-ci`,
|
||||||
|
a **public** package (anonymous pull, no gate-side credentials). It runs only
|
||||||
|
when an image input changes (Dockerfile / `flake.nix` / `flake.lock` /
|
||||||
|
`rust-toolchain.toml`) or on manual dispatch.
|
||||||
|
|
||||||
|
### 2. Gate — `ci.yaml`
|
||||||
|
|
||||||
|
On branch pushes and PRs, a single job runs **inside the CI image**:
|
||||||
|
`nix develop -c cargo clippy --all-targets -- -D warnings` then
|
||||||
|
`nix develop -c cargo test --no-fail-fast`.
|
||||||
|
|
||||||
|
**`fmt` is deliberately not gated.** The tree isn't clean under stock
|
||||||
|
`rustfmt` (~100 files would change; no `rustfmt.toml` is committed) and
|
||||||
|
reformatting would churn blame across the in-flight website branch and ongoing
|
||||||
|
`main` work — so, by user decision, the gate is **clippy + test** and fmt is
|
||||||
|
revisited on `main` (also recorded in ADR-ci-002).
|
||||||
|
|
||||||
|
### 3. Release — `release.yaml`
|
||||||
|
|
||||||
|
On a `v*` tag, one job in the CI image:
|
||||||
|
|
||||||
|
1. **tests** (`cargo test`) — so a tag can never publish untested code, even
|
||||||
|
one pointing at a never-gated commit (user choice over relying solely on the
|
||||||
|
branch gate);
|
||||||
|
2. **builds the static binary** for **`x86_64-unknown-linux-musl`** (D2:
|
||||||
|
single static binary, no runtime deps). The glibc/nix-store build is
|
||||||
|
non-portable; the musl target with `crt-static` is fully static. rusqlite's
|
||||||
|
`bundled` SQLite C is compiled by a **musl `cc`** (`pkgsCross.musl64`) wired
|
||||||
|
into the flake devShell via `CC_<target>` + `CARGO_TARGET_<TARGET>_LINKER`;
|
||||||
|
`[profile.release] strip = "symbols"` trims it (~13 MB → ~10 MB);
|
||||||
|
3. **publishes** the binary + a `.sha256` to a Gitea release via the API and
|
||||||
|
the auto-provided **`GITEA_TOKEN`** — no third-party action (just `curl` +
|
||||||
|
`node`, both in the image).
|
||||||
|
|
||||||
|
### 4. Triggers — branch vs tag hygiene
|
||||||
|
|
||||||
|
- Gate and image-build are scoped to **branch** pushes (`branches: ['**']`).
|
||||||
|
Tag pushes ignore `paths:` filters and would otherwise spuriously rebuild the
|
||||||
|
unchanged image and re-gate an already-gated commit; the branch filter
|
||||||
|
excludes tags. **`release.yaml` owns tags** (`tags: ['v*']`).
|
||||||
|
- Pushing commits + a tag together still gates the commits (via the branch
|
||||||
|
ref) and releases (via the tag ref) — no lost coverage, no duplicate runs.
|
||||||
|
|
||||||
|
### 5. Auth
|
||||||
|
|
||||||
|
- **Image push:** a dedicated PAT with `write:package`, supplied as the
|
||||||
|
`REGISTRY_USERNAME` / `REGISTRY_TOKEN` Actions secrets (the package owner
|
||||||
|
must match the token's user — an `oli`-namespace push with a different user
|
||||||
|
is refused with `reqPackageAccess`).
|
||||||
|
- **Release publish:** the auto `GITEA_TOKEN` (repo/release scope).
|
||||||
|
|
||||||
|
### 6. Scope this iteration — Linux x86_64, step by step
|
||||||
|
|
||||||
|
The user's target is the full **D1** matrix, approached incrementally. This
|
||||||
|
iteration ships **Linux x86_64 only**; the rest is deferred (below).
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- **One toolchain, dev and CI.** They build through the same flake and cannot
|
||||||
|
drift. New image rebuilds only when the flake/toolchain/Dockerfile change.
|
||||||
|
- **D2 is met on Linux.** The release artifact is a genuinely static,
|
||||||
|
stripped musl binary that runs with no runtime dependencies.
|
||||||
|
- **DinD is per-job (no layer cache across runs),** so every `build-ci-image`
|
||||||
|
run rebuilds from scratch (~6 min). Acceptable at its trigger frequency;
|
||||||
|
base-pull caching via the `dind-cached` proxy variant is a possible later
|
||||||
|
optimisation.
|
||||||
|
- **The CI image is ~5.5 GB+** (the Rust toolchain closure, now also musl).
|
||||||
|
Pulled once per runner and cached; slimming (multi-stage, prune) is optional.
|
||||||
|
- **Every gate run recompiles the full dependency graph** (warm *toolchain*,
|
||||||
|
cold *deps*; clippy and test don't share artifacts), ~2 min total. Fine for
|
||||||
|
now; dependency/`target` caching is a deferred speed item.
|
||||||
|
- **`GITEA_TOKEN` must retain release scope;** if an instance policy narrows
|
||||||
|
it, the release publish falls back to a repo-scoped PAT secret.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
- **Run on the runner host's nix.** Rejected — the probe showed steps run in a
|
||||||
|
container where host nix is unreachable.
|
||||||
|
- **Install nix per-job in the default image.** Works but cold every run
|
||||||
|
(slow) and throwaway once the image exists; rejected in favour of the baked
|
||||||
|
image.
|
||||||
|
- **`catthehacker` or bare `nixos/nix` as the base.** catthehacker is a
|
||||||
|
multi-GB runner emulation we don't need; bare `nixos/nix` lacks
|
||||||
|
`sleep`/`bash`/`node` and won't start. `node:22-bookworm-slim` is the small,
|
||||||
|
contract-satisfying middle (user's suggestion).
|
||||||
|
- **A standard `rust:1.95` CI image instead of the flake.** Simpler in CI but a
|
||||||
|
*second* toolchain definition (drift) — counter to the unify-with-dev goal.
|
||||||
|
- **A third-party Gitea release action.** Avoided; the API + auto token keep
|
||||||
|
the release self-contained and debuggable.
|
||||||
|
|
||||||
|
## Deferred / out of scope (tracked, step by step)
|
||||||
|
|
||||||
|
- **D1 matrix:** **macOS only** now (x86_64 + aarch64). The four non-macOS
|
||||||
|
targets shipped via cargo-zigbuild (see the 2026-06-13 amendment); macOS needs
|
||||||
|
Apple's SDK (osxcross + private SDK, or a Mac runner).
|
||||||
|
- **D3 packaging:** Homebrew / Scoop / winget / `cargo-binstall` manifests
|
||||||
|
(and binstall-friendly asset naming/archives).
|
||||||
|
- **Tier 4 (PTY E2E):** still unwired (`requirements.md` **TT4**); the gate runs
|
||||||
|
tiers 1–3 only, so **TT5** ("CI runs all tiers on Linux/macOS/Windows") is
|
||||||
|
partially met — Linux, tiers 1–3.
|
||||||
|
- **CI speed:** dependency/`target` caching (cargo-chef into the image, or
|
||||||
|
`actions/cache`), and image slimming / `dind-cached` base-pull caching.
|
||||||
|
- **Website deploy:** the static site → Cloudflare via Gitea Actions (a
|
||||||
|
separate, simpler workflow on the website branch).
|
||||||
|
- **fmt gate:** revisit on `main` once a `rustfmt` style is chosen.
|
||||||
|
|
||||||
|
## Relationship to other decisions
|
||||||
|
|
||||||
|
- **Builds on ADR-ci-002** (nix flake dev + build env). This ADR adds the
|
||||||
|
musl-target/cc to that flake and consumes it from CI.
|
||||||
|
- **Advances `requirements.md`:** **TT5** (CI runs the tiers — Linux, 1–3),
|
||||||
|
**D2** (static binary — Linux, done), **D1**/**D3** (partial/deferred).
|
||||||
|
- **Mirrors the website subproject's** separate ADR namespace and its
|
||||||
|
static→Cloudflare-via-Gitea-Actions deployment posture (ADR-website-001).
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
# 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).
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
# ADR-ci-003: Cross-platform release builds (the D1 matrix)
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
**Accepted (2026-06-13); implemented the same day on the `ci` branch.** Every
|
||||||
|
fork was settled with the user. Verified end-to-end:
|
||||||
|
|
||||||
|
- all four targets cross-build locally from Linux x86_64;
|
||||||
|
- the Linux binaries are statically linked (D2); the Windows artifacts are
|
||||||
|
valid PE32+ (x86-64 / Aarch64);
|
||||||
|
- a real release-matrix run (tag `v.0.0.0-citest3`) published **8 assets** — the
|
||||||
|
four binaries + a `.sha256` each.
|
||||||
|
|
||||||
|
**Runtime-verified (2026-06-13, by the user):** the **Linux x86_64** and
|
||||||
|
**Windows aarch64** binaries launch and run correctly — one of each OS family
|
||||||
|
and both architectures. The remaining two (**Linux aarch64**, **Windows
|
||||||
|
x86_64**) are link-clean and valid format but not yet runtime smoke-tested.
|
||||||
|
|
||||||
|
This ADR records the **cross-platform build strategy**; it sits on top of
|
||||||
|
**ADR-ci-002** (the nix flake, which now carries the cross toolchain) and
|
||||||
|
**ADR-ci-001** (the pipeline, whose release job this fills in).
|
||||||
|
|
||||||
|
## Amendment — 2026-06-14: macOS implemented (closes D1)
|
||||||
|
|
||||||
|
macOS is no longer deferred. The two `*-apple-darwin` targets now build on a
|
||||||
|
**Tart (Apple-Silicon) macOS runner** registered to Gitea — building on **real
|
||||||
|
Apple hardware** makes the SDK fully licensed, so the whole osxcross / SDK
|
||||||
|
grey-area + public-image-redistribution problem (§5 below) simply **does not
|
||||||
|
arise**. With all six D1 targets producing artifacts, **D1 is complete.**
|
||||||
|
|
||||||
|
Details, all verified on the runner via a throwaway smoke-test before wiring the
|
||||||
|
release leg:
|
||||||
|
|
||||||
|
- **`release-macos.yaml`** — `workflow_dispatch` with a `tag` input,
|
||||||
|
`runs-on: macos`. The runner registered as `macos:host`, but `:host` is
|
||||||
|
act_runner's execution-backend schema (run on host, no container), **not** part
|
||||||
|
of the label, so the label is `macos`. Steps: `cargo test` (macOS gets the only
|
||||||
|
automated test coverage outside the Linux gate — user choice) → build both
|
||||||
|
darwin targets natively through the flake (`apple-sdk` added to the devShell so
|
||||||
|
the toolchain links AppKit) → **upload to the same release** via the idempotent
|
||||||
|
create-or-get.
|
||||||
|
- **De-nix + re-sign.** The darwin stdenv bakes a `/nix/store` `libiconv` load
|
||||||
|
path into the binary (the *only* non-system dependency; everything else is
|
||||||
|
AppKit/Foundation/CoreGraphics/IOKit + `libSystem`/`libobjc`). The release step
|
||||||
|
rewrites it to `/usr/lib/libiconv.2.dylib` with `install_name_tool` and
|
||||||
|
**re-signs ad-hoc** (`codesign -f -s -`) — `install_name_tool` invalidates the
|
||||||
|
signature and Apple Silicon refuses an unsigned binary. A guard fails the build
|
||||||
|
if any `/nix/store` path remains. Result: portable, signed binaries (the native
|
||||||
|
one was confirmed to launch).
|
||||||
|
- **Dispatch-only, intermittent runner.** The Mac isn't always on, so macOS is a
|
||||||
|
separate dispatched workflow (not a job in `release.yaml`) — a release always
|
||||||
|
carries the four Linux/Windows assets regardless of the Mac, and the two macOS
|
||||||
|
assets are added by dispatching `release-macos` for that tag. **Caveat:** Gitea
|
||||||
|
exposes `workflow_dispatch` only for workflows on the **default branch**, so
|
||||||
|
`release-macos` becomes triggerable once the CI work is merged to `main`.
|
||||||
|
- **Cache hygiene (host-execution runner).** The runner wipes the workspace each
|
||||||
|
run, so cargo `target/` never accumulates; the persistent cache is the nix
|
||||||
|
store, bounded by **generation** — record the current devShell in a persistent
|
||||||
|
profile, keep the 2 newest generations (`nix-env --delete-generations +2`),
|
||||||
|
reclaim the rest. (The first sweep reclaimed a ~3.8 GB one-time backlog of
|
||||||
|
build scaffolding — source + build-only deps, not re-installed toolchains.)
|
||||||
|
- **D2 on macOS.** macOS binaries cannot be fully static (`libSystem` is always
|
||||||
|
dynamic); "no runtime deps" there means *system libraries only*, which the
|
||||||
|
de-nix step guarantees.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
`requirements.md` **D1** asks for binaries on **Linux, macOS, Windows × x86_64
|
||||||
|
and aarch64** (six targets); **D2** asks for a **single static binary, no
|
||||||
|
runtime deps**. The CI runner executes jobs in a **Linux x86_64** container
|
||||||
|
(ADR-ci-001), so every target is **cross-compiled from Linux**.
|
||||||
|
|
||||||
|
What's feasible is decided almost entirely by one dependency — **`arboard`**
|
||||||
|
(the clipboard backend for the `copy` command). Its per-platform backends in
|
||||||
|
`Cargo.lock`:
|
||||||
|
|
||||||
|
| Target family | arboard backend | Needs a platform SDK to cross-link? |
|
||||||
|
|---|---|---|
|
||||||
|
| Linux x86_64 / aarch64 | `x11rb` (pure Rust) | No |
|
||||||
|
| Windows x86_64 / aarch64 | `clipboard-win` + `windows-sys` (import libs bundled) | No |
|
||||||
|
| **macOS x86_64 / aarch64** | **`objc2-app-kit` → links AppKit** | **Yes — Apple's SDK** |
|
||||||
|
|
||||||
|
So **four targets cross-compile with no SDK**; **macOS is the hard wall** —
|
||||||
|
AppKit can only be linked against Apple's SDK.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### 1. Tooling — `cargo-zigbuild`
|
||||||
|
|
||||||
|
Cross-compile with **`cargo-zigbuild`** (Zig's bundled clang + libc as a single
|
||||||
|
universal cross `cc`/linker), added to the flake devShell alongside `zig`. One
|
||||||
|
tool serves every non-macOS target, **including the `cc`-crate compile of
|
||||||
|
rusqlite's bundled SQLite C**, with no per-target toolchain. It replaced the
|
||||||
|
earlier single-target musl `cc` (ADR-ci-002's first cut).
|
||||||
|
|
||||||
|
### 2. Targets this iteration — the four non-macOS
|
||||||
|
|
||||||
|
Added to `rust-toolchain.toml` and the release matrix:
|
||||||
|
|
||||||
|
- **`x86_64-unknown-linux-musl`**, **`aarch64-unknown-linux-musl`** — musl +
|
||||||
|
`crt-static`, so **fully static** portable binaries (D2);
|
||||||
|
- **`x86_64-pc-windows-gnu`**, **`aarch64-pc-windows-gnullvm`** — Zig statically
|
||||||
|
links its libc, so the `.exe` is **standalone** (no mingw runtime DLLs).
|
||||||
|
|
||||||
|
### 3. The Windows `synchronization` stub
|
||||||
|
|
||||||
|
Rust's `std` links **`-lsynchronization`** (its `WaitOnAddress`-based thread
|
||||||
|
parking). That import library is normally supplied by Rust's `rust-mingw`
|
||||||
|
"self-contained" component — which **rust-overlay does not ship** — and Zig's
|
||||||
|
mingw doesn't carry it either, so the link fails with *"unable to find dynamic
|
||||||
|
system library 'synchronization'"*. The functions (`WaitOnAddress`,
|
||||||
|
`WakeByAddress*`) are **forwarded by `kernel32`** (already linked), so an
|
||||||
|
**empty stub** `libsynchronization.a` (committed at **`ci/winstub/`**, 8 bytes,
|
||||||
|
wired via **`.cargo/config.toml`** for the Windows targets *only*) satisfies the
|
||||||
|
linker without contributing symbols. Host and Linux builds are untouched by it.
|
||||||
|
|
||||||
|
### 4. Workflow shape — test once, then a build matrix
|
||||||
|
|
||||||
|
`release.yaml` is **`test` → `build`**:
|
||||||
|
|
||||||
|
- **`test`** runs once on the host (`cargo test`) — a tag never publishes
|
||||||
|
untested code;
|
||||||
|
- **`build`** is a **matrix over the four targets** (`needs: test`,
|
||||||
|
`fail-fast: false`), each `cargo zigbuild --release --target <triple>`, then
|
||||||
|
packages the binary (`.exe` for Windows) + a `.sha256` and uploads both to the
|
||||||
|
**shared release** via an **idempotent create-or-get** (the first matrix job
|
||||||
|
creates the release; the rest fetch it).
|
||||||
|
|
||||||
|
### 5. macOS — deferred, with rationale
|
||||||
|
|
||||||
|
macOS is **not** in this iteration. `arboard`→AppKit needs the macOS SDK, and:
|
||||||
|
|
||||||
|
- the SDK ships **only inside Xcode**; Apple's license ties its use to
|
||||||
|
**Apple-branded hardware**, so using it on a Linux runner is a **grey area**
|
||||||
|
(widely done, low enforcement, but technically against the terms);
|
||||||
|
- **redistributing** the SDK is a clearer violation — and our **CI image is
|
||||||
|
public**, so the SDK **cannot be baked into it** even if the grey area were
|
||||||
|
accepted; it would have to live in a private store;
|
||||||
|
- the **clean** path is building on **real Apple hardware** (a Mac registered as
|
||||||
|
a Gitea runner, or hosted Mac CI), where the SDK is fully licensed.
|
||||||
|
|
||||||
|
macOS therefore becomes its **own step**, choosing between **(a)** osxcross + a
|
||||||
|
**private** SDK kept out of the public image, or **(b)** a **Mac runner**. The
|
||||||
|
user decides when we get there.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- **D1: four of six targets met** from a single Linux runner; **D2 met on
|
||||||
|
Linux** (static musl). Windows `.exe`s are standalone.
|
||||||
|
- **Runtime coverage:** Linux x86_64 + Windows aarch64 confirmed running
|
||||||
|
(user, 2026-06-13); Linux aarch64 + Windows x86_64 are the outstanding
|
||||||
|
runtime checks.
|
||||||
|
- **Each matrix target recompiles from scratch** (~2–4 min; ~10 min total on the
|
||||||
|
single runner), and Zig's per-target libc cache is cold each run. Fine at
|
||||||
|
release frequency; cacheable later if it matters.
|
||||||
|
- **The empty stub depends on `kernel32` forwarding `WaitOnAddress`** (true on
|
||||||
|
Windows 8+), which covers every supported target.
|
||||||
|
- **Asset naming** `rdbms-playground-<tag>-<target>[.exe]` is close to what
|
||||||
|
`cargo-binstall` / the D3 package managers will want.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
- **`cross` (cross-rs).** Docker-image-per-target; covers Linux + Windows but
|
||||||
|
**not macOS** (no legally redistributable Apple images), and needs DinD
|
||||||
|
orchestration inside our job. Rejected — no macOS, more moving parts than
|
||||||
|
zigbuild.
|
||||||
|
- **Per-target nix cross (`pkgsCross`).** Clean for Linux-musl and
|
||||||
|
Windows-x86_64 (mingw-w64, which *does* ship `libsynchronization.a`), but
|
||||||
|
Windows-aarch64 isn't readily packaged and **macOS-from-Linux is unsupported**
|
||||||
|
in nixpkgs. Rejected — incomplete.
|
||||||
|
- **Native runners per OS.** Cleanest for macOS/Windows, but needs mac/windows
|
||||||
|
runners we don't have. Kept on the table specifically for the deferred macOS
|
||||||
|
step.
|
||||||
|
- **A real `libsynchronization.a`** (from nixpkgs mingw or a `rust-mingw`
|
||||||
|
component) instead of the empty stub. More principled, but more flake
|
||||||
|
machinery, doesn't cover Windows-aarch64, and unnecessary — the stub links
|
||||||
|
clean because the symbols resolve via `kernel32`.
|
||||||
|
|
||||||
|
## Deferred / out of scope
|
||||||
|
|
||||||
|
- ~~**macOS** (x86_64 + aarch64)~~ — **done** via the Tart runner (see the
|
||||||
|
2026-06-14 amendment); §5 below is the as-deferred rationale, kept for history.
|
||||||
|
- **D3 packaging** — Homebrew / Scoop / winget / `cargo-binstall` manifests
|
||||||
|
(and binstall-friendly archive naming).
|
||||||
|
- **CI speed** — caching per-target builds / Zig's libc cache.
|
||||||
|
- **Runtime smoke test** of the two not-yet-checked targets (Linux aarch64,
|
||||||
|
Windows x86_64).
|
||||||
|
|
||||||
|
## Relationship to other decisions
|
||||||
|
|
||||||
|
- **Extends ADR-ci-002** — the flake devShell now carries `cargo-zigbuild` +
|
||||||
|
`zig` and the four release targets.
|
||||||
|
- **Fills in ADR-ci-001 §3 (Release)** — that single-target job is now this
|
||||||
|
matrix.
|
||||||
|
- **Advances `requirements.md`** **D1** (4/6) and **D2** (Linux, done).
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# CI / Build Architecture Decision Records
|
||||||
|
|
||||||
|
Decision records for the **continuous-integration + release pipeline**
|
||||||
|
subproject — the Gitea Actions workflows under `.gitea/`, the nix CI image,
|
||||||
|
and the release tooling. These are kept in their own namespace, separate
|
||||||
|
from the project-wide ADRs in [`docs/adr/`](../../adr/README.md), so CI
|
||||||
|
decisions never compete with the main global ADR sequence for numbers — the
|
||||||
|
same split the website subproject uses (`docs/website/adr/`, on the `website`
|
||||||
|
branch), and for the same reason (see
|
||||||
|
[ADR-0000 "Numbering discipline"](../../adr/0000-record-architecture-decisions.md)).
|
||||||
|
|
||||||
|
**Numbering.** Files are named `<date>-adr-ci-<NNN>.md` and referenced in
|
||||||
|
prose as `ADR-ci-NNN`. The `<date>` (the ADR's accepted/created day,
|
||||||
|
`YYYYMMDD`) plus the `ci` segment keeps the namespace disjoint from `main`'s
|
||||||
|
integers. Assign the next free `NNN` from this index. Every ADR change
|
||||||
|
updates this index in the same edit (the ADR-0000 index-upkeep rule applies
|
||||||
|
here too).
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
- [ADR-ci-001 — CI + release pipeline on Gitea Actions](20260612-adr-ci-001.md) — **Accepted 2026-06-12** (implemented the same day on the `ci` branch). Establishes the CI/release pipeline on the self-hosted Gitea instance's Actions runner (`ci-public`). **Runner model** (established by a throwaway probe): jobs execute *inside* a container (`catthehacker/ubuntu:act-22.04` by default), as root, so the runner host's nix is **not** reachable from steps. **Toolchain delivery:** a **baked CI image** — `node:22-bookworm-slim` (satisfies the act_runner job-container contract: `/bin/sleep` keep-alive, `bash`, `node` for JS actions; a bare `nixos/nix` image lacks these and won't start) **+ single-user nix + the flake's devShell pre-warmed** — built by `build-ci-image.yaml` via DinD and pushed to the Gitea container registry as a **public** package, so CI runs `nix develop -c …` against the **same pinned toolchain as dev** (the flake, ADR-ci-002) with a warm store (~1.4 s to a ready toolchain). **Gate** (`ci.yaml`): `clippy -D warnings` + `cargo test` inside that image on branch pushes + PRs; **fmt deliberately not gated** (the tree isn't stock-rustfmt-clean — user decision, revisit on `main`; see ADR-ci-002). **Release** (`release.yaml`): on a `v*` tag, runs the tests, builds the **static `x86_64-unknown-linux-musl` binary** (D2: single static binary, no runtime deps — the glibc/nix build is non-portable), strips it, and publishes it + a `.sha256` to a Gitea release via the API and the auto-provided `GITEA_TOKEN`. **Triggers:** gate + image-build are scoped to **branch** pushes (`branches: ['**']`) so a release tag doesn't spuriously re-run them; the image-build additionally path-filters to its inputs (Dockerfile/flake/toolchain); the release owns tags. **Auth:** a dedicated PAT (`REGISTRY_USERNAME`/`REGISTRY_TOKEN` secrets) pushes the image; the auto `GITEA_TOKEN` publishes releases. **Scope:** the original release job was Linux x86_64 only; it's now the **four non-macOS D1 targets** (Linux + Windows × x86_64/aarch64) cross-built via cargo-zigbuild — see **ADR-ci-003**. macOS, D3 package-manager manifests, CI-speed dependency caching, and the website's static→Cloudflare deploy remain deferred, added step by step. Verified live: probe → runner facts; image built + checked locally; gate green (**2424 tests**); release exercised end-to-end (`v0.0.0-citest2` published with binary + checksum). Builds on **ADR-ci-002** (the nix flake, relocated here from main's ADR-0049 to avoid exactly this cross-branch collision).
|
||||||
|
- [ADR-ci-002 — Nix flake for a reproducible dev + build environment](20260612-adr-ci-002.md) — **Accepted 2026-06-12** (relocated from main's **ADR-0049** on the same day — content unchanged — to keep CI/dev-env decisions out of `main`'s integer sequence). The single, version-pinned declaration of the **dev *and* build toolchain** so CI never relies on whatever Rust is on the build machine — mirroring **datamage ADR 0046**, but far simpler (pure-Rust TUI). Root **Nix flake** with two outputs: **`devShells.default`** (pinned **Rust 1.95.0** via `rust-toolchain.toml` + `rust-overlay`, `cargo-sweep`, and the musl cc for the static release build) and **`packages.default`** (`rustPlatform.buildRustPackage` from the committed `Cargo.lock`; `doCheck = false`). Exact-pin (not floating `stable`) so `nix flake update` can't surprise-bump clippy past the `-D warnings` gate. System inputs near-empty by design (`libsqlite3-sys bundled` → stdenv cc only; `arboard`→`x11rb` pure-Rust). `.envrc` (`use flake`) for direnv parity. Verified through the flake: `nix build` yields a working binary, clippy clean, **2424 tests pass / 0 fail / 1 intentional ignored doctest**. Consumed by **ADR-ci-001** (the pipeline). Alternatives rejected: dev-shell-only; a standard `rust:1.95` CI image (a second toolchain definition = drift); `rustup` on the build host (non-reproducible).
|
||||||
|
- [ADR-ci-003 — Cross-platform release builds (the D1 matrix)](20260613-adr-ci-003.md) — **Accepted 2026-06-13** (implemented + a real matrix release verified the same day — tag `v.0.0.0-citest3` published 8 assets). Cross-compiles the **four non-macOS D1 targets** from the Linux x86_64 runner with **`cargo-zigbuild`** (Zig's bundled clang + libc as one universal cross cc/linker, incl. rusqlite's bundled SQLite C; added to the flake devShell, replacing the single-target musl cc): **`x86_64`/`aarch64-unknown-linux-musl`** (musl + crt-static → fully static, **D2**) and **`x86_64-pc-windows-gnu`** / **`aarch64-pc-windows-gnullvm`** (Zig statically links libc → standalone `.exe`). **Windows `synchronization` stub:** Rust std links `-lsynchronization` (WaitOnAddress thread-parking), an import lib rust-overlay's toolchain doesn't ship and Zig's mingw lacks; the symbols are forwarded by `kernel32`, so an **empty 8-byte stub** `libsynchronization.a` (`ci/winstub/`, wired via `.cargo/config.toml` for the Windows targets only) satisfies the linker. **Workflow:** `release.yaml` = **`test` once (host) → `build` matrix** over the four targets (`needs: test`, `fail-fast: false`); each job packages binary (`.exe` for Windows) + `.sha256` and uploads to the **shared release** via idempotent create-or-get. **macOS** (2026-06-14 amendment) — built natively on a **Tart (Apple-Silicon) runner** (`runs-on: macos`), which makes the SDK fully licensed and dissolves the grey-area/public-image problem; `release-macos.yaml` is **dispatch-only** (intermittent runner; becomes triggerable once CI is on `main`), de-nixes the binary's libiconv load path (`install_name_tool` → `/usr/lib`) + re-signs ad-hoc, and uploads to the tagged release. **D1 complete (all six targets).** Alternatives rejected: `cross` (no macOS, needs DinD), per-target nix cross (Windows-aarch64 unpackaged, macOS-from-Linux unsupported), a real `libsynchronization.a` (more machinery, doesn't cover Windows-aarch64). Runtime-verified by the user (2026-06-13): Linux x86_64 + Windows aarch64 run correctly; Linux aarch64 + Windows x86_64 are the outstanding runtime checks. Builds on ADR-ci-002 (flake) and fills in ADR-ci-001 §3 (Release).
|
||||||
Generated
+82
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
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 {
|
||||||
|
buildInputs = buildInputs ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
|
||||||
|
# macOS release builds (aarch64/x86_64-apple-darwin) link AppKit
|
||||||
|
# (arboard) + libSystem; the Apple SDK provides those framework/
|
||||||
|
# system-lib stubs as *system* paths (/usr/lib, /System/Library).
|
||||||
|
# NOTE: the darwin stdenv still propagates a *nix-store* libiconv and
|
||||||
|
# links it regardless of inputs, so the release workflow rewrites that
|
||||||
|
# one load path to /usr/lib/libiconv.2.dylib (install_name_tool) and
|
||||||
|
# re-signs — see release-macos / the macOS smoke-test. Adding
|
||||||
|
# `pkgs.libiconv` here would only reinforce the wrong path, so don't.
|
||||||
|
pkgs.apple-sdk
|
||||||
|
];
|
||||||
|
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
|
||||||
|
] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [
|
||||||
|
# Cross-compilation for the non-macOS D1 targets: `cargo zigbuild`
|
||||||
|
# uses Zig's bundled clang + libc as one universal cross cc/linker
|
||||||
|
# (incl. the `cc`-crate compile of rusqlite's bundled SQLite C) for
|
||||||
|
# Linux musl + Windows gnu/gnullvm. macOS builds natively with the
|
||||||
|
# Apple toolchain on the Mac runner, so these are Linux-only.
|
||||||
|
pkgs.cargo-zigbuild
|
||||||
|
pkgs.zig
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
echo "RDBMS Playground dev shell ($(uname -s))"
|
||||||
|
echo " rust: $(rustc --version | cut -d' ' -f1-2)"
|
||||||
|
echo " cargo: $(cargo --version | cut -d' ' -f1-2)"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
[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).
|
||||||
|
components = ["rustfmt", "clippy"]
|
||||||
|
# The non-macOS D1 release matrix, all cross-built from Linux x86_64 via
|
||||||
|
# `cargo zigbuild` (D1: cross-platform binaries; D2: single static binary).
|
||||||
|
# Linux uses musl + crt-static for fully static, portable binaries; Windows
|
||||||
|
# uses the gnu/gnullvm ABIs (Zig statically links libc, so the .exe is
|
||||||
|
# standalone). macOS is deferred — its arboard/AppKit link needs Apple's SDK,
|
||||||
|
# which a Linux runner can't supply cleanly (see docs/ci/adr ADR-ci-001).
|
||||||
|
targets = [
|
||||||
|
"x86_64-unknown-linux-musl",
|
||||||
|
"aarch64-unknown-linux-musl",
|
||||||
|
"x86_64-pc-windows-gnu",
|
||||||
|
"aarch64-pc-windows-gnullvm",
|
||||||
|
# macOS — built natively on the Apple-Silicon Mac runner (aarch64 native,
|
||||||
|
# x86_64 cross). These need Apple's SDK to link, which a Linux runner can't
|
||||||
|
# supply, so they are produced only on the Mac (see docs/ci/adr ADR-ci-003).
|
||||||
|
"aarch64-apple-darwin",
|
||||||
|
"x86_64-apple-darwin",
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user