Releases#
Changelog for Rig.
Unreleased#
v0.4.1 — 2026-11-04#
Changed#
A git dependency pins a full commit sha#
A git dependency is a commit pin: :git/sha, the full 40-hex sha. Rig
refuses every other form, one line per problem naming the fix, before
tools.deps reads the manifest — a tools.deps error here carries no
:path and surfaces as a stack trace:
invalid git dep:
deps.edn: com.example/widgets: :git/tag is not supported — a tag moves; pin the commit it names with :git/shaThe refused forms:
:git/tagand:git/branch— a movable ref is not a pin. Before, the lock recorded wherever the ref pointed.- a short
:git/sha - the legacy
:tagand:shakeys - a coord with no commit pin
- a coord that declares no
:git/urland does not name a known host
rig lock and rig tree refuse the manifest at start (exit 1);
rig check reports the same problems as git-ref errors. rig add
and rig update do not edit git coordinates: edit the manifest and
run rig lock. On a known host — github.com, gitlab.com,
codeberg.org, bitbucket.org, beanstalkapp.com, sourcehut.org, the
table tools.deps uses — Rig derives the :git/url.
Git dependencies.
GITLIBS selects the gitlibs root#
The binary and the kernel take the gitlibs root from $GITLIBS
($GITLIBS/libs), defaulting to ~/.gitlibs/libs — the root
tools.gitlibs, and so the clojure CLI, checks out under. Before, the
binary hardcoded ~/.gitlibs/libs: a machine that set GITLIBS kept
two roots — the clojure CLI's checkouts under the override, Rig
reading ~/.gitlibs.
Fixed#
Hot commands provision missing git checkouts#
A machine without the gitlibs checkouts — a bare CI runner — could not
run the hot commands on a committed lock that pins a git dep. The
checkout was absent, and the command pointed at rig lock. The
command now provisions the checkout itself when online. The kernel op
git-procure runs the same tools.gitlibs procure the lock runs — the
mirror clone plus a worktree at the pinned sha. Provisioning is
idempotent: it adopts an existing checkout and never re-clones one. It
fetches the url and sha the lock records, not the repository's state.
--offline still refuses the fetch: a missing checkout fails the
command and points at the gitlibs cache.
A :local/root dep keeps its :exclusions#
Rig adds :exclusions to every :local/root dep: the libs tools.deps
merges into the local manifest from its bundled root deps. The
addition replaced the :exclusions a manifest declared on the dep. It
now merges the injected libs with the declared exclusions, duplicates
dropped.
Docs#
- Dependencies gains a
Git dependencies section: the full-sha pin, the refused forms, url
derivation, and the gitlibs root with its
GITLIBSoverride. The closed build and CI pages describe the on-demand provisioning and the--offlinerefusal.
v0.4.0 — 2026-11-04#
New#
:rig/main compiles into the artifact without a declaration#
When a module's :rig/main names a Clojure namespace the module does
not compile itself — a dependency namespace, or a namespace of another
workspace module — rig build AOT-compiles it into the jar or uberjar
without any declaration: the AOT plan adds it to the entry points when
a .clj/.cljc source for it exists on the locked classpath. The
entry point's classes reach the artifact, so the jar's Main-Class is
runnable. A main with no source to compile — a Java Main-Class, or a
namespace the classpath carries as classes alone — is left alone: there
is nothing for the build to compile.
Configuration — Entry points.
In-module :gen-class namespaces compile into the artifact without a declaration#
A namespace that is one of the module's own sources and whose ns
form carries a :gen-class clause joins the AOT plan's entry points by
construction: its generated class reaches the artifact only as AOT
output, so rig build compiles the namespace unconditionally, even
when a preceding compile in the build already loaded it. No key
declares it.
Configuration — Entry points.
rig new scaffolds the development module#
A scaffolded workspace now carries a development module alongside the
app module: one per workspace, not one per module. Its deps.edn
loads the app module by :local/root and pins
reloaded.repl, the
reloaded-workflow library; dev/user.clj is the scratch namespace the
development REPL opens in. Start it with rig nrepl -p dev; the
workflow is in Day-to-day workflow. rig new-module does not touch the dev module — a new module joins it as a
:local/root dep when you want it in the development REPL.
The scaffolded :test alias also carries the property-based and
mocking tools (test.check, spy) and the two kaocha reporters (JUnit
XML, cloverage) as :extra-deps, dormant until a run loads them with
:plugins.
Every tooling coordinate of the scaffold — the test runner, the
reporters, reloaded.repl — resolves at scaffold time to the newest
release past its cooldown, in one batched
kernel call, so a new project starts on what the repositories serve
rather than on versions compiled into the binary. --offline writes
the versions bundled with the binary, and so does a lookup that has no
answer for a coordinate — with a note naming it.
Changed#
The lock gate replaces --frozen#
The --frozen flag is gone. The lock behavior is now fixed per command:
- the locking family —
rig lock,rig add,rig remove,rig update,rig new,rig new-module— writesdeps.lock. It needs no flag; - every command that builds a classpath from the lock (
test,build,run,nrepl,exec,install,publish) fails with exit 3 when the lock is missing or stale. The dev-time re-lock (therelocked (stale: …)line) is gone: a hand-editeddeps.ednnow needs an explicitrig lockbefore the command runs; rig checkkeeps its reporting contract: a stale requirement is a reported problem (exit 1), and a missing lock still fails with exit 3.
CI invocations drop the flag: rig check && rig test.
:rig/uber-opts is gone#
The key and its :exclude patterns are gone: rig no longer excludes
anything from the uber jar beyond tools.build's own defaults
(signature files, *.pom, module-info.class, …). Duplicate entries
from the dependency jars keep the first in classpath order, and rig build still reports the ones whose contents differ. A manifest that
still carries :rig/uber-opts parses; the key is ignored. rig migrate drops the legacy :exoscale.project/uber-opts entry instead
of renaming it.
rig verify is folded into rig check#
rig verify is gone: its artifact hash verification is now the first
stage of rig check, and the CI gate is rig check && rig test. The
security-failure exit code is unchanged — a hash mismatch still exits 4,
and a tampered entry is reported, never re-fetched and healed.
check also gained byte-level staleness: a manifest that changed since
the lock was written is reported as a stale-lock problem naming the
module (exit 1), even when the change left every requirement untouched.
A requirement the pin no longer satisfies is reported once — by the
kernel's finer per-requirement line. A missing lock still exits 3, and
check never re-locks.
rig version is folded into rig info#
rig version is gone: rig info prints the project version as a
version line — the VERSION file of the target module (module dir,
then workspace root), or the locked module version, and -p selects the
module. Where rig version exited 1 when no source recorded a version,
rig info prints version: - and reports the rest of the state.
rig nrepl replaces rig repl#
rig repl (a bare clojure.main REPL) is gone: rig nrepl starts an
nREPL server on the (alias) classpath instead, so an editor can attach.
--alias <a> selects the classpath; --host and --port the bind
target (default: the loopback interface on a free port, printed on
startup). The nREPL jar is vendored inside the rig binary — one upstream
jar whose only dependency (org.clojure/clojure) is provided by the
project's own locked Clojure — and appended to the classpath last, so a
project that declares nrepl/nrepl keeps its own version. The vendored
nREPL needs Clojure 1.10 or newer; on an older pin the command stops
with a hint.
The tool jars ship inside the binary#
The resolver kernel jar, the rig.runner jar, and the vendored nREPL jar
are embedded in the rig binary (go:embed) instead of fetched from the
GitHub release at runtime against a hash pin. On first use each jar is
extracted into the state dir at its content-sha path, adopting a cached
copy when its digest matches: no network, no pin to maintain, and the
Docker image pre-materializes the jars into the store at their content-sha
paths at build time, so a first use writes nothing and the image works
with a read-only root file system. The kernel identity (coordinate, version,
git sha) is stamped into the binary at release build via -ldflags, the
same way as the Rig version, and the lock's resolver block still
records the sha256 of the jar that actually ran. The RIG_KERNEL_JAR /
RIG_RUNNER_JAR dev overrides work as before. The kernel and runner
jars remain published as release assets: pre-embedding rig binaries
still fetch them at runtime.
deps.lock no longer records locked_at#
The top-level locked_at timestamp is out of the lock schema: no other
lockfile records when it was written, and the moving value made the file
non-deterministic — the Renovate re-lock in CI had to strip it before
diffing. Locks written by older rig versions still load: the field is
ignored on read and absent from the next re-lock, so the schema stays at
version 2. rig info no longer prints it.
Manifest parse errors point at the failure#
The EDN reader rig carries in the binary for reading manifests (ednlit)
reports a parse failure as ednlit: <file>:<line>:<col>: message,
followed by the offending source line with a caret under the column —
instead of a byte offset into the file. It also refuses, with an
explicit error, the EDN forms it has no type for: big integers, big
decimals, ratios, character literals and tagged literals. The Clojure
reader accepts all of them; a manifest needs none of them.
Shell completion stays quiet#
The "newer release available" notice is suppressed on completion
invocations — --autocomplete and the hidden __complete request the
shell issues on every tab press. Completion stays fast and quiet: no
network, no stderr.
Removed#
:rig/ns-compile#
The key is gone. :rig/main and the module's :gen-class namespaces
compile into the artifact without a declaration, and the module's
other namespaces compile with it. A manifest still carrying the key is
refused at read time — the resolver names the file and the fix, and
the refusal exits 4 (the legacy-key refusal exits 3) — and rig migrate drops the key, the legacy :exoscale.project/ns-compile
spelling included, with a warning.
The behavior loss: an out-of-tree namespace — a source on the classpath
outside the module's artifact dirs — can no longer be declared as an
extra entry point. To ship its classes, move the source into the
module: a :gen-class namespace then compiles unconditionally, and
any other namespace compiles with the module.
rig lint#
rig lint is gone: it ran clj-kondo from the user's PATH, the one verb
that depended on a host tool Rig cannot pin or ship (cljfmt is pinned in
the kernel jar; clj-kondo is not). Run clj-kondo directly, or
rig exec clj-kondo --lint <dir>, if you want the lint.
rig update with no arguments#
The no-args form — a full re-resolve that re-selects the newest
eligible version for every floating requirement — is gone: floating
versions are not really a thing. rig update <coord> [version] is the
only form, and a floating requirement is refreshed by naming its
coordinate.
Fixed#
:rig/timestamp-string stamps in any zone#
A zip entry stores a wall-clock, and the writer renders the epoch in
the JVM's default zone, so a UTC pin — 2020-01-01T00:00:02Z, say —
stamped 01:00:02 on a CET host: the jar was not byte-identical
across hosts. The stamp now reads the pin's UTC wall-clock as local
time in the default zone before writing the entry — the pin's clock, in
any zone — and two builds of the same lock are byte-identical again.
v0.3.2 — 2026-10-30#
Fixed#
Checkpoint trouble degrades instead of failing every command#
A CRaC restore that dies before the op runs — a corrupt or unopenable
image, say — exits 1, the same code the kernel uses for a failed op.
Rig trusted the exit code: the cold fallback, the documented safety
net, never ran, and every checkpoint-eligible command failed as
kernel: op failed, the JVM's own error filtered away with the
checkpoint noise. One damaged image in the machine-global chain was a
machine-wide rig lock outage that only deleting the store by hand
cleared. A nonzero restore exit without a landed dump now always
re-runs the op cold — the cold path is the only voice on failure —
and retires the image that could not serve it (an externally-killed
restore leaves the image alone), so a broken chain repairs itself on
its first failed command. A chain that fills its 6-image cap now
rotates, the next cold run re-bootstrapping instead of parking the
checkpoint path off for good. -v prints why a restore fell back.
v0.3.1 — 2026-10-07#
Fixed#
Managed JDK and GraalVM installs on macOS#
Temurin and GraalVM CE ship their macOS archives in the bundle
layout, with the JDK under Contents/Home. The managed stores
verified installs by stat'ing bin/java (and bin/native-image)
at the install root. A complete macOS install therefore never
validated:
every command touching it failed with corrupt install (missing bin/java) — rig jvm install, jvm path, and anything downstream.
Installs now resolve the home inside the bundle, and rig jvm path
prints the correct macOS JAVA_HOME.
v0.3.0 — 2026-10-07#
New#
Kernel checkpointing (CRaC)#
On Linux, Rig restores the kernel JVM from a checkpoint of a warmed
run instead of forking a cold one: ~0.3 s instead of 1–3 s per lock,
check, tree. Every restored run re-dumps the checkpoint, so it
stays current with use. Everything stays opportunistic: without a
CRaC-capable JVM every command behaves exactly as before.
rig crac install fetches the Zulu CRaC JDK pinned by this release
(sha256-verified, into the state dir); rig crac status and
rig crac clean inspect and prune it. Rig keys image chains to the
kernel jar bytes, the JVM build and the CPU features. A rotated
kernel or JDK starts a fresh chain instead of restoring stale state.
A daily boot-time pass sweeps old-kernel chains
automatically.
Config that rides the JVM environment in cold runs
(RIG_REPO_TOKENS, RIG_PROXY_REPOS) rides the request file for
restored runs. It is authoritative there: a restored kernel never
inherits the bootstrap run's tokens. See
Configuration — Kernel checkpointing.
Maven coordinates in built artifacts#
A module with :rig/lib and a version now carries
META-INF/maven/<group>/<artifact>/pom.{xml,properties} in everything
it builds — jar, uberjar and native image. The pom is the deployed
one: same coordinates, same lock-pinned dependencies, as
rig publish deploys them. It is the convention every jar scanner
attributes through, and the classic version-self-report
(io/resource on pom.properties): an artifact built by Rig now
reports about itself the coordinates it deploys under.
rig jvm path and rig graalvm path#
rig jvm path <major> and rig graalvm path <major> print the
JAVA_HOME / GRAALVM_HOME of the newest installed managed JDK or
GraalVM satisfying the major — a bare path on stdout, for scripts and
CI. The state dir is read only: the commands never install, never
download, and need no network; when nothing matches they fail (exit 2)
with the install hint. setup-rig uses them to expose the pinned
JVM's homes as action outputs.
Fixed#
- Ubers carried no classes their AOT bytecode linked against.
Source-only dependencies (
org.clojure/tools.namespace,rewrite-clj) — the build's preloads loaded them without ever emitting classes: the build went green while the uber died at first use with aClassNotFoundException. Uber preloads now run with the compile path at the working class dir. Source-only dependencies — and their transitive requires — therefore ride into the uber as classes, with no declaration.:rig/ns-compileentries now compile unconditionally, even when a preload already loaded them: asking for a compile means it. - Temurin's legacy JDK 8 asset names (
jdk8u492-b09) did not parse as a release version, so the managed-JDK resolver could not match its own installed JDK 8. They now parse as8.0.492+09.
v0.2.1 -- 2026-10-07#
Fixed#
v0.2.0 — 2026-10-06#
Everything merged since the first release.
New#
rig launch — the production entrypoint#
rig launch runs the built artifact with the production JVM flag set of
Rig: G1 (AlwaysPreTouch), exit on out-of-memory (with heap dump), and
loopback-only JMX on port 10101. You can override these per module via
:rig/launch-opts and per deployment via the RIG_LAUNCH_OPTS env var
(appended last, same override rules). It replaces its own process with
the JVM (process exec) — in a container the app is PID 1 and signals
reach the JVM directly. rig build bakes the launch plan (main, launch
opts, build JVM) into the jar of a module that declares a main, as
META-INF/rig/launch.json (uberjars pick it up too). So
rig launch <jar> works standalone, outside the workspace — for example
as a container ENTRYPOINT. It runs what Rig already built and cached.
The major version of the launch JVM must match the major version of the
build JVM. See
Configuration — Production launch
and Docker.
Native-image builds#
rig build --native compiles the locked classpath into a GraalVM
native-image binary (default target/<lib>, :rig/native-file, extra
arguments :rig/native-opts; the entry point is :rig/main). Rig
derives the GraalVM version from the :rig/jvm pin of the workspace
and records it in the lock ({vendor, requested}). It manages GraalVM
community JDKs itself, one per major version — rig graalvm install / list / uninstall / update, downloaded from the
graalvm/graalvm-ce-builds GitHub releases, sha256-verified, into the
state dir. The build never downloads: when Rig has installed no GraalVM
for the major, --native fails with a rig graalvm install <major>
hint. See
Configuration — Native images.
Leiningen migration#
rig migrate now converts a Leiningen workspace in place (--dry-run
first): project.clj → root and per-module deps.edn with :rig/*
keys. Rig materializes :managed-dependencies into the :deps of the
modules that inherit them. A declared version or source key wins over
the pool entry, and each win is a per-dep warning. It decomposes
:sub monorepos into per-module manifests with :local/root sibling
deps, and it merges :provided into the base (Leiningen has it active by
default). rig migrate reports unexpressible keys and blocking
problems (nested :sub, :sign-releases true, an s3p:// deploy
repository). After you migrate, run rig lock and rig check. See
Migrating from Leiningen for the full key
mapping.
Prep libraries and native javac#
Rig now honors the standard tools.deps :deps/prep-lib key. Before
rig build, rig test, rig run and rig repl, Rig runs the prep
function of the target module and of every local dependency module that
declares one, in dependency order. Rig checks each prep for staleness
(manifest hash, source digests, locked dependencies, function name),
and the clean step of the build never deletes what the prep :ensures.
If the prep is "javac my own sources", Rig does it natively. Rig itself
compiles modules that declare :rig/java-src-dirs, as part of prep, on
their locked classpath. javac runs before the prep function and before
the Clojure compile, so module code can reference its own Java classes.
A module declaring both runs the javac first, then the function, each
with its own staleness stamp. See Java sources.
AOT-compile in rig check and rig build#
Stage 2 of rig check now AOT-compiles the namespaces of each module in
dependency order, and it preloads the non-project requires first.
rig build compiles AOT the same way. Rig does not compile a namespace
again when a preload already loaded it, so its top level never runs
twice. The exception is the namespaces you list in :rig/ns-compile:
they compile unconditionally — asking for a compile means it. Code that
references a namespace it does not require — a bare ns/var, or a class
only produced by AOT-compiling a sibling namespace — previously failed
with a ClassNotFoundException. Both paths now compile it. New key
:rig/ns-compile lists extra namespaces to AOT-compile alongside the
sources of the module itself. An uber build additionally loads its
preloads through the working class dir. Dependencies that ship only
sources and no classes (org.clojure/tools.namespace, rewrite-clj)
therefore land in the uber as classes. Without them, the module's AOT
bytecode
references interfaces that exist nowhere on the artifact's classpath,
and the uber dies with a ClassNotFoundException when that code first
loads.
New configuration#
| Key | Declared in | Effect |
|---|---|---|
:rig/compile-jvm-opts |
workspace root | JVM flags for the build and check JVMs (the AOT build and the check load/AOT-compile). Version-sensitive flags like --enable-preview require a :rig/jvm pin; a lock with the flag and no pin is rejected. |
:rig/launch-opts |
module | JVM flags for rig launch; baked into the artifact's launch descriptor. |
:rig/javac-opts |
module | javac options; when the workspace pins :rig/jvm, --release <n> is prepended unless the opts already set the source level. |
:rig/native? / :rig/native-file / :rig/native-opts |
module | Native-image build: enable, output path, extra arguments. |
:rig/ns-compile |
module | Extra namespaces AOT-compiled alongside the module's own sources. |
:rig/timestamp-string |
module | Fixed zip entry timestamp (entries written name-sorted) — with a pinned :rig/jvm, the build output is byte-reproducible. Pre-1980 values are refused. |
Rig now documents and honors the standard tools.deps :jvm-opts key.
It applies to dev execution — rig run, rig repl, rig exec, rig test — and to the prep function of the module. It never applies to
rig build, rig check or rig launch
(JVM flags).
rig build now derives the directories it packages from the :paths of
the module, when :rig/artifact-dirs is absent. Previously it fell back
to the hardcoded ["src" "resources"], so a module with non-default
source directories built an empty jar. Rig renames :rig/src-dirs to
:rig/artifact-dirs, and the lock schema bumps to v2. Lock-consuming
commands reject v1 locks with an unsupported-version error. rig lock,
rig add, rig remove and rig update migrate a v1 lock in place. See
Lockfile — anatomy.
Changed#
- JVM version inputs are major-version only.
:rig/jvmand every JVM version input (rig jvm install,rig graalvm install) accept a major (feature) version —"21", not"21.0.10+7". The major is what matters for compatibility, and Rig manages the exact release itself. Thejvm/graalvmblocks indeps.lockno longer record an exactversion; lock files written by an earlier build keep theirs, which Rig now ignores — re-lock to rewrite them. - The rig-managed JDK takes precedence. When the lock pins a JVM,
Rig uses the rig-managed JDK for the major from the state dir, even
when the system
javahas the same feature version. A matching system JDK (JAVA_HOME, thenPATH) serves only when Rig installed no managed JDK. With no matching JDK anywhere, online Rig installs the newest matching release on demand; offline it fails with an install hint. - One JDK per major version.
rig jvm installno longer upgrades: when the store already holds a JDK for the major, it exits without changing anything.rig jvm updateandrig graalvm updateare now store-level commands: they move the rig-managed install for the pinned major of the workspace to the newest release, and they replace it. They install the newest release when the store holds none. They no longer writedeps.lock, and--frozendoes not apply to them. - The shared pool force-pins. A
:rig/depsentry now pins the coordinate's version workspace-wide: a module that consumes it transitively gets the pool version, like Leiningen's managed pool. A pool entry nothing consumed no longer holdsrig checkred. - A hung test run now dies.
rig testkills the runner JVM when an exec-fn has not finished in 30 minutes and fails the run —--timeoutconfigures it,0disables it. - Stricter CLI. Rig refuses
rig lock --frozen(writing the lock isrig lock's job), and refuses a repeated-p/--path— both usage errors (exit 2). Before, both quietly did something else. - The GitHub API lookup uses a token.
rig graalvm installandrig self-updateresolve releases through the GitHub API, which allows 60 requests per hour per IP address. Shared CI runners exhaust that pool. Rig sends the bearer token ofGH_TOKENorGITHUB_TOKEN(theghCLI's names, in that order) on the API request, and sends it to no other host. GitHub Actions exportsGITHUB_TOKENfor every step, so CI needs no configuration. Downloads and their.sha256sidecars carry no token. - In a container, the declared JDK in the image base is the build JVM.
The Production launch
image builds on the
eclipse-temurinbase with Rig copied in — noRUN rig jvm installstep. The runtime stage takes only therigbinary, andrig launchnever touches the resolver kernel or the runner.
Fixed#
- The AOT compile JVM. The kernel forks one JVM to compile the
namespaces of the module.
tools.buildchose that JVM from the environment:JAVA_CMD, then thePATHsearch, thenJAVA_HOME. The host JVM compiled the code, while the kernel itself ran on the JVM Rig used for the workspace. A green build said nothing about the pinned JVM. The kernel now starts the fork on its own JVM — the JVM Rig launched for the workspace.JAVA_CMDandJAVA_HOMEno longer select the fork's JVM. The build's kernel JVM also getsJAVA_HOMEof the managed JDK, as the run and test JVMs already got it. Code that starts a JVM at build time now sees the same JDK as inrig runandrig test. - Bytecode floor. The kernel jar of v0.1.0 compiled without a
--releasepin and shipped a Java 21-targetedMain.class— it failed to load on any:rig/jvmpin below 21. Rig now compiles the kernel with--release 8. After every assembly, Rig scans the release jars: any class above the floor fails the build (multi-release jar entries only where the JVM can load them). With a:rig/jvmpin,rig buildlikewise scans every class in the jar or uberjar it produces — dependency classes included — against the pinned JVM, and fails listing the offending entries. (javac does not receive--releaseon Java < 9 hosts, where the flag does not exist.) - Sources in built jars. The drop-sources step ran before the
compile and wiped every
.clj/.cljcfrom the class dir. A source shipped as payload — a clj-kondo hook export, adeps-newtemplate — therefore vanished from the published jar. It now runs after the compile and drops only the sources paired with their AOT__init.class— the load-time recompile hazard (two class identities,ClassCastException) it exists to kill. Everything else ships as the data it is. The build never drops prep output under an:ensurepath — it is an input of the build, not its output. - What the AOT plan compiles. Rig treated every
.cljunder the:pathsroots as a namespace. A data source (a config map without annsform), a file nested under a resources root, or a namespace requiring itself killedrig checkandrig buildoutright. The plan now keeps only the files whose declared namespace matches their path, ignores self-requires, and reports what it left out —rig checkshows aplan-skipwarning instead of dying. - Java modules ran without their own classes. Rig compiled a module
declaring
:rig/java-src-dirsinto its class dir, then kept that dir off the dev classpath unless:pathsnamed it too.rig check,rig testandrig rundied with aClassNotFoundExceptionon the module's own Java classes. The class dir now follows the module's sources onto the classpath. - Class-dir clean. The clean step of the build now deletes only
class-dir content that is not under a declared
:ensurepath. A module whose prep owned the class dir previously lost the whole dir on every build. The jar shipped without the classes of the prep, and dependent builds failed withClassNotFoundException. The staleness check did not notice this. - Lock resolver identity. The CLI now stamps the
resolverblock of the lock at lock time — the kernel pin (lib/version/git sha) plus the sha256 of the kernel jar it actually launched. Before, the kernel self-reported from a build-info resource baked into the jar, which fell back to a placeholder (v0.1.0 + zeroed sha) whenever the resource was absent.rig infoshows the short jar sha when present. Older locks load unchanged. - Non-module
:local/rootdeps. A:local/rootdep can point at a directory with its owndeps.ednthat is not a declared workspace module (a dev/test overlay). Rig now locks such a dep as its own module entry and follows nested refs — its sources reach the classpath. Previously the lock absorbed it into the longest-prefix module, andrig testfailed withexec-fn not found. An overlay is never a build/test/publish target. - Local module beats published coordinate. When the same library
enters a basis as both a
:local/rootand a:mvn/versionrequirement, the local module now wins in either direction. This case typically arises when a transitive POM references the published artifact of the module itself. Vanilla tools.deps cannot order the pair and fails. The lock records the local module, and the published coordinate never enters it. rig check:- a dependency declared with a classifier, for example
org.apache.kafka/kafka-clientswith a$testclassifier, no longer reports a spuriousstale-lock … lock has no pin. The pin key now includes the classifier. - unqualified dependency names (
aleph) normalize to the qualified form the lock pins (aleph/aleph), with a$classifiersuffix preserved. - Stage 2 now walks
.cljcsources — previously it walked only.clj, so an unloadable.cljcnamespace reported a vacuous ok. rig checkno longer requiresdata_readers.clj(a top-level map at a source root — theclojure.mainclasspath convention) as a namespace.rig checkreports a module that pinsorg.clojure/clojurebelow 1.8.0 asclojure-floor, before it launches any JVM (the same gate applies torig test).- with no lock at all, one message, exit 3 (the lock-problem code) — previously one stale-lock problem per module, exit 1.
- a dependency declared with a classifier, for example
- Cache entries carry their extension. An artifact sat in the cache
at
<sha256>with no suffix. The JDK therefore handed consumersfile:URLs for classpath jars, and resource copying out of them broke (a kaocha-cljs run died onENOENT). Cache names now end in the lock's extension; existing caches rename into the layout on first hit. rig testprints like the REPL. Runner code saw*print-namespace-maps*false where the same classpath underclojure -Mor lein sees true — the runner now runs underclojure.main's bindings.- Publishing. Rig drops direct-to-S3 publishing (
s3p://repositories): publishing targets http/https Maven repositories only (S3 is what Pier provides).rig migratereports a deploy repository with ans3p://URL as a blocking problem. - Manifest parsing. The compact macro for namespaced maps
(
{ns/…}) now parses;rig migratenormalizes it to the non-compact form. rig versionno longer prints a blank line (exit 0) when the lock records an empty version — it reports "no version found" (exit 1).:rig/version-fn :git-count-revstrims the version template's trailing newline out of the version — it used to name the artifactapp-1.0.1<newline>.jar.- Leiningen migration, continued. A bare exclusion symbol
(
:exclusions [foo]) now migrates tofoo/foo, like Leiningen reads it — verbatim, tools.deps silently excluded nothing. Rig warns about dropped Leiningen keys on the profile that carries them, and an entry appended to a manifest lands on its own line. rig buildwarns when:rig/uber-opts :excludeexcludes nothing.tools.buildmatches patterns withre-matches(full match), so a prefix-style^clojure/silently shipped everything; onlydir/.*actually excludes a dir. Every pattern that matches no entry, and every:rig/uber-optskey Rig does not consume, now gets named in a warning.
Docs#
- New pages: Migrating from Leiningen and
Java sources (native javac,
:deps/prep-lib,:rig/javac-opts). New reference sections: JVM flags, Native images and Production launch. The lockfile anatomy page now covers v2. The Docker entrypoint pattern usesrig launch. - Messaging rework across the site.
Under the hood#
- Hot commands (
rig test,rig checkstage 2) now run on a separate pinned runner jar (rig-runner), an install-time artifact shipped next to the kernel jar. The Docker image pre-seeds it, sotestandcheckwork on read-only stores. When the runner fails, Rig fails.rig testandrig checkexit 1; any other runner exit code becomes a plain Rig error. The dev commands (run,repl,exec) pass the child code through verbatim. - CI now builds the kernel and runs its test suite on Temurin 8 — the floor gate that keeps the kernel jar loadable on the oldest supported JVM. CI fixed the tag-trigger behavior.
- The release workflow now propagates the released version into the
README and docs examples (install one-liner,
self-update --versionexamples), replacing the hand-pinning that had left stalev0.2.0examples in the docs. - Rig removes
rig release(breaking). A release is now a version commit plusrig publishplus a tag — the version history and the tag are yours, and Rig does the publish. See Build, publish, release. rig migratedrops the legacy:exoscale.project/extra-clean-targetswith a warning: Rig has no:rig/clean-dirskey,rig cleanremoves the target dirs and nothing else.
v0.1.0 — 2026-09-25#
GitHub release. First release of Rig — build and run Clojure projects the obvious way: one binary, one manifest, one lockfile, one way to do each thing.
- One binary, one manifest namespace, one lockfile. All tool
configuration lives under
:rig/*in thedeps.ednfiles you already have; standard tools.deps keys, aliases and CI keep working as-is.deps.lockpins every artifact in the resolved dependency tree by sha256. Classpaths reference only the lock (the closed build), and Rig hash-verifies every artifact before it enters a classpath. - Security at the adoption moment. A cooldown window refuses
freshly published versions (default 48h, per-repo overrides,
--force), andrig verifyis the CI security gate. Rig supports repositories behind an OIDC gate (:auth :oidc,rig auth get). - The full command set. Locking and dependency changes:
lock,update,add,remove. Consistency and execution:verify,check(lock-vs-manifest consistency plus namespace load per module),test,run,repl,exec. Artifacts:build [--uber],install,publish,release. Inspection:outdated,tree,info,version. Hygiene:clean,lint,fmt. Project creation:new,new-module. Legacy conversion:migrate, for:exoscale.*/:slipset.*workspaces. - JVM management.
:rig/jvmpins the JVM of the project (Temurin). The lock records the exact release, and Rig installs missing JDKs into its state dir, hash-verified. - Installation. A pinned one-liner installs from the GitHub release
artifacts (sha256-verified against the
SHA256SUMSof the release). Theghcr.io/brutasse/rigDocker image ships multi-arch, with the kernel pre-baked and a Temurin 21 base.rig self-updatechecks for a release at most once per 24h.