Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f4beb30
chore: build all artifacts with nix
kevinswiber Jul 23, 2026
6e3b363
fix: skip tests in nix cli build
kevinswiber Jul 23, 2026
b61a77d
fix: package vscode extension with nix
kevinswiber Jul 23, 2026
acbe713
fix: label nix development builds
kevinswiber Jul 23, 2026
d3c6e61
fix: run nix package checks
kevinswiber Jul 24, 2026
3c2b9c9
fix: satisfy nix check lint
kevinswiber Jul 24, 2026
881a7f8
test: run nix checks with nextest
kevinswiber Jul 24, 2026
d6ac629
build: cache nix rust dependencies with crane
kevinswiber Jul 24, 2026
45bbee3
ci: add parallel Nix-driven CI workflow for Linux and macOS
kevinswiber Jul 24, 2026
4fc9874
build: run clippy and rustfmt checks through the flake
kevinswiber Jul 24, 2026
f5ae7cb
ci: add experimental Windows-msvc cross-compile nextest spike
kevinswiber Jul 24, 2026
09abe9f
test: resolve test binary and fixtures at runtime for cross-machine a…
kevinswiber Jul 24, 2026
8432562
ci: mark the Windows cross spike green after the runtime-resolution m…
kevinswiber Jul 24, 2026
cfa104a
test: resolve bench_support fixture paths at runtime
kevinswiber Jul 24, 2026
5e96304
ci: run the Nix gates in phases on the test profile
kevinswiber Jul 24, 2026
da4e73d
test: retire the completed naming-cutover doc guards
kevinswiber Jul 24, 2026
9bb34f3
build: stop installing the clippy gate's target directory
kevinswiber Jul 24, 2026
dcf73a1
ci: cache the Nix store with Magic Nix Cache
kevinswiber Jul 24, 2026
a01cad2
ci: stop rebuilding the whole suite in the Nix lint job
kevinswiber Jul 24, 2026
356e938
ci: take the Linux and macOS toolchain from the flake
kevinswiber Jul 25, 2026
14040a9
ci: gate pull requests on the hermetic Nix suite again
kevinswiber Jul 25, 2026
a1df8ae
docs: record why CI is split the way it is
kevinswiber Jul 25, 2026
f74f930
ci: cache the Nix store with hestia
kevinswiber Jul 25, 2026
c9c3e22
docs: record the measured hestia cache result
kevinswiber Jul 25, 2026
225dcec
ci: prune the hestia cache nightly
kevinswiber Jul 25, 2026
bc7d3d4
ci: shard the Windows cross-archive run
kevinswiber Jul 25, 2026
22b4788
test: retire the cargo install packaging check
kevinswiber Jul 25, 2026
daf6b88
ci: promote the Windows cross-archive lane into the gate
kevinswiber Jul 25, 2026
2139b13
ci: stop keying the Windows check leg as a shared shard cache
kevinswiber Jul 25, 2026
ea57851
ci: remove the Windows wall-time measurement harness
kevinswiber Jul 25, 2026
c21af3e
test: guard runtime resolution of build-machine paths
kevinswiber Jul 25, 2026
bfba689
ci: remove color output for cargo via CARGO_TERM_COLOR = never
kevinswiber Jul 25, 2026
3c5b186
ci: report the suite result from the Nix gate
kevinswiber Jul 25, 2026
d49fc05
ci: run the Linux suite in the main workflow
kevinswiber Jul 25, 2026
c05b9af
ci: move the cross-archive transfer onto the current artifact actions
kevinswiber Jul 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/result
67 changes: 67 additions & 0 deletions .github/workflows/cache-gc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Cache GC

# Prunes the hestia binary cache so the Nix lane stays inside GitHub's 10GB
# per-repository cache quota, which it shares with ci.yml's rust-cache entries.
#
# Scope: this deletes only paths hestia itself pushed. Hestia tracks what is alive
# through its own "roots" (one per branch and system, e.g. `main-x86_64-linux`), and
# collects paths unreachable from any root once they fall out of the push grace period.
# Cache entries written by other actions — Swatinem/rust-cache, setup-node, the Nix
# installer — are not in hestia's manifest and are never considered.
#
# It has to run on the default branch: a pull request's cache scope is read-only towards
# the default branch and disappears with the PR, so a PR-scoped run cannot prune what the
# default branch owns.
#
# See docs/ci-architecture.md#store-caching.

