Back in February I created a new gradle project using `gradle init`. Just recently I noticed that one of my build files was referencing a “libs” property:
alias(libs.plugins.jvm)
testImplementation(libs.junit.jupiter.engine)
It turns out that these are referencing entries in `./gradle/libs.versions.toml` which is a version catalog:
A version catalog is a list of dependencies, represented as dependency coordinates, that a user can pick from when declaring dependencies in a build script.
When I created the project it automatically set up a version catalog including the kotlin plugin and junit.
[versions]
junit-jupiter-engine = "5.10.0"
[libraries]
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter-engine" }
[plugins]
jvm = { id = "org.jetbrains.kotlin.jvm", version = "1.9.20" }
My project has multiple modules, and each one is defining some test libraries:
testImplementation("io.kotest:kotest-runner-junit5:5.8.0")
testImplementation("io.kotest:kotest-assertions-core:5.8.0")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
So lets see if I can prevent duplicating these references and centralise them in libs.versions.toml…
I’ll add the following to libs.versions.toml
[versions]
kotlin = "1.9.20"
kotest = "5.8.0"
[libraries]
kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
kotlin-test-junit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "kotlin" }
[bundles]
kotest = ["kotest-runner-junit5", "kotest-assertions-core", "kotlin-test-junit5"]
Now I can simply refer to libs.bundles.kotest in each module build file to include that bundle - and have consistent versions across the project:
testImplementation(libs.bundles.kotest)
In a bid to only have the kotlin version defined once, I can also reference the libs definitions in the plugin block like so:
plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
alias(libs.plugins.jvm)
kotlin("plugin.serialization") version "${libs.plugins.jvm.get().version}"
id("org.jetbrains.dokka") version libs.versions.dokka.get()
}
For full information read the documentation here: https://docs.gradle.org/current/userguide/platforms.html
On a related note, I asked Cody (the coding AI assistant plugin I have installed in IntelliJ IDEA) about this and the information I got was pretty spot on:
What is libs.versions.toml?
libs.versions.toml is a file used in the Gradle Dependency Management (GDM) plugin for managing dependencies in a Gradle project. It is a TOML (Tom's Obvious, Minimal Language) file that defines a centralized location for specifying the versions of dependencies used in the project.
The main purpose of libs.versions.toml is to:
Centralize dependency versions: Instead of specifying the version of each dependency in multiple places throughout the project, you can define the versions in a single file, making it easier to manage and update dependencies across the project.
Enforce consistent versions: By defining the versions in a centralized location, you can ensure that all modules in the project use the same versions of dependencies, reducing the risk of version conflicts.
Enable easier updates: When you need to update a dependency version, you only need to change it in the libs.versions.toml file, and the change will propagate to all modules that use that dependency.
Here's an example of what the libs.versions.toml file might look like:
[versions]
kotlin = "1.6.21"
coroutines = "1.6.4"
ktor = "2.0.3"
logback = "1.2.11"
[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
In this example:
The [versions] section defines the versions of various dependencies or libraries.
The [libraries] section defines the actual dependencies, referencing the versions defined in the [versions] section using the version.ref property.
In your Gradle build scripts, you can then reference these dependencies using the libs object provided by the GDM plugin. For example:
dependencies {
implementation(libs.kotlin.stdlib)
implementation(libs.coroutines.core)
implementation(libs.ktor.server.core)
implementation(libs.logback.classic)
}
The libs.versions.toml file is typically checked into version control, allowing all developers working on the project to use the same versions of dependencies. When a dependency needs to be updated, you only need to change the version in the libs.versions.toml file, and the change will propagate to all modules that use that dependency.
That’s a pretty great answer - but I also want to know if renovate can use this TOML file when it’s raising PRs for library upgrades…
Note: I haven’t verified the following answer is accurate…
How do I use libs.versions.toml with renovate?
To use libs.versions.toml with Renovate, you need to configure Renovate to handle TOML files and specify the file path for libs.versions.toml. Here's how you can do it:
Enable TOML file support in Renovate: By default, Renovate doesn't handle TOML files. You need to enable this support by adding the following configuration to your renovate.json file:
{
"enabledManagers": ["toml"]
}
Configure the file path for libs.versions.toml: You need to tell Renovate where to find the libs.versions.toml file. Add the following configuration to your renovate.json file:
{
"enabledManagers": ["toml"],
"toml": {
"fileMatch": ["libs.versions.toml"]
}
}
This configuration tells Renovate to look for files named libs.versions.toml and manage the dependencies defined in those files.
Configure Renovate to update the libs.versions.toml file: By default, Renovate will create separate Pull Requests (PRs) for each dependency update. However, with libs.versions.toml, you might want to group all dependency updates into a single PR. You can achieve this by adding the following configuration to your renovate.json file:
{
"enabledManagers": ["toml"],
"toml": {
"fileMatch": ["libs.versions.toml"]
},
"packageRules": [
{
"matchFiles": ["libs.versions.toml"],
"groupName": "libs.versions.toml dependencies"
}
]
}
This configuration groups all updates to the libs.versions.toml file into a single PR with the name "libs.versions.toml dependencies".
Configure Renovate to update the Gradle build files: Renovate will update the libs.versions.toml file, but you might also want it to update the corresponding dependency declarations in your Gradle build files. To achieve this, you can add the following configuration to your renovate.json file:
{
"enabledManagers": ["toml", "gradle"],
"toml": {
"fileMatch": ["libs.versions.toml"]
},
"packageRules": [
{
"matchFiles": ["libs.versions.toml"],
"groupName": "libs.versions.toml dependencies"
}
],
"gradle": {
"enabled": true
}
}
This configuration enables the Gradle package manager in Renovate, which will update the dependency declarations in your Gradle build files based on the changes made to the libs.versions.toml file.
With these configurations in place, Renovate will monitor the libs.versions.toml file and create a single PR for all dependency updates, while also updating the corresponding dependency declarations in your Gradle build files.
Note that you might need to adjust these configurations based on your specific project structure and requirements. Additionally, make sure to check the Renovate documentation for the latest configuration options and best practices.
That’s another pretty great answer - but I haven’t tested this out with renovate yet, so fingers crossed it’s accurate. Maybe I could try this out in a future post.