Gradle7 Version Catalog: How to use it with buildSrc?
Asked Answered
P

2

39

I am very excited about the incubating Gradle's version catalogs and have been experimenting with it. I’ve found that the information in my gradle/libs.versions.toml is accessible in the build.gradle.kts scripts for my app and utility-lib projects.

However, I am unable to use the content of the toml file for buildSrc/build.gradle.kts or the convention files.

The only way that I could build was to hard-code the dependencies into those files, as I did before the version catalog feature.

In the buildSrc folder, I created a settings.gradle.kts file and inserted the dependencyResolutionManagement code for versionCatalogs, which is pointing to the same file as for my app and utility-lib projects.

Based on the Gradle7 docs, it seems that sharing a version catalog with buildSrc and modules is possible… I’d appreciate a nudge into getting it to work with buildSrc, if possible.

Here is a simple sample project, which I created via gradle init: my-version-catalog

Thank you for your time and help,

Mike

Pitchfork answered 1/6, 2021 at 19:59 Comment(2)
I found the following link, which is fairly current and it provides more details about this use-case. melix.github.io/blog/…Pitchfork
For reference the good link is Version Catalog FAQ by Cédric ChampeauRisser
R
54

With Gradle 7.3.3, it is possible. Note version catalogs are GA since Gradle 7.4

The code snippet assumes Gradle is at least 7.4, but if you need them prior that version, insert enableFeaturePreview("VERSION_CATALOGS") at the beginning of each settings.gradle.kts.

Using buildSrc

buildSrc/settings.gradle.kts

dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            from(files("../gradle/libs.versions.toml"))
        }
    }
}

buildSrc/build.gradle.kts

dependencies {
    implementation(libs.gradleplugin.intellij) // <- the lib reference
}

You can even use the version catalog for plugins

gradle/libs.versions.toml

...

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
jetbrains-changelog = { id = "org.jetbrains.changelog", version.ref = "changelog-plugin" }
jetbrains-intellij = { id = "org.jetbrains.intellij", version.ref = "intellij-plugin" }
hierynomus-license = { id = "com.github.hierynomus.license", version.ref = "license-plugin" }
nebula-integtest = { id = "nebula.integtest", version.ref = "nebula-integtest-plugin" }

build.gradle.kts

plugins {
    id("java")
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.nebula.integtest)
    alias(libs.plugins.jetbrains.intellij)
    alias(libs.plugins.jetbrains.changelog)
    alias(libs.plugins.hierynomus.license)
}

Note for accessing the catalog within scripts, please refer to the below section, the trick is the same.

Using convention plugins and included build

In the main project include a the Gradle project that holds the convention plugins.

build.gradle.kts

includeBuild("convention-plugins") // here it's a subfolder

convention-plugins/settings.gradle.kts

dependencyResolutionManagement {
    repositories {
        gradlePluginPortal()
    }
    versionCatalogs {
        create("libs") {
            from(files("../gradle/libs.versions.toml"))
        }
    }
}

rootProject.name = "convention-plugins"

The trick to enable convention plugins to access the version catalog is split in two part, add an ugly implementation dependency that locate where the version catalog generated classes are located.

libs.javaClass.superclass.protectionDomain.codeSource.location

Then in the convention plugin refer to the libs extension via Project::the.

val libs = the<LibrariesForLibs>()

This is tracked by gradle/gradle#15383.

convention-plugins/build.gradle.kts

plugins {
    `kotlin-dsl`
}

dependencies {
    implementation(libs.gradleplugin.kotlin.jvm)

    // https://github.com/gradle/gradle/issues/15383
    implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
}

And in the actual convention plugin

import org.gradle.accessors.dm.LibrariesForLibs

plugins {
    id("org.jetbrains.kotlin.jvm")
}

// https://github.com/gradle/gradle/issues/15383
val libs = the<LibrariesForLibs>()

