alejandrohdezma / sbt-dependencies   0.19.2

Apache License 2.0 GitHub

Manage SBT dependencies from a single YAML file with version markers, auto-updates, and cross-project support

Scala versions: 2.12
sbt plugins: 1.x

Manage SBT dependencies from a single YAML file with version markers, auto-updates, and cross-project support

sbt-dependencies

Installation

Add the following line to your project/project/plugins.sbt file:

addSbtPlugin("com.alejandrohdezma" % "sbt-dependencies" % "0.19.2")

Adding the plugin to project/project/plugins.sbt (meta-build) allows it to manage both your build dependencies and your project dependencies.

Usage

This plugin manages your project's dependencies through a single project/dependencies.conf file. Instead of declaring libraryDependencies and addSbtPlugin in your build files, you list all dependencies in HOCON format grouped by project name:

sbt-build = [
  "ch.epfl.scala:sbt-scalafix:0.14.5:sbt-plugin"
  "org.scalameta:sbt-scalafmt:2.5.4:sbt-plugin"
]

my-project = [
  "org.typelevel::cats-core:2.10.0"
  "org.scalameta::munit:1.2.1:test"
]

The plugin automatically populates libraryDependencies for each project based on its group and provides commands to install, update, and validate dependencies.


VS Code / Cursor Extension

An extension for VS Code and Cursor is available that provides syntax highlighting for dependencies.conf files. It highlights organizations, artifacts, versions, version markers, configurations, variable references, and comments.

See the extension's README for installation and usage instructions.

How to...

Migrate an existing project

Run initDependenciesFile to automatically generate dependencies.conf from your current libraryDependencies and addSbtPlugin settings:

sbt> initDependenciesFile

After running this command, remove the libraryDependencies += and addSbtPlugin lines from your build files, as the plugin will now manage them via dependencies.conf.


Define dependencies

Create a project/dependencies.conf file listing your dependencies. Groups correspond to:

  • sbt-build: Dependencies for your build definition (plugins and libraries used in build.sbt)
  • <project-name>: Dependencies for a specific project (matches the SBT project name)

Dependencies follow this format:

org::name:version:config   # Cross-compiled (Scala) dependency with configuration
org::name:version          # Cross-compiled (Scala) dependency
org::name                  # Cross-compiled, latest version resolved automatically
org:name:version:config    # Java dependency with configuration
org:name:version           # Java dependency
org:name                   # Java, latest version resolved automatically

Supported configurations: compile (default), test, provided, sbt-plugin, etc.


Use cross-compiled (Scala) dependencies

Use :: (double colon) between organization and artifact name to indicate a Scala cross-compiled dependency. This is equivalent to %% in SBT:

my-project = [
  "org.typelevel::cats-core:2.10.0"   # Cross-compiled (like org.typelevel %% "cats-core")
  "com.google.guava:guava:33.0.0"     # Java dependency (like com.google.guava % "guava")
]

Filter dependencies by Scala version

Dependencies with Scala version suffixes in their artifact name are automatically filtered based on the current scalaVersion:

my-project = [
  "org.example:my-lib_2.13:1.0.0"  # Only added when scalaVersion is 2.13.x
  "org.example:my-lib_2.12:1.0.0"  # Only added when scalaVersion is 2.12.x
  "org.example:my-lib_3:1.0.0"     # Only added when scalaVersion is 3.x
  "org.example:other-lib:1.0.0"    # Always added (no suffix)
]

This is useful for dependencies that are published with Scala-specific variants but aren't cross-compiled in the usual way (e.g., some native libraries or Java libraries with Scala-specific modules).


Pin a dependency to a specific version

Control how dependencies are updated using version markers:

Marker Example Behavior
(none) 2.10.0 Update to latest compatible version
= =2.10.0 Pin to exact version, never update
^ ^2.10.0 Update within major version only (2.x.x)
~ ~2.10.0 Update within minor version only (2.10.x)
my-project = [
  "org.typelevel::cats-core:2.10.0"    # Will update to any newer version
  "org.typelevel::cats-effect:=3.5.0"  # Pinned, never updated
  "co.fs2::fs2-core:^3.9.0"           # Updated within 3.x.x only
  "io.circe::circe-core:~0.14.6"      # Updated within 0.14.x only
]