concurrency:
# Hestia handles concurrent pushes but not a concurrent GC: a second run's repack reads
# race the first run's post-commit deletes. Queue behind a running GC, never cancel it
# partway through a delete pass.
group: hestia-gc
cancel-in-progress: false

on:
schedule:
# Daily and off-peak. Deliberately not 07:00 UTC, which is the nightly full flake
# check in nightly.yml.
- cron: "23 3 * * *"
workflow_dispatch:
inputs:
dry-run:
description: Plan only; do not repack, touch, or delete anything.
type: boolean
default: false

permissions:
contents: read

jobs:
gc:
name: prune the hestia cache
runs-on: ubuntu-latest
permissions:
# The cache deletes go through the REST API, which needs actions:write. This is the
# only workflow here that gets it.
actions: write
contents: read
steps:
# The action writes Nix configuration when it installs, so Nix has to exist first.
- uses: DeterminateSystems/nix-installer-action@main

# Installs the released binary and exports HESTIA_BIN. Same SHA pin as the lanes
# that populate the cache — a GC from a different version than the writers is not
# a combination worth discovering at 03:23.
- uses: Mic92/hestia@fb239a2f72d4b6e26eec5425f289dea23b27a527 # v2

- name: Run garbage collection
env:
GITHUB_TOKEN: ${{ github.token }}
DRY_RUN: ${{ inputs.dry-run }}
run: |
if [ "$DRY_RUN" = "true" ]; then
"$HESTIA_BIN" gc --dry-run
else
"$HESTIA_BIN" gc
fi
223 changes: 153 additions & 70 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:
contents: read

env:
CARGO_TERM_COLOR: always
CARGO_TERM_COLOR: never
RUST_BACKTRACE: 1

jobs:
Expand Down Expand Up @@ -93,84 +93,167 @@ jobs:
working-directory: extensions/vscode
run: npm run check

test:
name: ${{ matrix.name }}
# Windows is the CI long pole and is spawn-bound (per-CreateProcess cost), so its
# ~226s execution is fanned across parallel slice-partitioned shards while ubuntu and
# macOS stay single legs. Lint runs on Linux only; the cfg(windows)/cfg(not(unix))
# arms are type-checked once on a dedicated Windows check leg; the shard legs run
# tests only. All Windows legs share one rust-cache entry (shared-key) since they
# compile the identical test-binary set.
strategy:
fail-fast: false
matrix:
include:
- {
name: "test (ubuntu-latest)",
os: ubuntu-latest,
gate: lint,
run_tests: true,
}
- {
name: "test (macos-latest)",
os: macos-latest,
gate: check,
run_tests: true,
}
- {
name: "test (windows-latest check)",
os: windows-latest,
gate: check,
run_tests: false,
}
- {
name: "test (windows-latest 1/3)",
os: windows-latest,
run_tests: true,
shard: 1,
shards: 3,
}
- {
name: "test (windows-latest 2/3)",
os: windows-latest,
run_tests: true,
shard: 2,
shards: 3,
}
- {
name: "test (windows-latest 3/3)",
os: windows-latest,
run_tests: true,
shard: 3,
shards: 3,
}
runs-on: ${{ matrix.os }}
test-linux:
# Named for the platform, not the mechanism: alongside test (macos-latest) and
# test (windows-latest N/3) this is what makes the gate legible as covering all
# three. It does more than test — fmt, clippy, and the artifact build run here too —
# and unlike the other two it runs as sandboxed derivations rather than plain cargo.
# See docs/ci-architecture.md#why-keep-a-nix-lane.
name: test (ubuntu-latest)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: DeterminateSystems/nix-installer-action@main

# Third approach here; the first two each hit a different GitHub ceiling.
# cache-nix-action tarred the whole /nix/store (4.87GB Linux + 4.46GB macOS
# against a 10GB STORAGE limit, so the platforms evicted each other). Magic Nix
# Cache stored one entry per store path (1788 of them), which tripped the API
# RATE limit and then disabled itself mid-run. Hestia packs paths into
# content-defined chunks — few large entries — so it avoids both.
# Uploads authenticate with the runner-injected ACTIONS_RUNTIME_TOKEN, so
# `permissions: contents: read` above is sufficient.
# See docs/ci-architecture.md#store-caching.
- uses: Mic92/hestia@fb239a2f72d4b6e26eec5425f289dea23b27a527 # v2

