Easily use any BOM as a Gradle Version Catalog.
Compatibility
Gradle Version |
Plugin Version |
7.6.x |
1.x |
8.x |
1.x, 2.x |
Quick Start
First, add your BOM dependencies to your version catalog
libs.versions.toml
[versions]
spring = "3.2.0"
aws = "2.22.0"
[libraries]
awsBom = { group = "software.amazon.awssdk", name = "bom", version.ref = "aws" }
springBootDependencies = { group = "org.springframework.boot", name = "spring-boot-dependencies", version.ref = "spring" }
Then, add the plugin to your settings with the catalogs you want to generate
settings.gradle.kts
import dev.aga.gradle.versioncatalogs.Generator.generate
plugins {
id("dev.aga.gradle.version-catalog-generator") version("2.1.2")
}
dependencyResolutionManagement {
repositories {
mavenCentral() (1)
}
versionCatalogs {
generate("springLibs") { (2)
from(toml("springBootDependencies")) (3)
propertyOverrides = mapOf(
"jackson-bom.version" to "2.16.1", (4)
"mockito.version" to versionRef("mockito"), (5)
)
generateBomEntry = true (6)
}
generate("awsLibs") {
from(toml("awsBom"))
aliasPrefixGenerator = GeneratorConfig.NO_PREFIX (7)
}
}
}
1 | Must include repositories here for dependency resolution to work from settings |
2 | The name of the generated catalog |
3 | The name of the bom library in the version catalog |
4 | Optionally override some version properties using a literal value |
5 | Or, you can reference version aliases in the source TOML |
6 | Optionally generate an entry in the catalog for the BOM itself |
7 | All dependencies in the AWS BOM are for AWS so we can skip the prefix |
Lastly, use the dependencies in your build
build.gradle.kts
dependencies {
implementation(awsLibs.s3)
implementation(awsLibs.dynamodb)
implementation(springLibs.spring.springBootStarterWeb)
implementation(springLibs.jackson.jacksonDatabind)
}
Detailed Usage
Applying the Plugin
import dev.aga.gradle.versioncatalogs.Generator.generate (1)
plugins {
id("dev.aga.gradle.version-catalog-generator") version "2.1.2"
}
dependencyResolutionManagement {
repositories { (2)
mavenCentral()
}
versionCatalogs {
generate("myLibs") {
// excluded for brevity
}
}
}
1 | The extension function must be imported to allow the DSL to access the generate function.
Technically, the generate function can be placed anywhere in the settings script, but I like putting it inside the versionCatalogs block
since that’s also where you would put create statements. |
2 | In order for us to be able to resolve your BOM dependencies, you must specify the repositories in your settings file. When specifying repositories in settings you can (most of the time) remove the same declarations from your build file. According to the Gradle documentation, repositories declared in the build file will override whatever is declared in settings. |
plugins {
id 'dev.aga.gradle.version-catalog-generator' version '2.1.2'
}
dependencyResolutionManagement {
repositories { (1)
mavenCentral()
}
versionCatalogs {
generator.generate("myLibs") { (2)
// excluded for brevity
}
}
}
1 | In order for us to be able to resolve your BOM dependencies, you must specify the repositories in your settings file. When specifying repositories in settings you can (most of the time) remove the same declarations from your build file. According to the Gradle documentation, repositories declared in the build file will override whatever is declared in settings. |
2 | The generator extension should be available by default without an import |
The import statement and repository declaration blocks may be excluded from future code samples for brevity. |
Dependency Sources
TOML
dependencyResolutionmanagement {
versionCatalogs {
generate("springLibs") {
from {
toml {
libraryAlias = "springBootDependencies" (1)
file = file("gradle/libs.versions.toml") (2)
}
}
}
generate("awsLibs") {
from(toml("awsBom")) (3)
}
generate("junitLibs") {
from {
toml {
libraryAlias = "boms-junit5"
file = artifact("io.micronaut.platform:micronaut-platform:4.3.6") (4)
}
}
}
}
}
1 | Required, the alias of the library in the TOML file |
2 | The TOML file to find the provided alias in. This only needs to be provided
if not using the value gradle/libs.versions.toml |
3 | When using the default file, you can use the convenience function toml(..) and just provide the alias name |
4 | If your TOML is published to a repository, you can fetch it using the artifact function and standard string
notation. |
dependencyResolutionmanagement {
versionCatalogs {
generator.generate("springLibs") {
it.from { from ->
from.toml { toml ->
toml.libraryAlias = "springBootDependencies" (1)
toml.file = file("gradle/libs.versions.toml") (2)
}
}
}
generator.generate("awsLibs") {
it.from(it.toml("spring-boot-dependencies")) (3)
}
generate("junitLibs") {
it.from { from ->
from.toml { toml ->
toml.libraryAlias = "boms-junit5"
toml.file = toml.artifact("io.micronaut.platform:micronaut-platform:4.3.6") (4)
}
}
}
}
}
1 | Required, the alias of the library in the TOML file |
2 | The TOML file to find the provided alias in. This only needs to be provided
if not using the value gradle/libs.versions.toml |
3 | When using the default file, you can use the convenience function toml(..) and just provide the alias name |
4 | If your TOML is published to a repository, you can fetch it using the artifact function and standard string
notation. |
GitHub’s Dependabot only supports automatic updates in the default version catalog gradle/libs.versions.toml
|
String Notation
dependencyResolutionmanagement {
versionCatalogs {
generate("springLibs") {
from {
dependency("org.springframework.boot:spring-boot-dependencies:3.1.2") (1)
}
}
generate("awsLibs") {
from("software.amazon.awssdk:bom:2.25.6") (2)
}
}
}
1 | You can also use the standard string notation to specify your dependency |
2 | More conveniently, you can pass the string notation directly into from() |
dependencyResolutionmanagement {
versionCatalogs {
generator.generate("springLibs") {
it.from { from ->
from.dependency("org.springframework.boot:spring-boot-dependencies:3.1.2") (1)
}
}
generator.generate("awsLibs") {
it.from("software.amazon.awssdk:bom:2.25.6") (2)
}
}
}
1 | You can also use the standard string notation to specify your dependency |
2 | More conveniently, you can pass the string notation directly into from() |
Groovy is not my strong point so it’s likely that there is simpler / better syntax I’m not aware of. If you have any suggestions to improve the syntax in the documentation please open an issue or a PR. |
Including a BOM Entry
Optionally, you can configure the generator to also generate an entry in the catalog for the BOM itself.
generate("springLibs") {
from(toml("springBootDependencies"))
generateBomEntry = true (1)
}
1 | Set generateBomEntry to true to include an entry for the BOM in the generated catalog |
generator.generate("springLibs") {
it.from { from ->
from.toml { toml ->
toml.libraryAlias = "springBootDependencies"
}
}
it.generateBomEntry = true (1)
}
1 | Set generateBomEntry to true to include an entry for the BOM in the generated catalog |
Customizing Library Aliases
The aliases we generate for libraries discovered in the BOM are based on two separate components,
a prefix
and a suffix
. The prefix and suffix are separated by a hyphen to create the library’s alias.
If the prefix is blank, it will be ignored entirely.
Default Behavior
The default algorithm is as follows:
Prefix Generation
-
The
groupId
of the dependency is split by.
-
If the split only returns a list of one item and the value is the string
bundles
,plugins
, orversions
, anIllegalArgumentException
is thrown -
If the split returns a list of two or more items, and the last value is one of the invalid strings listed above, the last two items in the list are concatenated by a hyphen. The entirety of the string is the converted to camelCase
-
In any other scenario, the last item in the list is returned as-is
Due to the popularity of many packages that may not necessarily fit our algorithm, a number of special cases have been added to simplify their generated prefixes. Please refer to the below table for the hardcoded aliases we have set up. If you do not wish to use the aliases, you will need to provide your own prefix generation function. |
Condition | Value | Generated Prefix |
---|---|---|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Group begins with |
|
|
Suffix Generation
-
Replace any
.
or_
character in theartifactId
of the dependency with a-
-
Convert the resulting string to camelCase
Examples
-
org.springframework.boot:spring-boot-starter-web
→spring-springBootStarterWeb
-
com.fasterxml.jackson.core:jackson-databind
→jackson-jacksonDatabind
-
com.fasterxml.jackson.datatype:jackson-datatype-jsr310
→jackson-jacksonDatatypeJsr310
-
software.amazon.awssdk:s3
→awssdk-s3
-
org.hibernate.orm:hibernate-core
→orm.hibernateCore
In some cases our default logic may not be sufficient and it may attempt to store two dependencies with the same
alias. If this happens, an exception will be thrown pointing to the errant dependencies and you will either need to
exclude one or more of the conflicting dependencies, or override the default behavior. spring-boot-dependencies in the 2.7.x
version range is known to cause problems due to conflicting entries of ehcache . See #100
|
Customizing Prefix Generation
The prefix generation logic can be modified by overriding the property aliasPrefixGenerator
in GeneratorConfig
generate("myLibs") {
// excluded for brevity
aliasPrefixGenerator = { groupId, artifactId ->
if(groupId == "some.group") {
"somegroup"
} else {
GeneratorConfig.DEFAULT_ALIAS_PREFIX_GENERATOR(groupId, artifactId) (1)
}
}
}
1 | We can always fall back to the default logic if our condition isn’t met |
generator.generate("myLibs") {
// excluded for brevity
it.aliasPrefixGenerator = { groupId, artifactId ->
if(groupId == "some.group") {
"somegroup"
} else {
DEFAULT_ALIAS_PREFIX_GENERATOR.invoke(groupId, artifactId) (1)
}
}
}
1 | We can always fall back to the default logic if our condition isn’t met |
Skipping Prefix Generation Entirely
In some BOMs which only contain their own dependencies, for example the AWS BOM, the inclusion of the prefix may not be useful and instead you may want to skip the prefix entirely. A convenience function is provided to do so.
generate("awsLibs") {
from(toml("awsBom"))
aliasPrefixGenerator = GeneratorConfig.NO_PREFIX (1)
}
1 | The resulting generated alias for software.amazon.awssdk:s3 would just be s3 . When accessing this library
in our build file, the accessor would subsequently be awsLibs.s3 instead of awsLibs.awssdk.s3 |
generator.generate("awsLibs") {
it.from(it.toml("awsBom"))
aliasPrefixGenerator = NO_PREFIX (1)
}
1 | The resulting generated alias for software.amazon.awssdk:s3 would just be s3 . When accessing this library
in our build file, the accessor would subsequently be awsLibs.s3 instead of awsLibs.awssdk.s3 |
Customizing Suffix Generation
The suffix generation logic can be modified by overriding the property aliasSuffixGenerator
in GeneratorConfig
generate("myLibs") {
// excluded for brevity
aliasSuffixGenerator = { prefix, groupId, artifactId -> (1)
val suffix = GeneratorConfig.DEFAULT_ALIAS_SUFFIX_GENERATOR(groupId, artifactId) (2)
if(prefix == "spring") {
suffix.replaceFirst("spring","") (3)
} else {
suffix
}
}
}
1 | The prefix argument refers to the generated prefix value for the dependency |
2 | The default logic can always be accessed through GeneratorConfig.DEFAULT_ALIAS_SUFFIX_GENERATOR |
3 | In this example we are extending the default behavior to remove the "duplicate" appearance of the word spring .
For example, instead of spring-springBootStarterWeb , we would generate spring-bootStarterWeb |
generator.generate("myLibs") {
// excluded for brevity
it.aliasSuffixGenerator = { prefix, groupId, artifactId -> (1)
def suffix = DEFAULT_ALIAS_SUFFIX_GENERATOR.invoke(groupId, artifactId) (2)
if(prefix == "spring") {
suffix.replaceFirst("spring","") (3)
} else {
suffix
}
}
}
1 | The prefix argument refers to the generated prefix value for the dependency |
2 | The default logic can always be accessed through DEFAULT_ALIAS_SUFFIX_GENERATOR |
3 | In this example we are extending the default behavior to remove the "duplicate" appearance of the word spring .
For example, instead of spring-springBootStarterWeb , we would generate spring-bootStarterWeb |
Customizing Version Aliases
If any dependencies in the source BOM specify a dependency’s version via a property, we will create a version alias in the generated catalog for that behavior.
Default Behavior
The default algorithm to generate a version alias from a property is:
-
Replace all case-insensitive instances of the literal string
version
with an empty string -
All instances of two or more consecutive periods are replaced with a single period
-
Any leading or trailing periods are trimmed
-
All periods are replaced with a hyphen
-
The entire string is converted to camelCase
Examples
-
jackson.version
→jackson
-
version.jackson
→jackson
-
jackson.modules.version
→jacksonModules
Customizing Version Aliases
The version alias generation logic can be customized by overriding the property versionNameGenerator
in GeneratorConfig
generate("myLibs") {
// excluded for brevity
versionNameGenerator = { propertyName -> (1)
if(propertyName == "somethingWeird") {
"notAsWeird"
} else {
GeneratorConfig.DEFAULT_VERSION_NAME_GENERATOR(propertyName) (2)
}
}
}
1 | The property name from the maven POM, i.e. jackson.version |
2 | The default logic can always be accessed through GeneratorConfig.DEFAULT_VERSION_NAME_GENERATOR |
generator.generate("myLibs") {
// excluded for brevity
it.versionNameGenerator = { propertyName -> (1)
if(propertyName == "somethingWeird") {
"notAsWeird"
} else {
DEFAULT_VERSION_NAME_GENERATOR.invoke(propertyName) (2)
}
}
}
1 | The property name from the maven POM, i.e. jackson.version |
2 | The default logic can always be accessed through DEFAULT_VERSION_NAME_GENERATOR |
Case Conversion
For converting between different text cases, for example lower-hyphen to lower-camel, you can use the convenience
function caseChange
aliasSuffixGenerator = { _, _, artifactId ->
GeneratorConfig.caseChange(artifactId, CaseFormat.LOWER_HYPEN, CaseFormat.CAMEL) (1)
}
1 | You will have to add an import for net.pearx.kasechange.CaseFormat into the build file. The dependency is already available for use when you apply the plugin |
it.aliasSuffixGenerator = { _, _, artifactId ->
caseChange(artifactId, CaseFormat.LOWER_HYPEN, CaseFormat.CAMEL) (1)
}
1 | You will have to add an import for net.pearx.kasechange.CaseFormat into the build file. The dependency is already available for use when you apply the plugin |
Overriding Versions
In some cases you may want to override the version specified by the BOM you are generating the catalog from. At the current point in time, this is only possible if the BOM uses a property to specify the version[1]
generate("myLibs") {
// excluded for brevity
propertyOverrides = mapOf(
"jackson.version" to "2.16.1", (1)
"aws.version" to versionRef("aws") (2)
)
}
1 | Specify a string version to use in place of all occurrences of jackson.version when processing the BOM |
2 | Specify an existing version alias from your TOML file. The versionRef function
can only be used if your BOM source is a TOML (not a string dependency notation), and the version alias being
referenced must exist in the same TOML file in which your BOM alias is declared. |
generator.generate("myLibs") {
// excluded for brevity
it.propertyOverrides = [
"jackson.version": "2.16.1", (1)
"aws.version": it.versionRef("aws") (2)
]
}
1 | Specify a string version to use in place of all occurrences of jackson.version when processing the BOM |
2 | Specify an existing version alias from your TOML file. The versionRef function
can only be used if your BOM source is a TOML (not a string dependency notation), and the version alias being
referenced must exist in the same TOML file in which your BOM alias is declared. |
Filtering Dependencies
If you want to exclude certain dependencies from having entries generated for them in your catalog, you can filter
dependencies either by groupId
or by name
(artifactId)
Filtering By GroupId
generate("myLibs") {
// excluded for brevity
excludeGroups = ".*spring.*" (1)
}
1 | Exclude all dependencies that have "spring" in their groupId |
generator.generate("myLibs") {
// excluded for brevity
it.excludeGroups = ".*spring.*" (1)
}
1 | Exclude all dependencies that have "spring" in their groupId |
Filtering By Name
generate("myLibs") {
// excluded for brevity
excludeNames = ".*hibernate.*" (1)
}
1 | Exclude all dependencies that have "hibernate" in their name/artifactId |
generator.generate("myLibs") {
// excluded for brevity
it.excludeNames = ".*hibernate.*" (1)
}
1 | Exclude all dependencies that have "hibernate" in their name/artifactId |
Caching
Unfortunately, settings plugins in Gradle cannot take advantage of the same caching mechanisms that are provided for tasks with incremental builds. However, we have added the ability to save the generated TOML catalog to a file, and the build will (re)use that file the next time the build is invoked. Currently, the behavior is disabled by default.
generate("myLibs") {
// excluded for brevity
cacheEnabled = true (1)
cacheDirectory = file("build/version-catalogs") (2)
}
1 | Enable the caching functionality |
2 | The directory to save the generated catalogs in. If not specified, the generated catalogs will be saved in
build/version-catalogs , relative to the root project directory. |
generator.generate("myLibs") {
// excluded for brevity
it.cacheEnabled = true (1)
it.cacheDirectory = file("build/version-catalogs") (2)
}
1 | Enable the caching functionality |
2 | The directory to save the generated catalogs in. If not specified, the generated catalogs will be saved in
build/version-catalogs , relative to the root project directory. |
When enabling caching, be mindful of the cache directory being used. You will mostly likely want to make sure
the directory is cleaned by the clean task. If the cache directory is not affected by clean , changes to the source
may not be properly recognized and your generated catalog may contain stale data.
|
Build File
dependencies {
implementation(springLibs.spring.springBootStarterWeb) (1)
}
1 | When using the libraries from your generated catalog, each - in the generated alias is replaced with a . |
dependencies {
implementation springLibs.spring.springBootStarterWeb (1)
}
1 | When using the libraries from your generated catalog, each - in the generated alias is replaced with a . |
The generated catalog won’t be immediately available (same as adding a new entry to your libs.versions.toml file).
You will need to evaluate the settings through another gradle task (i.e. assemble ) to trigger the catalog generation.
|
Project Goals
-
Compatible with Dependabot
-
Nested BOM support (i.e.
spring-boot-dependences
importsmockito-bom
, etc) -
Easy to override versions (similar to
ext["version.property"] = …
in Spring Boot Dependencies plugin)