Search code examples
kotlingradledependenciesdependency-managementgradle-kotlin-dsl

How do I encapsulate version management for gradle plugins?


Problem

I have a setup of various distinct repos/projects (i.e. app1, app2, app3) that all depend on shared functionality in my base package.

The projects also use various other third-party dependencies (i.e. app1 and app3 use spring, all of them use kotlinx-serialization). I want to synchronise the versions of all third-party dependencies, so that any project using my base package uses the same version of every third-party dependency. However, I don't want to introduce new dependencies to projects that do not use them (i.e. app2 does not use spring)

Solution attempts

For libraries, I have been able to solve this with the help of a gradle platform, which does exactly what I want - I specify the versions in my base package, then add the platform as a dependency to my projects and can then simply add dependencies by name (i.e. implementation("org.springframework.boot:some-package")) without having to specify a version number, because it uses the provided value from my platform.

However, for plugins, I have not been able to do this. Many libraries come with plugins and naturally the plugin should be at the same version as the library. I have tried various approaches, including writing a standalone plugin, but none have worked.

Current best idea

I added implementation("org.springframework.boot:spring-boot-gradle-plugin:3.0.2") to the dependencies of my standalone plugin. Then, I added the following code to my standalone plugin:

class BasePlugin : Plugin<Project> {
    override fun apply(target: Project) {
        target.plugins.apply("org.springframework.boot")
    }
}

This works and applies the plugin to my main project at the correct version. However, there are 2 major problems with this:

a) Now every project applies the spring plugin, including app2 (which does not use spring).

b) I have many plugins to manage and no idea how to get the long implementation-string for most of them. I found the "org.springframework.boot:spring-boot-gradle-plugin:3.0.2" by looking up the plugin-id on https://plugins.gradle.org/ and then looking at the legacy plugin application section, which sounds like I am on the wrong track.

I just want to manage the versions of plugins and libraries of multiple projects/repos in a central place - this feels like a fairly basic use case - why is this so hard?


Solution

  • There are some great and detailed answers about dependency management, but unfortunately none worked to perform cross-project version management for plugins. It seems that there is no gradle functionality to do this, but I got it working with a bit of a workaround. Here is my (working) approach, in hope that it helps someone else with this:

    1. Create a Standalone gradle Plugin
    2. In the build.gradle.kts of the plugin, include the maven coordinates (not its ID) of every other plugin whose version you want to manage in any of your projects in the dependency block with the api keyword. i.e. api("org.springframework:spring-web:6.0.2")
    3. In the main projects, remove every other plugin from the plugins block, so that your custom standalone plugin is the only one remaining.
    4. Create a file (i.e. a plugins.json or whatever you want) in the project root directory of all main projects and in there supply the plugin IDs of the plugins that you actually intend to use in that project. Just the IDs, no version numbers, i.e. "org.springframework.boot" for Spring's plugin. (Keep in mind that for plugins declared as kotlin("abc") you will have to add the prefix "org.jetbrains.com.", as the kotlin method is just syntactic sugar for that)
    5. In your plugin source code, in the overriden apply method, look for. a file named plugins.json (or whatever you chose) in the project.buildFile.parent directory (which will be the directory of the project using this plugin, NOT of the plugin itself). From this file, read the plugin IDs
    6. for every pluginID in the file, call project.plugins.apply(id)

    How/Why it works:

    • The main project build.gradle.kts is executed, looks at the plugin block and applies your standalone plugin (which is the only one), which calls its apply method.
    • This plugin then applies other plugins based on their ID from the file.
    • Normally, this will throw an error because these plugins are not found, but because we defined them as dependencies with the api keyword in our standalone plugin, they are now available on the classpath and in exactly the version of that import statement.

    Hope it helps someone!