# Named individually on purpose: `nix flake check` batches every check in
# arbitrary order and so cannot return early on a cheap failure. nightly.yml runs
# the un-named form so an added check cannot be left ungated.
# See docs/ci-architecture.md#the-pr-gate-names-its-checks-the-nightly-one-does-not.

# Phase 1 — formatting. Compiles nothing, so this is a true early return.
- name: fmt
run: nix build .#cli-fmt

# Phase 2 — clippy, with the shared test-profile dependency artifacts.
- name: clippy
run: nix build .#cli-clippy

# Phase 3 — the full suite in the sandbox, plus pinned-tool drift.
- name: test
run: nix build .#cli-nextest .#devshell-tools

# Phase 4 — hermetic, network-free build of everything the project ships.
- name: build
run: nix build .#build-all

# `nix build` prints nothing on success, so a green run showed no evidence the suite
# had run at all. Recover the counts from the derivation's own log rather than
# restoring `-L`, which streams every compile line and made these runs unreadable.
#
# Nextest colourises even in the sandbox — it has no TTY, but nothing tells it not to,
# and the workflow's CARGO_TERM_COLOR does not reach inside a derivation — so the
# escape codes have to come out before matching.
- name: suite summary
run: |
drv="$(nix eval --raw .#cli-nextest.drvPath)"
log="$(nix log "$drv" 2>/dev/null || true)"
if [ -n "$log" ]; then
printf '%s\n' "$log" | sed 's/\x1b\[[0-9;]*m//g' \
| grep -aE 'Starting [0-9]+ tests|Summary \[|^ +(FAIL|SLOW)' || true
else
echo "No local build log: the suite result was substituted from the cache."
echo "Identical inputs, so the derivation was not rebuilt and the tests did not re-run."
fi

- name: dump failing build log
if: failure()
run: nix log "$(nix eval --raw .#cli-nextest.drvPath)" 2>/dev/null | tail -150 || true

test-macos:
name: test (macos-latest)
# The only platform whose suite runs as a plain cargo invocation. Linux is covered
# hermetically by the sandboxed test-linux job above; Windows by the cross-compiled
# archive below. This leg keeps rust-cache's incremental target/, which is the whole reason it
# is a `nix develop` run and not a derivation.
# See docs/ci-architecture.md#toolchain-comes-from-the-flake.
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: DeterminateSystems/nix-installer-action@main
- uses: Mic92/hestia@fb239a2f72d4b6e26eec5425f289dea23b27a527 # v2
# rust-cache belongs on this leg and NOT on the Nix lane: it caches ./target,
# which a sandboxed `nix build` never writes. See
# docs/ci-architecture.md#rejected-alternatives.
- uses: Swatinem/rust-cache@v2
- name: just check-types
run: nix develop .#ci -c just check-types
- name: just test-ci
run: nix develop .#ci -c just test-ci

test-windows-check:
name: test (windows-latest check)
# Type-checks only; the shards below run the tests. Kept on rustup, and kept at all,
# because `just check-types` is `--all-features` while the archive is built with
# default features: the cfg(windows) arms behind `bench` and `gix-parity` are compiled
# for Windows nowhere else. Nix cannot supply this — no native Windows support — and
# store-foundation-qualification and git-parity need rustup on Windows anyway.
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
- uses: taiki-e/install-action@just
- uses: taiki-e/install-action@nextest
# No longer shared with anything: the sharded test legs run prebuilt binaries and
# have no target/ to cache. Keyed on its own so it stops colliding with them.
- uses: Swatinem/rust-cache@v2
with:
shared-key: ${{ runner.os == 'Windows' && 'windows-shards' || '' }}
- name: just lint
if: matrix.gate == 'lint'
run: just lint
- name: just check-types
if: matrix.gate == 'check'
run: just check-types
- name: configure Windows test debuginfo
if: runner.os == 'Windows'
shared-key: windows-check
- run: just check-types