Add a note to a pinned dependency

When pinning a dependency with a version marker, you can add a note explaining why using an object format:

my-project = [
  { dependency = "org.typelevel::cats-core:=2.10.0", note = "v3 drops Scala 2.12" }
]

Long notes are automatically formatted across multiple lines:

my-project = [
  {
    dependency = "org.http4s::http4s-core:~0.21.34"
    note = "Pinned to 0.21.x because the EntityEncoder changes in 0.22 require a full rewrite of our streaming layer"
  }
]

Both formats (plain strings and objects with notes) can coexist in the same dependency list. Notes are preserved through updateDependencies — only the version is updated while the note remains unchanged.


Mark a dependency as intransitive

Use the object format with intransitive = true to exclude transitive dependencies:

my-project = [
  { dependency = "org.http4s::http4s-core:0.23.3", intransitive = true }
]

This applies .intransitive() to the ModuleID, preventing SBT from pulling in its transitive dependencies.

You can combine intransitive with a note to document why:

my-project = [
  { dependency = "org.http4s::http4s-core:0.23.3", intransitive = true, note = "We only need the core types" }
]

Both single-line and multi-line formats are supported. Long entries are automatically formatted across multiple lines:

my-project = [
  {
    dependency = "org.http4s::http4s-core:0.23.3"
    note = "We only need the core types, transitive deps conflict with our custom HTTP layer"
    intransitive = true
  }
]

The intransitive flag is preserved through updateDependencies — only the version is updated.


Use shared version variables

You can use variable syntax to reference versions defined (or computed) in your build:

my-project = [
  "org.typelevel::cats-core:{{catsVersion}}"
  "org.typelevel::cats-effect:{{catsVersion}}"
]

Define variable resolvers in your build.sbt:

dependencyVersionVariables := Map(
  "catsVersion" -> { artifact => artifact % "2.10.0" }
)

When running updateDependencies, variable-based dependencies show their resolved version and the latest available version, but the variable reference is preserved in the file.

Using with here-sbt-bom

The here-sbt-bom plugin reads Maven BOM files and exposes version constants. You can reference these in your dependencies.conf:

my-project = [
  "com.fasterxml.jackson.core:jackson-core:{{jackson}}"
  "com.fasterxml.jackson.core:jackson-databind:{{jackson}}"
]
// build.sbt
val jacksonBom = Bom("com.fasterxml.jackson" % "jackson-bom" % "2.14.2")

dependencyVersionVariables := Map(
  "jackson" -> { artifact => artifact % jacksonBom.key.value }
)

Configure Scala versions

You can configure scalaVersion and crossScalaVersions directly in dependencies.conf using the advanced format:

sbt-build {
  scala-versions = ["2.13.12", "2.12.18"]
  dependencies = [
    "ch.epfl.scala:sbt-scalafix:0.14.5:sbt-plugin"
  ]
}

my-project {
  scala-version = "3.3.1"
  dependencies = [
    "org.typelevel::cats-core:2.10.0"
  ]
}

Use scala-version (singular) for a single version or scala-versions (plural) for cross-building.

Behavior:

  • The first version becomes scalaVersion
  • All versions become crossScalaVersions
  • scala-version/scala-versions in the sbt-build group applies at the build level (ThisBuild / scalaVersion and ThisBuild / crossScalaVersions)
  • scala-version/scala-versions in individual project groups overrides the build-level settings for that project

This allows you to set a default Scala version for all projects while letting specific projects use different versions.


Use the advanced group format

Groups support an advanced format that enables additional configuration beyond just listing dependencies:

my-project {
  scala-versions = ["2.13.12", "2.12.18", "3.3.1"]
  dependencies = [
    "org.typelevel::cats-core:2.10.0"
    "org.scalameta::munit:1.2.1:test"
  ]
}

The simple format (array of dependencies) and advanced format (object with dependencies key) can be mixed in the same file.


Install a new dependency

Use install to add a new dependency to the current project's group in dependencies.conf:

sbt> install org.typelevel::cats-core:2.10.0
sbt> install org.typelevel::cats-effect  # Resolves latest version
sbt> install org.scalameta::munit:1.2.1:test

Install a build dependency

Use installBuildDependencies to add a new dependency to the meta-build (sbt-build group):

sbt> installBuildDependencies ch.epfl.scala:sbt-scalafix:0.14.5:sbt-plugin

Update project dependencies

Use updateDependencies to update dependencies in the current project to their latest versions (respecting version markers):

sbt> updateDependencies

Update only specific dependencies

Pass a filter to updateDependencies to update only matching dependencies:

sbt> updateDependencies org.typelevel:          # Update all org.typelevel dependencies
sbt> updateDependencies :cats-core              # Update cats-core from any organization
sbt> updateDependencies org.typelevel:cats-core # Update a specific dependency

Update build dependencies

Use updateBuildDependencies to update dependencies in the meta-build (project/dependencies.conf, group sbt-build):

sbt> updateBuildDependencies

Update Scala versions

Use updateScalaVersions to update Scala versions in the current project to their latest versions within the same minor line:

sbt> updateScalaVersions

Each version is updated within its minor line:

  • 2.13.12 → latest 2.13.x
  • 2.12.18 → latest 2.12.x
  • 3.3.1 → latest 3.3.x

Use updateBuildScalaVersions to update Scala versions in the sbt-build group (build-level settings):

sbt> updateBuildScalaVersions

Update the SBT version

Use updateSbt to update the SBT version in project/build.properties to the latest version. If updated, it triggers a reboot to apply the new version.

sbt> updateSbt

Update the scalafmt version

Use updateScalafmtVersion to update the scalafmt version in .scalafmt.conf to the latest version:

sbt> updateScalafmtVersion

Update the plugin itself

Use updateSbtPlugin to update the sbt-dependencies plugin itself in project/project/plugins.sbt:

sbt> updateSbtPlugin

Wrapper plugins can override which plugin gets updated by setting these keys in their own buildSettings:

sbtDependenciesPluginOrganization := "com.example"
sbtDependenciesPluginName         := "sbt-my-plugin"

Update everything at once

Use updateAllDependencies to update the plugin itself, Scala versions, dependencies, scalafmt version, and the SBT version all at once:

sbt> updateAllDependencies

Configure artifact migrations

When running updateDependencies, the plugin automatically detects when a dependency has moved to new coordinates (new groupId or artifactId) and migrates it. This follows the same artifact migrations scheme as Scala Steward and uses Scala Steward's artifact migration list by default.

Migrated dependencies are shown with a 🔀 indicator:

 ↳ 🔀 org.json4s::json4s-core:4.0.7 -> io.github.json4s::json4s-core:4.1.0

You can customize the migration sources using the dependencyMigrations setting:

// Disable all migrations
ThisBuild / dependencyMigrations := Nil

// Add a custom migrations URL
ThisBuild / dependencyMigrations += url("https://example.com/my-migrations.conf")

// Use a local file
ThisBuild / dependencyMigrations := List(file("project/artifact-migrations.conf").toURI.toURL)

Custom migration files use Scala Steward's HOCON format:

changes = [
  {
    groupIdBefore = org.json4s
    groupIdAfter = io.github.json4s
    artifactIdAfter = json4s-core
  }
]

Each entry supports groupIdBefore, groupIdAfter, artifactIdBefore, and artifactIdAfter. At least one of groupIdBefore or artifactIdBefore must be defined.


Configure update ignores

When running update commands (updateDependencies, updateScalaVersions, updateSbt, updateScalafmtVersion, updateSbtPlugin), the plugin automatically excludes known-bad dependency versions from update candidates. This follows the same updates.ignore format as Scala Steward and uses Scala Steward's default configuration by default.

You can customize the ignore sources using the dependencyUpdateIgnores setting:

// Disable all ignores
ThisBuild / dependencyUpdateIgnores := Nil

// Add a custom ignore URL
ThisBuild / dependencyUpdateIgnores += url("https://example.com/my-ignores.conf")

