Skip to content

kargo.lock

kargo.lock is written by Kargo after resolving dependencies. Commit it for applications and libraries alike: it makes builds reproducible and detects tampered downloads.

For every resolution (one per target and classpath, such as jvm/runtime):

  • a fingerprint of the inputs (requested dependencies, BOMs, repositories, and the version of Kargo’s resolver). The repositories are the ones kargo.toml declares, so a mirror or --vendored keeps the lock,
  • the root modules, each once,
  • every resolved module with its version and the dependencies it asked for,
  • the repository path and SHA-256 of every downloaded file.

kargo bench pins the libraries of the benchmarks under jvm/bench (the dependencies, dev-dependencies, kotlinx-benchmark and JMH) and JMH’s harness generator under jvm/bench-tools.

The JS target’s resolutions are js/main and js/test, like the Wasm target’s wasm/main and wasm/test.

The web resources of Wasm libraries from a repository are pinned under wasm/web-resources (and wasm/web-resources-test for the tests): one entry per library that ships them, with its -web-resources.zip. A project whose libraries ship none has no such entry.

A [[generate]] step that runs a tool, or the protobuf built-in, pins what it downloads under generate/<name>: the tool and its dependencies, or protoc and the gRPC plugins.

What the build downloads and runs is pinned too, under tools/, so a changed or tampered compiler or plugin fails the build like a changed dependency does:

Key What it pins
tools/kotlin-compiler The Kotlin compiler (kotlin-compiler-embeddable) and the Build Tools API that incremental compilation uses, with their dependencies. Packages built together share one Kotlin version, so a workspace has one entry
tools/<artifact> of a compiler plugin The compiler plugin of a framework, such as tools/kotlin-serialization-compiler-plugin-embeddable
tools/junit-platform-console-standalone The JUnit console launcher that runs the JVM tests
tools/ksp The KSP tool and its dependencies, when a ksp dependency needs it
tools/dokka-cli, tools/dokka-plugins Dokka and its plugins, once kargo doc ran
tools/ktlint-cli, tools/detekt-cli ktlint and detekt, once kargo fmt or kargo lint ran

Each entry is recorded and checked like a dependency resolution, and --locked fails when one is missing or out of date. kargo fetch locks the tools of the enabled targets (--tools adds Dokka, ktlint and detekt), kargo update locks again the ones the lockfile had, and a build locks any other the first time it runs it. A Compose Desktop jar for other platforms (kargo package --platforms) pins the Skiko natives it adds the same way, as tools/skiko-awt-runtime-<platform>. In a workspace, every entry but the compiler’s is keyed with its package’s name, like the resolutions.

A profile with dependencies of its own has resolutions of its own, named after it: jvm/runtime@staging is the runtime classpath of the staging profile. The profiles without such dependencies all share jvm/runtime.

kargo.lock
# This file is automatically generated by Kargo.
# It is not intended for manual editing.
version = 1
[resolution."jvm/runtime"]
inputs = "4f9c..."
roots = ["org.jetbrains.kotlinx:kotlinx-datetime"]
[[resolution."jvm/runtime".package]]
module = "org.jetbrains.kotlinx:kotlinx-datetime-jvm"
version = "0.7.1"
dependencies = ["org.jetbrains.kotlin:kotlin-stdlib:2.1.20"]
[[resolution."jvm/runtime".package.files]]
path = "org/jetbrains/kotlinx/kotlinx-datetime-jvm/0.7.1/kotlinx-datetime-jvm-0.7.1.jar"
sha256 = "b3a1..."

A module is locked once, at one version, and lists all of its files. A module declared with a classifier, such as a native library, lists its main jar and the classifier jar:

kargo.lock
[[resolution."jvm/runtime".package]]
module = "org.lwjgl:lwjgl"
version = "3.3.6"
[[resolution."jvm/runtime".package.files]]
path = "org/lwjgl/lwjgl/3.3.6/lwjgl-3.3.6.jar"
sha256 = "5c7e..."
[[resolution."jvm/runtime".package.files]]
path = "org/lwjgl/lwjgl/3.3.6/lwjgl-3.3.6-natives-windows.jar"
sha256 = "91d0..."
  • If the inputs of a resolution haven’t changed, Kargo reuses the locked result without touching the network for metadata.
  • Every artifact is checked against its locked SHA-256 before it is used. Missing files are downloaded and checked. A cached file in ~/.kargo/cache/maven that doesn’t match is an error: Kargo doesn’t use it, and doesn’t replace it either.
  • Hashing a large dependency tree on every build is slow, so Kargo keeps each cached file’s hash in a .stamp file next to it, and reuses that hash while the file’s size and modification time are unchanged. A copy whose content was replaced without changing its size or modification time is therefore only caught by --locked, which hashes every cached file.
  • When kargo.lock is rewritten (kargo add, kargo update, a manifest change), cached artifacts are checked against the checksum their repository publishes, in parallel. A file that is unchanged since it was last checked (a .verified stamp next to it records its size and modification time) or whose SHA-256 equals its entry in the previous lockfile is not checked again, so adding a dependency does not re-verify the whole tree over the network.
  • Downloads are also checked against the checksum the repository publishes next to them: the .sha256 file when there is one (Maven 3.9 and Gradle publish it), else the .sha1. A repository on disk that has neither vouches for its own copy. A file without any published checksum is accepted, and -v prints Unverified for it; --require-checksums makes that an error.
  • When the lockfile is written (the first build, or kargo update), a file that was already in ~/.kargo/cache/maven is checked against the repository’s checksum too. A corrupt cached file (for example, left by an interrupted download) is downloaded again, so a bad file is never locked.
  • The cache keeps the files of each repository apart. Files from Maven Central and Google (or their mirrors) are stored by their plain Maven path, ~/.kargo/cache/maven/<group>/<artifact>/<version>/, since a release there never changes. Everything else, including every SNAPSHOT, is stored in a directory of its repository, ~/.kargo/cache/maven/@<name>-<hash of the URL>/<group>/.... So a private or forked artifact that reuses the coordinates of a public one never replaces it, and one repository’s copy is never used for another. kargo.lock lists the plain Maven path without the repository directory, and a locked build uses the cached copy that matches the lock. Files cached by an older Kargo that are not from Central or Google are downloaded again once.
  • When you change dependencies in kargo.toml, only the affected resolutions are recomputed.
  • The file is only rewritten when its content changes.

A SNAPSHOT keeps its version (1.0-SNAPSHOT), but the files it locks have the timestamped names the repository stores, so the lockfile records which build you use:

kargo.lock
[[resolution."jvm/runtime".package]]
module = "com.acme:lib"
version = "1.0-SNAPSHOT"
[[resolution."jvm/runtime".package.files]]
path = "com/acme/lib/1.0-SNAPSHOT/lib-1.0-20240101.120000-1.jar"
sha256 = "9d2e..."

The build stays locked until kargo update (which prints Updating com.acme:lib v1.0-SNAPSHOT (20240101.120000-1) -> v1.0-SNAPSHOT (20240102.080000-2)) or --refresh picks a newer one. A selective kargo update <group:artifact> keeps every other SNAPSHOT on its locked build. See SNAPSHOT versions.

Packages built together share one kargo.lock: the workspace’s, at its root, or the consuming package’s when it has path dependencies outside a workspace. Each package’s resolutions are keyed with its name, such as mathlib/jvm/runtime. Outside a workspace, the consuming package keeps the plain keys (jvm/runtime). kargo update re-resolves every package of the project.

When a file doesn’t match kargo.lock, the error is checksum mismatch for .... For a cached file, it names the file to delete. Kargo never uses such a file, so deleting it is the way to get a fresh copy: the next build downloads it again and checks it against the lockfile. If the download is also a mismatch, the error says which side is suspect:

  • If the downloaded file matches the checksum the repository publishes, the lockfile entry is likely wrong, for example because it was recorded from a corrupt cached file. If you trust the repository, run kargo update to re-lock it.
  • If the repository publishes no checksum, Kargo can’t tell: the artifact may have changed upstream or been tampered with, or the lockfile entry may be wrong. Investigate before running kargo update.

A repository that publishes no checksum for a file gives Kargo nothing to check the download against before it is locked. --require-checksums (or KARGO_REQUIRE_CHECKSUMS=1) makes such a download an error, which suits CI:

Terminal window
kargo build --locked --require-checksums

A file that kargo.lock already pins is checked against the lock instead, so a locked project builds even from a repository without checksums. Offline, a cached file that was never checked against its repository can’t be locked with --require-checksums; run once online first.

Command Effect
kargo update Re-resolve everything and rewrite the lockfile (picks up new versions within ranges and the newest build of each SNAPSHOT)
kargo update <group:artifact>... Re-resolve only these modules; every other module keeps its locked version
kargo update <group:artifact> --precise <version> Lock one module at exactly this version. It stays locked until kargo.toml changes or you update that module again
kargo update --dry-run Print what would change (Updating, Adding, Removing) without writing the lockfile
--locked Fail if the lockfile would change, and hash every cached artifact instead of trusting its .stamp. Use it in CI.
--refresh Ignore the lockfile and cached metadata for this run, and re-verify cached artifacts against the repository (re-downloading corrupt ones)

If the lockfile is corrupt or from an unsupported version, delete it; Kargo regenerates it on the next build.

A Kargo release that changes how dependencies resolve changes the inputs fingerprint, so the affected resolutions are computed again the first time you build with it. With --locked that is an error: after upgrading Kargo, build once without --locked and commit the updated kargo.lock.