gradlegradle-plugingradle-kotlin-dslkotlin-gradle-pluginbuildsrc

Use Kotlin plugins from buildSrc


How can I apply the Kotlin plugins from a buildSrc plugin?

I have a Kotlin project with a build.gradle.kts file containing this:

plugins {
    application
    kotlin("jvm")
    kotlin("plugin.serialization")
}

I want to create a custom plugin in buildSrc:

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper

class MyPlugin: Plugin<Project> {
    override fun apply(project: Project) {
        project.pluginManager.apply("org.gradle.application") //This works
        project.pluginManager.apply("¿kotlin(jvm)?") //<-- Here is my doubt
        project.pluginManager.apply("¿kotlin(plugin.serialization)?") //<-- Here is my doubt
    }
}

And use it like this:

plugins {
    id("com.example.myplugin")
}

Solution

  • To apply Gradle plugins from within buildSrc plugins you need to do two things

    1. Add the plugins as dependencies in buildSrc/build.gradle.kts

      Plugins must be added as dependencies using the Maven coordinates, not the plugin ID†. The Maven coordinates of plugins can be found in the Gradle plugin portal.

      // buildSrc/build.gradle.kts
      
      plugins {
        `kotlin-dsl`
      }
      
      dependencies {
        // the Maven coordinates of the Kotlin Gradle and Serialization plugins
        implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
        implementation("org.jetbrains.kotlin:kotlin-serialization:1.7.20")
      }
      
    2. apply the plugins, either using the class, or the plugin ID.

      (Note that kotlin("jvm") is a helper function that obscures the actual Gradle plugin ID, which is org.jetbrains.kotlin.jvm)

      class MyPlugin: Plugin<Project> {
        override fun apply(project: Project) {
      
          project.pluginManager.apply("org.jetbrains.kotlin.jvm")
          project.pluginManager.apply("org.jetbrains.kotlin.plugin.serialization")
      
          // the plugin class for the Kotlin JVM & Serialization plugins
          project.plugins.apply(org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper::class)
          project.plugins.apply(org.jetbrains.kotlinx.serialization.gradle.SerializationGradleSubplugin::class)
        }
      }
      

      (it wasn't easy to find the plugin classes - I had to dig around in the jar to find the plugin marker artifact, e.g. kotlin-serialization-1.7.20-gradle71.jar!/META-INF/gradle-plugins/org.jetbrains.kotlin.plugin.serialization.properties)

    Precompiled script plugins

    You might also like to use precompiled script plugins. They allow for writing buildSrc script plugins that much more similar to standard build.gradle.kts files, and so you can apply plugins in the plugins block.

    // buildSrc/src/main/kotlin/conventions/kotlin-jvm.gradle.kts
    
    plugins {
      kotlin("jvm")
    }
    

    To enable precompiled script plugins, make sure you've applied the kotlin-dsl plugin in buildSrc/build.gradle.kts.

    // buildSrc/build.gradle.kts
    
    plugins {
      `kotlin-dsl`
    }
    

    Error: Plugin request for plugin already on the classpath must not include a version

    What happens when you apply the above steps and you get an error Plugin request for plugin already on the classpath must not include a version?

    There are a lot of ways to specify the version of a Gradle plugin (far too many in my opinion!). If you're updating an existing project with the above steps, you've just used one more way of specifying a version, and Gradle is spitting out an unhelpful error message.

    The way to fix it is to limit the number of ways your project specifies Gradle plugin versions. What's probably happened is that you've added a plugin in buildSrc/build.gradle.kts that you also apply in the plugins block of a subproject. Gradle will get confused about which version of the plugin you want.

    The way to fix it is to only specify the plugin version in a single place, and that place is as a dependency in buildSrc/build.gradle.kts.

    // my-cool-subproject/build.gradle.kts
    
    plugins {
      kotlin("jvm")// version "1.9.0" // version is not necessary, it's specified in buildSrc/build.gradle.kts
    }
    

    Footnotes

    † Note that it is actually possible to use the plugin ID as a regular dependency, with some manipulation. Gradle can discover plugins from any Maven repository via the Plugin Marker Artifact. Given a plugin ID the Maven GAV coordinates can be determined as follows:

    val kmpPluginId = "org.jetbrains.kotlin.multiplatform"
    val kmpPluginVersion = "1.9.0"
    val kmpMavenGav = "${kmpPluginId}:${kmpPluginId}.gradle.plugin:${kmpPluginVersion}"