// Use a local file
ThisBuild / dependencyUpdateIgnores += file("ignores.conf").toURI.toURL

Custom ignore files use Scala Steward's HOCON format:

updates.ignore = [
  { groupId = "org.scala-lang", artifactId = "scala3-compiler", version = { exact = "3.8.2" } }
  { groupId = "org.scala-lang", artifactId = "scala-compiler", version = "2.13." }
  { groupId = "com.typesafe.akka" }
]

Each entry requires a groupId. The artifactId and version fields are optional:

  • If artifactId is omitted, all artifacts in the group are matched.
  • If version is omitted, all versions are matched.
  • version can be a string (treated as a prefix) or an object with exact, prefix, suffix, or contains fields.

Configure retracted versions

When running update commands, the plugin automatically excludes retracted (broken) dependency versions from update candidates. This follows the same updates.retracted format as Scala Steward and uses Scala Steward's default configuration by default.

When a dependency's current version is retracted, a warning is logged with the reason and a link to documentation.

You can customize the retraction sources using the dependencyUpdateRetractions setting:

// Disable all retractions
ThisBuild / dependencyUpdateRetractions := Nil

// Add a custom retraction URL
ThisBuild / dependencyUpdateRetractions += url("https://example.com/my-retractions.conf")

// Use a local file
ThisBuild / dependencyUpdateRetractions += file("retractions.conf").toURI.toURL

Custom retraction files use Scala Steward's HOCON format:

updates.retracted = [
  {
    reason = "Broken binary compatibility"
    doc = "https://example.com/issue/123"
    artifacts = [
      { groupId = "org.example", artifactId = "my-lib", version = { exact = "1.2.0" } }
    ]
  }
]

Each entry requires a reason and a doc URL. The artifacts array uses the same pattern as update ignores: groupId (required), artifactId (optional), and version (optional, string or object with exact/prefix/suffix/contains).


Configure update pins

When running update commands (updateDependencies, updateScalaVersions, updateSbt, updateScalafmtVersion, updateSbtPlugin), the plugin can restrict updates to versions matching a pin's version pattern. This follows the same updates.pin format as Scala Steward and uses Scala Steward's default configuration by default.

Unlike ignores (which exclude specific versions), pins act as a whitelist: only versions matching the pin's pattern are allowed for matching artifacts. Artifacts without a matching pin are unaffected.

You can customize the pin sources using the dependencyUpdatePins setting:

// Disable all pins
ThisBuild / dependencyUpdatePins := Nil

// Add a custom pin URL
ThisBuild / dependencyUpdatePins += url("https://example.com/my-pins.conf")

// Use a local file
ThisBuild / dependencyUpdatePins += file("pins.conf").toURI.toURL

Custom pin files use Scala Steward's HOCON format:

updates.pin = [
  { groupId = "org.http4s", version = "0.23." }
  { groupId = "org.typelevel", artifactId = "cats-core", version = { prefix = "2." } }
]

Each entry requires a groupId. The artifactId and version fields are optional:

  • If artifactId is omitted, all artifacts in the group are matched.
  • If version is omitted, all versions are allowed (the pin has no effect).
  • version can be a string (treated as a prefix) or an object with exact, prefix, suffix, or contains fields.

Configure post-update hooks

When updateAllDependencies runs, it generates a JSON file at target/sbt-dependencies/.sbt-post-update-hooks listing scripts that should be run after updating. This is useful for CI automation — for example, running sbt scalafixAll after updating a dependency that ships scalafix rewrite rules, or sbt headerCreateAll after updating sbt-header.

The hooks are loaded from Scala Steward's postUpdateHooks configuration by default. Each hook specifies a groupId/artifactId filter and a command to run when a matching dependency is updated.

The output file is a JSON array, parseable with jq or similar tools:

[
  {"script": "sbt scalafixAll", "message": "Reorganize imports with OrganizeImports 0.6.0"},
  {"script": "sbt headerCreateAll", "message": "Update file headers with sbt-header 1.2.0"}
]

You can customize the hook sources using the dependencyPostUpdateHooks setting:

// Disable all hooks
ThisBuild / dependencyPostUpdateHooks := Nil

// Add a custom hooks URL
ThisBuild / dependencyPostUpdateHooks += url("https://example.com/my-hooks.conf")

// Use a local file
ThisBuild / dependencyPostUpdateHooks += file("hooks.conf").toURI.toURL

Custom hook files use Scala Steward's HOCON format:

postUpdateHooks = [
  {
    groupId = "com.github.liancheng"
    artifactId = "organize-imports"
    command = ["sbt", "scalafixAll"]
    commitMessage = "Run OrganizeImports after updating to ${nextVersion}"
  }
]

Each entry supports groupId (optional), artifactId (optional), command (string array, required), and commitMessage (required). The commit message supports ${nextVersion}, ${currentVersion}, and ${artifactName} variables.


Configure scalafix migrations

When updateAllDependencies runs, it also matches Scala Steward's scalafix migrations against updated dependencies. When a dependency update crosses a migration's version threshold, the corresponding scalafix rule is included in the post-update hooks output.

The generated scripts are scoped to the project where the dependency was updated:

[
  {"script": "sbt \"scalafixEnable; core/scalafixAll github:typelevel/cats/Cats_v2_2_0?sha=v2.2.0\"", "message": "Run scalafix migration in core: Cats_v2_2_0 (see https://github.com/typelevel/cats/...)"}
]

For build-level dependencies (plugins in the sbt-build group), migrations run the scalafix CLI directly on build files instead:

[
  {"script": "scalafix --rules=Sbt0_13BuildSyntax", "message": "Run scalafix migration (build): Sbt0_13BuildSyntax"}
]

When a migration requires extra scalac options (e.g., -P:semanticdb:synthetics:on), they are included in the sbt command automatically.

You can customize the migration sources using the dependencyScalafixMigrations setting:

// Disable all scalafix migrations
ThisBuild / dependencyScalafixMigrations := Nil

// Add a custom migrations URL
ThisBuild / dependencyScalafixMigrations += url("https://example.com/my-migrations.conf")

// Use a local file
ThisBuild / dependencyScalafixMigrations += file("migrations.conf").toURI.toURL

Custom migration files use Scala Steward's HOCON format:

migrations = [
  {
    groupId: "org.typelevel"
    artifactIds: ["cats-core"]
    newVersion: "2.2.0"
    rewriteRules: ["github:typelevel/cats/Cats_v2_2_0?sha=v2.2.0"]
    doc: "https://github.com/typelevel/cats/blob/v2.2.0/scalafix/README.md"
    scalacOptions: ["-P:semanticdb:synthetics:on"]
  }
]

Each entry requires groupId, artifactIds (regex patterns), newVersion, and rewriteRules. The doc and scalacOptions fields are optional. A migration triggers when the dependency's version crosses newVersion (i.e., currentVersion < newVersion <= nextVersion).


Show library dependencies

Use showLibraryDependencies to display the library dependencies for the current project in a formatted, colored output. It shows both direct dependencies and those inherited from dependent projects (via .dependsOn).

sbt> showLibraryDependencies
  • Green = direct dependencies
  • Yellow = inherited from other projects

Get all resolved dependencies

Use allProjectDependencies to get the complete list of resolved library dependencies for the project after conflict resolution and eviction:

sbt> show allProjectDependencies

This is useful for programmatic access to dependencies in custom tasks or checks.


Validate resolved dependencies

Use dependenciesCheck to register custom check functions that validate resolved dependencies after update. If any check throws, the build fails.

// build.sbt
dependenciesCheck += { (deps: List[ModuleID]) =>
  if (deps.exists(_.name.contains("log4j")))
    throw new MessageOnlyException("log4j is banned - use logback instead")
}

Each function receives the full list of resolved ModuleIDs after conflict resolution and eviction.


Disable eviction warnings

Use disableEvictionWarnings to downgrade eviction errors to info level, preventing them from failing the build:

sbt> disableEvictionWarnings

To restore eviction warnings to error level (default behavior), use enableEvictionWarnings:

sbt> enableEvictionWarnings

Contributors to this project

alejandrohdezma
alejandrohdezma