Simple SBT dependency lock plugin.
SBT version support:
| Supported major SBT version | Built against concrete SBT Version | Build JDK version |
|---|---|---|
| 1 | 1.4.5 | 17 |
| 2 | 2.0.0-RC12 | 17 |
If your SBT version is earlier than the build SBT version, the plugin might still work, but the support is not guaranteed.
The primary use case for the plugin:
- In a multi-module project for some modules, which produce important artifacts (e.g. Docker images for services), we want to track a full set of dependencies, including transitive ones.
- For those modules, we want to make dependency changes visible and easily reviewable in PRs / MRs.
What's different compared to stringbean/sbt-dependency-lock:
sbt-kagi-pluginis enabled explicitly per-module, not by default for all.- For simplicity only a single classpath (e.g. one cross Scala version +
Test,Compile,Runtime) per module supported. - Concise lock file format, geared towards readability in change reviews. For instance, it uses one line per dependency, full coordinates are repeated for each dependency line.
- Simplified file format also means JAR file checksums are not supported.
- By default,
sbt-kagi-pluginstores all the lock files in a single directory nameddependency-lock- for easier bulk inspection and comparison between modules.
Dependency lock file format example:
# Dependency lock file generated by sbt-kagi-plugin
# Run "sbt kagiDependencyLockWrite" to update
# DO NOT EDIT MANUALLY!
# ch.qos.logback
ch.qos.logback:logback-classic:1.5.21
ch.qos.logback:logback-core:1.5.21
# co.fs2
co.fs2:fs2-core_2.13:3.12.2
co.fs2:fs2-io_2.13:3.12.2
# com.chuusai
com.chuusai:shapeless_2.13:2.3.13
# com.fasterxml.jackson.core
com.fasterxml.jackson.core:jackson-annotations:2.14.3
com.fasterxml.jackson.core:jackson-core:2.14.3
com.fasterxml.jackson.core:jackson-databind:2.14.3
# com.fasterxml.jackson.datatype
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.3
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.3
To get started, add the following line to your project/plugins.sbt:
addSbtPlugin("com.evolution" % "sbt-kagi-plugin" % "LATEST_VERSION")Replace LATEST_VERSION with the latest version of the plugin, which you can find
on Maven Central.
Enable KagiPlugin on the modules you want to lock dependencies for:
lazy val myProject = project
.in(file("."))
.enablePlugins(KagiPlugin)
.settings(
libraryDependencies += "com.google.guava" % "guava" % "33.4.0-jre",
)Generate lock files for your project by running:
sbt kagiDependencyLockWrite
This creates a dependency-lock/<module-name>.lock.txt file for each module with the plugin enabled.
The lock file records all resolved external dependencies and their versions.
To verify that current dependencies match the lock file, run:
sbt kagiDependencyLockCheck
If the dependencies have drifted from the lock file, the build will fail with a detailed diff:
[error] dependency lock file not in sync - 2 changes: /path/to/dependency-lock/root.lock.txt
[error] + com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
[error] * com.google.guava:guava:33.3.0-jre -> 33.4.0-jre
Lock files are plain text and VCS-friendly. Commit them to your repository so that dependency changes are visible in pull requests and code reviews.
The sbt-kagi-plugin works by resolving the external dependency classpath for each module (by default, the Runtime
configuration) and recording it in a human-readable lock file.
The lock file format groups dependencies by their group ID:
# Dependency lock file generated by sbt-kagi-plugin
# Run "sbt kagiDependencyLockWrite" to update
# DO NOT EDIT MANUALLY!
# com.google.guava
com.google.guava:failureaccess:1.0.2
com.google.guava:guava:33.4.0-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
When kagiDependencyLockCheck runs, it resolves the current classpath and compares it to the lock file.
Any additions, removals, or version changes are reported as errors.
By default, the plugin locks the Runtime classpath.
To lock only the Compile classpath (excluding runtime-only dependencies), override the kagiDependencyLockClasspath
setting:
lazy val myProject = project
.in(file("."))
.enablePlugins(KagiPlugin)
.settings(
kagiDependencyLockClasspath := Compile,
)By default, lock files are stored in dependency-lock/ at the build root. To change this:
ThisBuild / kagiDependencyLockDir := (ThisBuild / baseDirectory).value / "locks"Add kagiDependencyLockCheck to your CI pipeline to catch unintended dependency changes:
# GitHub Actions example
- name: Check dependency lock
run: sbt kagiDependencyLockCheckEnable KagiPlugin on each module you want to lock. Each module gets its own lock file named after the module:
lazy val core = project
.in(file("core"))
.enablePlugins(KagiPlugin)
.settings(/* ... */)
lazy val app = project
.in(file("app"))
.enablePlugins(KagiPlugin)
.dependsOn(core)
.settings(/* ... */)This produces dependency-lock/core.lock.txt and dependency-lock/app.lock.txt.
JDK 17 is required to build the project, due to sbt 2 having 17 as the minimal JDK version.
To reformat code using scalafmt, run:
sbt fmtCode formatting is verified in build commands, and PRs with malformed code will not be accepted.
A fast build without scripted tests can be run with:
sbt buildFastA full build with scripted tests can be run with:
sbt buildFullDO NOT BREAK BINARY AND SOURCE COMPATIBILITY!
Binary compatibility is checked on every build automatically, but for the source compatibility you need to watch for it yourself.
- Being on the up-to-date main branch, create a release tag:
git tag v1.2.3- Push the tag:
git push origin v1.2.3- This will trigger the release GitHub Action. If it succeeds, the release ends up on Maven Central with GitHub release notes generated automatically from PRs info.
- If the release GitHub Action fails, the tag will be deleted on remote. After deleting the tag locally, fix the main branch and do the process again:
git tag -d v1.2.3-
kagiDependencyLockDir: SettingKey[File]Directory where lock files are stored. Defaults to
dependency-lock/at the build root. -
kagiDependencyLockFile: SettingKey[File]Path to the lock file for the current module. Defaults to
<kagiDependencyLockDir>/<module-name>.lock.txt. -
kagiDependencyLockClasspath: SettingKey[Configuration]Configuration used to resolve the dependency classpath for lock file generation. Defaults to
Runtime.
-
kagiDependencyLockWrite: TaskKey[Unit]Resolve the dependency classpath and write (or overwrite) the lock file for the current module.
-
kagiDependencyLockCheck: TaskKey[Unit]Resolve the dependency classpath and compare it to the existing lock file. Fails the build if they differ.