windows-cross-archive:
name: cross-compile nextest archive (ubuntu → msvc)
# Compiles the Windows test binaries once, on Linux, with cargo-xwin against the MSVC
# CRT. The shards below then execute them with no Rust toolchain on Windows at all.
# See docs/ci-architecture.md#windows.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: DeterminateSystems/nix-installer-action@main
- uses: Mic92/hestia@fb239a2f72d4b6e26eec5425f289dea23b27a527 # v2
# cargo-xwin fetches the MSVC CRT/SDK here; XWIN_ACCEPT_LICENSE is set in the shell.
- name: cross-compile x86_64-pc-windows-msvc nextest archive
run: nix develop .#windows-cross --command just windows-cross-archive x86_64-pc-windows-msvc
- uses: actions/upload-artifact@v7
with:
name: nextest-archive-x86_64-pc-windows-msvc
path: target/nextest/pointbreak-x86_64-pc-windows-msvc.tar.zst
if-no-files-found: error
retention-days: 3

test-windows:
name: test (windows-latest ${{ matrix.shard }}/${{ matrix.shards }})
needs: windows-cross-archive
runs-on: windows-latest
# Sharded because the suite is spawn-bound on per-CreateProcess cost, so execution
# parallelises across machines far better than across cores on one. Unlike the legs
# this replaced, no shard compiles anything — all three consume the single archive
# above — so sharding buys execution time without multiplying build time.
strategy:
fail-fast: false
matrix:
include:
- { shard: 1, shards: 3 }
- { shard: 2, shards: 3 }
- { shard: 3, shards: 3 }
steps:
- uses: actions/checkout@v6
- uses: taiki-e/install-action@nextest
- uses: actions/download-artifact@v8
with:
name: nextest-archive-x86_64-pc-windows-msvc
path: archive
# --workspace-remap points the runtime-resolved fixture and binary lookups at this
# checkout; see tests/support/env.rs for why they are resolved at runtime.
- name: run archived suite
shell: bash
run: echo "CARGO_PROFILE_TEST_SPLIT_DEBUGINFO=packed" >> "$GITHUB_ENV"
- name: just test-ci
if: matrix.run_tests
run: just test-ci ${{ matrix.shard && format('--partition slice:{0}/{1}', matrix.shard, matrix.shards) || '' }}
run: |
cargo nextest run \
--archive-file archive/pointbreak-x86_64-pc-windows-msvc.tar.zst \
--workspace-remap . \
--partition "slice:${{ matrix.shard }}/${{ matrix.shards }}" \
--no-fail-fast

store-foundation-qualification:
name: store foundation qualification (${{ matrix.os }})
Expand Down
47 changes: 47 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Nightly

# What ci.yml cannot afford to run per pull request.
#
# ci.yml's test-linux job already runs the sandboxed suite on Linux for every change.
# This adds the two things too slow to gate on: the same hermetic check on macOS, which
# measured 22m37s against Linux's 15m46s, and `nix flake check` in full, which builds
# every check in the flake rather than the four ci.yml names explicitly.
#
# Nothing here runs on a pull request, deliberately. A job that only skips is noise in
# the checks list.

on:
schedule:
- cron: "0 7 * * *"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: nightly-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: never
RUST_BACKTRACE: 1

jobs:
full-check:
name: full flake check (${{ matrix.os }})
# Runs `nix flake check` rather than naming the checks the way ci.yml's test-linux
# job does, so that a check added to the flake cannot be silently left ungated.
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: DeterminateSystems/nix-installer-action@main
- uses: Mic92/hestia@fb239a2f72d4b6e26eec5425f289dea23b27a527 # v2
- name: nix flake check
run: nix flake check
- name: dump failing build log
if: failure()
run: nix log "$(nix eval --raw .#cli-nextest.drvPath)" 2>/dev/null | tail -150 || true
15 changes: 11 additions & 4 deletions .github/workflows/nix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ jobs:
- name: Install just
uses: taiki-e/install-action@just

# nixfmt --check + statix + deadnix + `nix flake check`. Runs only on Nix
# changes (see paths above); the Rust gate stays in ci.yml.
- name: Run nix-check
run: just nix-check
# nixfmt --check + statix + deadnix. Builds nothing, so this job matches its
# name and finishes in seconds.
#
# See docs/ci-architecture.md#nixyml-lints-nix-files-and-nothing-else.
# It used to run `just nix-check`, whose `nix flake check` BUILDS every check
# — clippy, the full nextest suite, tool drift — with no store cache, taking
# 14-18 minutes. Since ci.yml realises those same derivations on every
# pull request, this job was running the entire suite a second time, in
# parallel, from cold. Linting the Nix files is what is unique to this lane.
- name: Run nix-lint
run: just nix-lint
Loading