ci: D1 release matrix over the four non-macOS targets
build-ci-image / build (push) Successful in 9m56s
ci / gate (push) Successful in 2m47s
release / test (push) Successful in 2m18s
release / build (aarch64-pc-windows-gnullvm) (push) Successful in 3m31s
release / build (aarch64-unknown-linux-musl) (push) Successful in 3m52s
release / build (x86_64-pc-windows-gnu) (push) Successful in 4m14s
release / build (x86_64-unknown-linux-musl) (push) Successful in 3m25s

release.yaml becomes test (once, host) -> build (matrix) over the four
cargo-zigbuild targets; each matrix job uploads its binary + .sha256 to
the shared release (idempotent create-or-get). Records the expansion in
ADR-ci-001 (2026-06-13 amendment); macOS stays deferred.
This commit is contained in:
claude@clouddev1
2026-06-13 12:14:49 +00:00
parent 04ebd83f08
commit 298475b326
3 changed files with 79 additions and 33 deletions
+46 -30
View File
@@ -1,13 +1,15 @@
# Release: on a version tag, build the static Linux binary (D2) and publish it # Release: on a version tag, build the cross-platform binaries and publish them
# to a Gitea release with a checksum. Runs in the same prebuilt CI image as the # to a Gitea release with checksums. Runs in the prebuilt CI image, so the
# gate, so the pinned toolchain + musl target/cc are already warm. # pinned toolchain + the release targets + cargo-zigbuild/zig are already warm.
# #
# Scope: x86_64-unknown-linux-musl only, for now. The rest of the D1 matrix # Matrix (D1, cross-built from Linux x86_64 via cargo-zigbuild):
# (aarch64, macOS, Windows) and the D3 package-manager manifests layer on later, # x86_64-unknown-linux-musl aarch64-unknown-linux-musl (static, D2)
# step by step. # 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 here before the build so a tag can never publish untested code, # Tests run once (host) before the matrix, so a tag can never publish untested
# even one pointing at a commit that was never gated on a branch. # code, even one pointing at a commit that was never gated on a branch.
name: release name: release
on: on:
push: push:
@@ -15,46 +17,59 @@ on:
- 'v*' - 'v*'
jobs: jobs:
release: test:
runs-on: ci-public runs-on: ci-public
container: container:
image: git.lazyeval.net/oli/rdbms-playground-ci:latest image: git.lazyeval.net/oli/rdbms-playground-ci:latest
env:
TARGET: x86_64-unknown-linux-musl
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: test - name: test
run: nix develop -c cargo test --no-fail-fast run: nix develop -c cargo test --no-fail-fast
- name: build static binary build:
run: nix develop -c cargo build --release --target "$TARGET" 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: package artifacts - 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 # Pin bash: the runner defaults scripted steps to dash, which rejects
# `set -o pipefail`. bash is in the CI image. # `set -o pipefail`. bash is in the CI image.
shell: bash
run: |
set -euo pipefail
BIN="target/$TARGET/release/rdbms-playground"
ls -l "$BIN"
OUT="rdbms-playground-${{ github.ref_name }}-$TARGET"
mkdir -p dist
cp "$BIN" "dist/$OUT"
( cd dist && sha256sum "$OUT" > "$OUT.sha256" )
ls -l dist
- name: publish gitea release + assets
shell: bash shell: bash
env: env:
# Auto-provided by Gitea Actions; has repo write (release) scope. TARGET: ${{ matrix.target }}
# GITEA_TOKEN is auto-provided with repo write (release) scope.
TOKEN: ${{ secrets.GITEA_TOKEN }} TOKEN: ${{ secrets.GITEA_TOKEN }}
API: ${{ github.server_url }}/api/v1 API: ${{ github.server_url }}/api/v1
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }} TAG: ${{ github.ref_name }}
run: | run: |
set -euo pipefail set -euo pipefail
# Create the release for this tag; if it already exists, look it up.
# 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" \ created=$(curl -sS -X POST "$API/repos/$REPO/releases" \
-H "Authorization: token $TOKEN" \ -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
@@ -66,6 +81,7 @@ jobs:
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{process.stdout.write(String(JSON.parse(s).id))})') | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{process.stdout.write(String(JSON.parse(s).id))})')
fi fi
echo "release id: $id" echo "release id: $id"
for f in dist/*; do for f in dist/*; do
name=$(basename "$f") name=$(basename "$f")
echo "uploading $name" echo "uploading $name"
@@ -73,4 +89,4 @@ jobs:
-H "Authorization: token $TOKEN" \ -H "Authorization: token $TOKEN" \
-F "attachment=@$f" > /dev/null -F "attachment=@$f" > /dev/null
done done
echo "published $TAG" echo "published $TARGET assets for $TAG"
+32 -2
View File
@@ -24,6 +24,35 @@ it rather than restating it.
> previously forced website ADRs to be renumbered (see that namespace's > previously forced website ADRs to be renumbered (see that namespace's
> history note and ADR-0000 "Numbering discipline"). > history note and ADR-0000 "Numbering discipline").
## Amendment — 2026-06-13: D1 matrix expanded (non-macOS targets)
The release now builds the **four non-macOS D1 targets**, all cross-compiled
from the Linux x86_64 runner with **`cargo-zigbuild`** (Zig's bundled clang +
libc as one universal cross cc/linker — including the `cc`-crate compile of
rusqlite's bundled SQLite C — added to the flake devShell, replacing the
single-target musl cc):
- `x86_64-unknown-linux-musl`, `aarch64-unknown-linux-musl` — static (D2);
- `x86_64-pc-windows-gnu`, `aarch64-pc-windows-gnullvm` — standalone `.exe`.
`release.yaml` became a **`test` (once, host) → `build` (matrix over the four
targets)** workflow; each matrix job uploads its artifact + `.sha256` to the
shared release (idempotent create-or-get).
**Windows link fix:** Rust's std links `-lsynchronization` (WaitOnAddress
thread-parking), an import lib that rust-overlay's toolchain doesn't ship and
Zig's mingw lacks. Those symbols are forwarded by `kernel32` (already linked),
so an **empty stub** `libsynchronization.a` (committed at `ci/winstub/`, wired
via `.cargo/config.toml` for the Windows targets only) satisfies the linker.
Verified locally: all four build; the Linux binaries are statically linked; the
Windows artifacts are valid PE32+ (x86-64 / Aarch64) — not yet runtime
smoke-tested on Windows.
**macOS stays deferred** (see Deferred): `arboard`→AppKit needs Apple's SDK,
which a Linux runner can't supply cleanly — and the CI image is *public*, so the
SDK can't be baked in even if the licensing grey area were accepted. macOS is
its own step (osxcross + a private SDK, or a real Mac runner).
## Context ## Context
The project is near feature-complete and needs CI (`requirements.md` **TT5**; The project is near feature-complete and needs CI (`requirements.md` **TT5**;
@@ -162,8 +191,9 @@ iteration ships **Linux x86_64 only**; the rest is deferred (below).
## Deferred / out of scope (tracked, step by step) ## Deferred / out of scope (tracked, step by step)
- **D1 matrix:** aarch64, macOS, Windows builds (cross toolchains; macOS is the - **D1 matrix:** **macOS only** now (x86_64 + aarch64). The four non-macOS
hard part on a Linux runner). 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 - **D3 packaging:** Homebrew / Scoop / winget / `cargo-binstall` manifests
(and binstall-friendly asset naming/archives). (and binstall-friendly asset naming/archives).
- **Tier 4 (PTY E2E):** still unwired (`requirements.md` **TT4**); the gate runs - **Tier 4 (PTY E2E):** still unwired (`requirements.md` **TT4**); the gate runs
+1 -1
View File
@@ -18,5 +18,5 @@ here too).
## Index ## 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 this iteration:** Linux x86_64 only — the rest of the D1 matrix (aarch64/macOS/Windows), D3 package-manager manifests, CI-speed dependency caching, and the website's static→Cloudflare deploy are deferred, to be 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-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 **four non-macOS D1 targets** (Linux + Windows × x86_64/aarch64) cross-built from Linux via cargo-zigbuild (2026-06-13 amendment); 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-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).