Compare commits
2 Commits
60dbb903cc
...
aeb92f56a7
| Author | SHA1 | Date | |
|---|---|---|---|
| aeb92f56a7 | |||
| 309d2e0b3f |
@@ -1,81 +0,0 @@
|
||||
# THROWAWAY build smoke-test for the macOS (Tart) runner. Verifies both
|
||||
# *-apple-darwin targets actually compile and link (incl. arboard's AppKit)
|
||||
# through the flake on the real Mac, before the full release-macos workflow is
|
||||
# wired. Delete once that lands.
|
||||
#
|
||||
# Push-triggered (workflow_dispatch only works for workflows on the default
|
||||
# branch; our CI is on `ci`). Runs when the flake/toolchain or this file change.
|
||||
# Bring the Mac up before pushing so the run isn't left queued.
|
||||
name: macos-build-test
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/macos-probe.yaml'
|
||||
- 'flake.nix'
|
||||
- 'rust-toolchain.toml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# Label NAME only — `:host` in the runner registration is the execution
|
||||
# backend (run on host), not part of the label.
|
||||
runs-on: macos
|
||||
env:
|
||||
# Guarantee flakes regardless of the Mac's nix config.
|
||||
NIX_CONFIG: "experimental-features = nix-command flakes"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: test (macOS — the gate only covers Linux)
|
||||
run: nix develop -c cargo test --no-fail-fast
|
||||
- name: build, de-nix, sign, verify both darwin targets
|
||||
run: |
|
||||
set -e
|
||||
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"
|
||||
|
||||
# The darwin stdenv bakes a /nix/store libiconv load path into the
|
||||
# binary. Rewrite it to the system libiconv (every Mac has it, ABI-
|
||||
# compatible), then re-sign ad-hoc — install_name_tool invalidates
|
||||
# the signature and arm64 won't run an unsigned/broken-sig binary.
|
||||
for l in $(otool -L "$f" | awk '/\/nix\/store.*libiconv.*dylib/ {print $1}'); do
|
||||
echo "rewrite $l -> /usr/lib/libiconv.2.dylib"
|
||||
install_name_tool -change "$l" /usr/lib/libiconv.2.dylib "$f"
|
||||
done
|
||||
codesign --force --sign - "$f"
|
||||
|
||||
echo "--- linked libs ---"; otool -L "$f"
|
||||
if otool -L "$f" | grep -q /nix/store; then
|
||||
echo "ERROR: $t still links a /nix/store dylib"; exit 1
|
||||
fi
|
||||
codesign --verify --verbose=2 "$f" && echo "signature OK"
|
||||
|
||||
# Smoke-run the natively-runnable target (this VM is arm64).
|
||||
if [ "$t" = "aarch64-apple-darwin" ]; then
|
||||
echo "--- run --help ---"; "$f" --help | head -1
|
||||
else
|
||||
echo "(skip run: $t needs Rosetta)"
|
||||
fi
|
||||
echo "OK: $t portable"
|
||||
done
|
||||
echo "=== both darwin targets built, de-nixed, signed, verified ==="
|
||||
|
||||
- name: prune nix store — keep the last 2 toolchain generations
|
||||
# The runner wipes the whole workspace before each run, so cargo target/
|
||||
# never accumulates (no sweep needed). The persistent caches are the nix
|
||||
# store (/nix) and ~/.cargo (in $HOME). Bound the nix store by generation:
|
||||
# record the current devShell closure as a generation of a persistent
|
||||
# profile (lives in $HOME, survives the workspace wipe), keep the 2 newest
|
||||
# (current + previous), reclaim what the older ones referenced. No time
|
||||
# window — never more than two toolchains regardless of flake.lock churn.
|
||||
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
|
||||
# ~/.cargo/registry also persists but grows only on Cargo.lock bumps;
|
||||
# bound it later with `cargo-cache --autoclean` if it ever matters.
|
||||
@@ -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
|
||||
@@ -20,6 +20,49 @@ 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
|
||||
@@ -135,7 +178,8 @@ user decides when we get there.
|
||||
|
||||
## Deferred / out of scope
|
||||
|
||||
- **macOS** (x86_64 + aarch64) — the SDK/runner decision above.
|
||||
- ~~**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.
|
||||
|
||||
@@ -20,4 +20,4 @@ here too).
|
||||
|
||||
- [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 deferred** — `arboard`→AppKit needs Apple's SDK, a licensing grey area on a Linux runner, and the **public** CI image can't carry it; its own step (osxcross + a private SDK, or a Mac runner). 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).
|
||||
- [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).
|
||||
|
||||
Reference in New Issue
Block a user