Search code examples
kotlingradlegradle-plugingradle-kotlin-dsl

Build config for developing a Gradle Plugin written in Kotlin


I want to build a Gradle plugin written in Kotlin and publish it to the Gradle Plugin Portal. However, I am not clear about what build config I need to achieve this. There is a lot of confusing information, and strange errors regarding the 'embedded Kotlin' that Gradle uses.

The plugin development guide doesn’t any examples for a Kotlin + build.gradle.kts (only a simple one that uses java-gradle-plugin)

There’s some technical details about the embedded Kotlin, but it’s not clear about explicit requirements, and is more abstract technical implementation than a practical “how to” guide.

Using gradle init (v8.0.2) creates a project that has the Kotlin version manually set, so it might not match the embedded Kotlin version

// generated by `gradle init` command

plugins {
    // Apply the Java Gradle plugin development plugin to add support for developing Gradle plugins
    `java-gradle-plugin`

    // Apply the Kotlin JVM plugin to add support for Kotlin.
    id("org.jetbrains.kotlin.jvm") version "1.8.10"
}

What config should I use to build a Gradle plugin written in Kotlin?


Solution

  • As I understand it, these are the best-practice rules for configuring a Gradle Project that will build a Gradle Plugin written in Kotlin.

    First, note the markers † and ‡

    • ‡ - these rules are automatically configured if the kotlin-dsl plugin is applied
    • MAY† - this is allowed, but strongly discouraged. Bypassing this rule might seem fine, but might cause unforeseen compatibility issues with Gradle versions or features, or other plugins

    Gradle Plugin development with Kotlin - best practice rules

    • MUST only use these combinations of plugins:

    • MUST NOT use kotlin("jvm") with embedded-kotlin OR kotlin-dsl

      // ❌ invalid example
      plugins {
        `java-gradle-plugin`
        // don't specify two different Kotlin plugins
        `embedded-kotlin`
        kotlin("jvm") version "1.8.10"
      }
      
    • MAY† use any version of kotlin("jvm"), but SHOULD use the same version as is used by Gradle. There's a helper variable, embeddedKotlinVersion, that will provide this automatically.

      plugins {
         `java-gradle-plugin`
         kotlin("jvm") version embeddedKotlinVersion
      }
      
    • MUST set the Kotlin language level appropriately, following the Gradle compatibility matrix

      // assume that the current Gradle version is 7.6
      
      plugins {
         `java-gradle-plugin`
         `embedded-kotlin`
      }
      
      tasks.withType<KotlinCompile>().configureEach {
         kotlinOptions {
           apiVersion = "1.4"
           languageVersion = "1.4"
         }
      }
      
    • MUST NOT use add kotlin-stdlib-jdk8 or kotlin-reflect Kotlin libraries, or gradleKotlinDsl() as runtime/implementation dependencies, as they will conflict with Gradle’s embedded versions, and potentially with other Gradle plugins.

    • SHOULD† use compileOnly() dependencies on kotlin-stdlib-jdk8, kotlin-reflect, and gradleKotlinDsl() with a version equal to embeddedKotlinVersion

    • MUST use compileOnly(gradleApi()) (the version defaults to the version of Gradle being used to build the project) MAY† not match the Gradle versions that you want the plugin to support.

    • MUST add Kotlin compiler arguments -java-parameters -Xjvm-default=all -Xsam-conversions=class Xjsr305=strict for compatibility with Config Cache, and improved interoperability with Java and Groovy

    • SHOULD use Gradle's Plugin Publishing plugin to publish plugins to the Gradle Plugin Portal.

    An example build.gradle.kts

    Since most options are configured by the kotlin-dsl plugin, that provides the simplest method for building a Gradle plugin written in Kotlin.

    1. Create a project using a Gradle version that best matches the Gradle versions you want to support, since the Gradle version being used to build the plugin will provide most defaults.

      (While it is possible to create variants of Gradle plugins per Gradle version, this is very complicated and not well supported by the Gradle API)

    2. Create a build.gradle.kts with the following config:

    // build.gradle.kts
    
    plugins {
      `kotlin-dsl`
      id("com.gradle.plugin-publish") version "$gradlePluginPublishVersion"
    }
    
    // Optional: enable stricter validation, to ensure Gradle configuration is correct
    tasks.validatePlugins {
      enableStricterValidation.set(true)
    }
    
    // create the plugin
    // Read more: https://docs.gradle.org/current/userguide/implementing_gradle_plugins.html#plugin-development-plugin
    gradlePlugin {
      plugins {
        create("simplePlugin") {
          id = "org.example.greeting"
          implementationClass = "org.example.GreetingPlugin"
        }
      }
    }