CI#
The CI workflow is two commands, plus the release (rig publish and a
tag — Build, publish, release):
rig check && rig test
rig build --uber
rig publishThe gate#
| Step | What it proves |
|---|---|
rig check |
the locked artifacts are byte-identical to the pinned hashes — verified from local sources on a warm cache, fetched and hash-checked on a cold one (the supply-chain gate, exit 4 on a mismatch). The lock still satisfies every manifest (no stale pins, conflicts, drift, floating versions). Every module's namespaces load on the locked classpath. No lock modification. |
rig test |
the tests pass on the locked classpath; a stale lock fails (exit 3) instead of re-locking. |
A stale lock is a hard error on every gate command: the hot commands exit
3, and check reports staleness as a problem (exit 1) — a requirement the
pin no longer satisfies, or a manifest whose bytes changed since the lock
was written. The lock is never refreshed under your feet: any deviation
from the committed lockfile fails the pipeline. The gate can use the
network to fill a cold cache. Either way, Rig hash-checks every byte
against the lockfile.
Run rig build --uber afterwards for artifacts (or whatever your
pipeline builds).
A complete example#
Example GitHub Actions workflow:
name: Deploy from main
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install rig (binary, JVM, cache)
uses: brutasse/setup-rig@b204f6b12fde6574ebbebe0227fcaaa10391882f # v2.0.0
with:
version: v0.4.1
- name: Check and test
run: |
rig check
rig test
- name: Build uberjars
run: rig build --uber
- name: Release
run: |
rig publish
git tag "v$(cat VERSION)"
git push origin HEAD --tagsNotes:
- setup-rig installs the Rig binary (verified with the SHA256 hashes in
the release
SHA256SUMS) and the JVM pinned indeps.lock, and restores the artifact cache keyed ondeps.lock. The action saves the cache automatically at the end of the job — even after a failed step — so you need no save step. A bare runner is enough: the host needs neither a JDK nor the Clojure CLI. Pin the action ref to a git sha, and pinversionto a release. - Git dependencies use the same git/ssh setup your repository already
uses. Their commit shas are in the lockfile, so a green run means "this
exact commit". A bare runner provisions the checkouts itself: when a
checkout is missing, the command fetches the locked commit into the
gitlibs root (
~/.gitlibs, or$GITLIBSwhen set). - The cache key is
deps.lock(same lockfile → warm hit, changed lockfile → miss and re-fetch). The default gate can fetch on a cold cache, so the cache only adds speed there. The offline gate below needs it: a cache hit is what keeps that build hermetic. - The release is
rig publishand a tag on the released version: the version history is yours (Build, publish, release). The push onmainthat triggered the workflow already carries the branch, so the step only pushes the tag.
Hermetic / air-gapped CI (opt-in --offline)#
To prove the build needs no network — or when CI itself has no network —
add --offline to the gate:
rig check --offline && rig test --offline--offline refuses the network. Every artifact (and the kernel and runner
jars) must already sit in the Rig cache (~/.local/share/rig) or in a
checksum-checked Maven repository (~/.m2/repository). On a cold cache,
Rig fails with exit 1. That is the point: a green offline run is the
hermeticity proof. --offline refuses the git fetch too: a missing
checkout fails the gate and points at the gitlibs root. Cache the Rig
cache alongside ~/.m2/repository and
~/.gitlibs, or run on a machine that has them filled. Those are exactly
the stores setup-rig caches. When its cache-hit output is true, an
offline gate has everything it needs. Check that output if the job should
fail fast on a cold cache.
Managed JVMs#
When the workspace pins a JVM (:rig/jvm in the root deps.edn), Rig uses
the rig-managed JDK for that major from the state dir. A matching system
JDK serves only when no managed JDK exists. For CI, that means:
- Do not rely on the runner's system JDK. Rig uses the managed JDK
automatically. setup-rig installs the pinned major by default and caches
the JVM store under its own key, so a lockfile bump never re-downloads a
JDK. On other CI, cache
~/.local/share/rig(it containsjdks/) or pre-install withrig jvm install <major>. - Hermetic
--offlinebuilds need the pinned JDK already in the state dir, or they fail with a hint. - Moving the pinned major to its newest release is a store-level change:
rig jvm updatereplaces the rig-managed JDK. Rig does not writedeps.lock, so there is nothing to commit.
Per-PR testing#
For branch-level CI that does not deploy, the same gate works on a subset:
rig check -p modules/example # -p restricts the ns-load stage
rig test -p modules/example :kaocha.filter/focus '[:unit]'Stages 1 (artifact hashes) and 2 (lockfile vs manifests) of check are
always workspace-wide. -p only narrows the namespace-loading stage 3.
What CI must NOT do#
- run
rig lockin CI — a stale lock must fail the gate (exit 3), not get refreshed under your feet; fixing it belongs to the PR that changed the requirements; - commit
deps.lockchanges from CI — lockfile changes belong to the PR that changed the requirements, where you review them.