Version resolution¶
How Rig picks versions, and why the answer is the answer of the ecosystem — plus exactly where Rig adds its own layer on top.
The graph is tools.deps'¶
Rig does not re-implement Maven. For every module and alias, the kernel
calls clojure.tools.deps/create-basis on the directory of the module —
the same function the clojure CLI uses. POM fetching, transitive
dependencies, nearest-wins, exclusions, and repository precedence are
tools.deps semantics, unmodified. Rig adds one exception on top of the
Maven comparator: a local module beats a published coordinate
(local modules beat published
coordinates).
What Rig owns is a single narrow question: which exact version does a
floating requirement (RELEASE/LATEST) mean today? That is the one
place where the answer of the ecosystem itself changes over time. It is
also the one Rig wants to gate, record, and keep stable across re-locks.
The flow of rig lock¶
- Read the manifests — Rig hashes each
deps.ednas raw bytes, for staleness, and parses it. Legacy keys refuse the workspace. - Select versions — per module,
select-versionspicks the concrete version for each floating requirement (below). - Substitute in memory —
apply-versionsrewrites the selected coordinates in the in-memory manifest map only. Resolution never touches thedeps.ednon disk. - Resolve the graph —
create-basisper module (base classpath, then each alias), now that every version is concrete. - Classify the classpath —
plan/map-classpathmaps every classpath root to a lock entry. A~/.m2artifact path becomes an artifact id, a~/.gitlibscheckout becomes a git dep, and a workspace module becomes a local reference. Anything else is an error: a classpath root that is not an artifact, a git dep, or a declared module cannot enter the lock. - Write the lock — the kernel returns the plan without hashes.
The Go binary fetches and hashes every artifact, then writes
deps.lock(the closed build).
Version selection (select-versions)¶
Per floating coordinate, in order of precedence:
- Requirement override — an explicit version passed to the command,
for example
rig update org.clojure/core <v>. It always wins, and Rig records it in the lock asexplicit. - Existing lock pin — by default (
respect-existing-pins), a coordinate already pinned indeps.lockkeeps that version. This is what makes re-locks stable:rig locknever bumps a version you already use, andrig updateof one coordinate keeps the rest. - Native resolution — when all cooldowns of the workspace are
zero, Rig leaves the floating requirement to
tools.depsitself. Rig adds nothing, and the result is the answer of the ecosystem, untouched. - Cooldown-gated selection — otherwise Rig looks at the version candidates of the repository and picks the newest one old enough (below).
Candidates and ordering: the ecosystem's data, the ecosystem's comparator¶
- Candidates come from the standard
maven-metadata.xmleach repository serves for the coordinate — the same file Maven and tools.deps read.LATESTuses the<latest>element of the metadata. It falls back to all listed versions when a repository publishes none.RELEASEuses all listed versions. - Ordering uses
DefaultArtifactVersion, the Maven version comparator — the same ordering the rest of the ecosystem sees, so "newest" means what it means everywhere else. - Publication time, for the cooldown, comes from the per-repository
maven-metadata-<repo-id>.xmlthat Maven repositories publish. It holds a<updated>timestamp per version, and falls back to thelastUpdatedof the metadata.
Rig refuses a candidate published less than the per-repo cooldown before
resolve time, and the next-newest eligible version wins. Rig records
every refused version in the skipped list of the lock, with its reason
(cooldown, forced, explicit). You see one line per decision. Rig
refuses a coordinate with no eligible candidate outright. rig lock
exits 5, and the retry is --force (Rig records it as forced). The
mechanics and the escape hatches are on the
security model page.
Repository semantics¶
- The default repositories are central and clojars — the same pair tools.deps starts from.
- A
:mvn/reposentry whose id matches a default replaces the built-in — the same rule tools.deps applies — so each id appears once. - Which repository serves a locked artifact — Rig decides this at
lock time and pins the answer into the lock (
repository+url). It checks in order: first the repository Maven itself recorded in~/.m2/…/_remote.repositories(offline and exact), then a HEAD probe of each declared repository, then the first one. From then on, the hot build fetches from the recorded URL and never re-probes (the closed build). - Credentials for private repositories come from
~/.m2/settings.xml, the same file Maven and tools.deps use. The local auth proxy of Rig serves:auth :oidcrepositories instead.
Ecosystem interop¶
- Each module stays independently resolvable.
clojure -Sdeps, editors, and any other tooling read thedeps.ednexactly as before — the pins of Rig live in the lock, not in the manifest. Resolution never writes the manifest on disk; the only writers are the explicitadd/remove/updatecommands (edit-depop, format-preserving viarewrite-clj+ cljfmt) andmigrate. - Git dependencies use the
tools.deps/tools.gitlibsconvention (~/.gitlibs/libs,group/name:<short-sha>:jarcoordinates), so Rig shares checkouts with theclojureCLI and never duplicates them. - Rig reuses
~/.m2/repositoryas an artifact source. It imports an artifact into the Rig cache only when the hash of its bytes matches. rig outdatedcompares the pins of the lock against the samemaven-metadata.xml. It reportslatest,latest-satisfying(same major), and whether the jump breaks compatibility, from the same candidate pool that selection uses.
Deliberate deviations¶
Cooldowns gate selection only. They change which version Rig picks
for a floating requirement — never the graph itself, never a concrete
requirement, never the order tools.deps computes. The decision adds
information, and Rig records it: skipped in the lock,
refused/forced on stderr. Set every cooldown to "0s". Then the
selection layer of Rig becomes the identity: what you get is exactly
what clojure would give you.
Local modules beat published coordinates¶
The second deviation lives in the graph, not in selection. When the same
library enters a basis as both a :local/root requirement and a
:mvn/version requirement, vanilla tools.deps cannot order the two, and
resolution fails. This case typically arises when a transitive POM
references the published artifact of the module itself. Rig registers
compare-versions methods for the cross-type pair so that the local
module dominates, in either direction. The lock records the local
module, and the published coordinate never enters it.
The ranking follows the own dominance rule of tools.deps: strictly-greater wins. So both directions agree, and the ranking matches what a workspace implies. The module is source you control, and a published artifact under the same coordinate must not shadow it.