dependencies {
    detektPlugins(libs.bundles.kotlinStuff) // access catalog entries
}

The org.gradle.accessors.dm.LibrariesForLibs class is generated by gradle is somewhere in local gradle folder ./gradle/<version>/dependency-accessors/<hash>/classes


Quick note that older IntelliJ IDEA currently (2022.3) reports alias(libs.gradleplugin.thePlugin) as an error in the editor, although the dependencies are correctly resolved. This tracked by KTIJ-19369, the ticket indicates this is actually a bug in Gradle Kotlin DSL gradle/gradle#22797, and someone made a simple IntelliJ IDEA plugin to hide this error until resolved.

Risser answered 27/1, 2022 at 11:56 Comment(10)
For the libs.versions.toml file in the gradle directory, you don't need to add VersionCatalog section. It the file is present in that directory the libs version catalog is available. With gradle 7.4, the enableFeaturePreview("VERSION_CATALOGS") is no longer needed.Blisse
@TomRutchik I don't think this works for buildSrc, or any subprojects (as included build) as it's considered as a different project which means the location has to be set explicitly.Risser
Brice, you're right about buildSrc and included build. You should however have access to the version catalog in the buildSrc/build.gradle.kts without adding the dependency but the buildSrc code won't! I'm somewhat curious if adding that dependency to an external built custom plugin would allow the custom plugin to have access to the libs catalog in the project that it gets applied. I'm going to give that a try, but somehow I don't think that'll work!Blisse
Hi, let me know what you did find out!Risser
This doesn't really answer the question fully. It makes the catalog accessible to the buildSrc/build.gradle , but not to the convention plugins. Do you have an example of using the catalog extension to reference the contents of libs.versions.toml?Hubblebubble
I also am stuck on this, the above makes it accessible from build.gradle.kts but not in src/main/kotlin/my-kotlin-library-conventions.kts for example =(Fawnia
@GavinRay @Hubblebubble I do have convention plugins as well, but I migrated to included build rather than buildSrc. I'll complement the answer right away with what I have.Risser
@Brice I owe you a beer mate, I can't believe you figured out those billion layers of hacks. Holy hell that's one nightmare to get working right... they need to revisit Version Catalog support for buildSrc I thinkFawnia
Thank you @GavinRay, but really this is also luck and sharing knowledge. Actually the trick is fairly simple, and I believe part of it makes sense. But clearly the usability in the context of convention plugins (either in buildSrc or included build) is kinda bad.Risser
If you ever want to use this in a convention plugin written in kotlin you can also duplicate the existing libs extension function on Project e.g. val Project.libs: LibrariesForLibs get() = (this as ExtensionAware).extensions.getByName("libs") as LibrariesForLibsPunish
B
1

Brice, it looks like a can of worms to go down that path, particularly for my situation, where I'm trying to use a libs.version.toml file from an android project, but the custom plugin is of course from a java/kotlin project. I tried creating the libs file by hardwiring the path to the toml file in the custom plugin. It might work if both were java projects, but I never tried that since that's not what I'm after. The ideal solution would be for the plugin to use the libs file from the project it is applied to, but it looks like the version catalog needs to be created in the settings file, before you even have access to "Project", so that's why you would have to hardwire the path.

Short answer. No, but there are other techniques for a custom plugin to get project version data from the project it is applied to.

Blisse answered 5/7, 2022 at 22:31 Comment(2)
What you can do so that the custom plugin gets access to a shared version catalog is to use gradle "version-catalog" plugin. You can use that to publish a version catalog to a repository. You can then import that publish version catalog in your custom plugin. The only caveat is that the project and the custom plugin both need to agree on the shared published version catalog. For many situations, that's all you need. If you have two project that use different shared version catalogs, you can't use the same custom plugin. You can however, override values in an imported version catalog.Blisse
For clarity, I talking about external custom plugins, not buildSrc pluginBlisse

© 2022 - 2024 — McMap. All rights reserved.