简体   繁体   中英

How do I encapsulate version management for gradle plugins?

Problem

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

The projects also use various other third-party dependencies (ie 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 (ie 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 (ie 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?

I use version numbers in a gradle.properties file for this purpose. Since the introduction of Gradle version catalogs, my approach is probably a bit out of date, but I'll share it here anyway. It's based on the fact that plugin versions can be managed in settings.gradle.kts by reading values from the properties file.

In gradle.properties :

springBootVersion=3.0.2

In settings.gradle.kts :

pluginManagement {
    val springBootVersion: String by settings

    plugins {
        id("org.springframework.boot") version springBootVersion
    }
}

And finally in build.gradle.kts :

plugins {
    id("org.springframework.boot")
}

dependencies {
    val springBootVersion: String by project
    implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion"))
}

Notice that the plugin version is omitted in the build script because it is already specified in the settings file. And note also that the method for accessing the property in the settings script is slightly different from that in the build script.

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

It is indeed better to avoid applying too many plugins - and that's why Gradle encourages reacting to plugins .

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.*
import org.springframework.boot.gradle.plugin.SpringBootPlugin

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

        // instead, react!
        target.plugins.withType<SpringBootPlugin>().configureEach {
            // this configuration will only trigger if the project applies both
            // BasePlugin *and* the Spring Boot pluging
        }

        // you can also react based on the plugin ID
        target.pluginManager.withPlugin("org.springframework.boot") {

        }
    }
}

Using the class is convenient if you want to access the plugin, or the plugin's extension, in a typesafe manner.

You can find the Plugin's class by

  • looking in the source code for the class that implements Plugin<Project> ,
  • in the plugin's build config for the implementationClass ,
  • or in the published plugin JAR - in the META-INF/gradle-plugins directory there will be a file that has the implementationClass .

This doesn't help your version alignment problem - but I thought it was worth mentioning!

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.

You're on the right track with the "long implementation string" as you call it. I'll refer to those as the 'Maven coordinates' of the plugin.

Gradle Plugin Maven Coordinates

The plugin id of the Kotlin JVM plugin is org.jetbrains.kotlin.jvm , but the Maven coordinates are org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0 .

Kotlin JVM 插件的 Gradle 插件门户屏幕截图,展示了如何使用插件 {} 块应用插件,与“传统”`apply(plugin = "org.jetbrains.kotlin.jvm")

The 'legacy' part refers to how the plugins are applied, using the apply(plugin = "...") syntax. The new way uses the plugin {} block, but under the hood, both methods still use the Maven coordinates of the plugin.

If you add those Maven coordinates (with versions) to your Java Platform , then you can import the platform into your project. But where?

Defining plugin versions

There are a lot of ways to define plugins, so I'll only describe one, and coincidentally it will be compatible with defining the version using a Java Platform.

If you're familiar with buildSrc convention plugins, you'll know that they can apply plugins, but they can't define versions.

// ./buildSrc/src/main/kotlin/kotlin-jvm-convention.gradle.kts

plugins {
  kotlin("jvm") version "1.8.0" // error: pre-compiled script plugins can't set plugin versions!
}

Instead, plugin versions must be defined in the build config for buildSrc

// ./buildSrc/build.gradle.kts

plugins {
  `kotlin-dsl`
}

dependencies {
  // the Maven coordinates of the Kotlin JVM plugin - including the version
  implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
}

This looks a lot more traditional, and so I hope the next step is clean: use your Java Platform!

Applying a Java Platform to buildSrc

// ./buildSrc/build.gradle.kts

plugins {
  `kotlin-dsl`
}

dependencies {
  // import your Java Platform
  implementation(platform("my.group:my-platform:1.2.3"))

  // no version necessary - it will be supplied by my.group:my-platform
  implementation("org.jetbrains.kotlin:kotlin-gradle-plugin")
}

Note that this same method will also apply if your projects an 'included build' instead of buildSrc.

Once the plugin versions are defined in ./buildSrc/build.gradle.kts , you can use them throughout your project (whether in convention plugins, or in subprojects), they will be aligned.

// ./subproject-alpha/build.gradle.kts

plugins {
  kotlin("jvm") // no version here - it's defined in buildSrc/build.gradle.kts
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM