Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Writing Gradle plugins in Kotlin#

Kotlin hasa great Java interoperability story, making it a good language to write Gradle plugins.

For complete compatibility, several aspects require extra care. This page gives an overview of the different compatibility issues and recommended setup to avoid them.

Gradle compatibility#

When executing a build, Gradleforces its own version ofkotlin-stdlib, the embedded version.

For this reason, your plugin must depend on a version ofkotlin-stdlib that is compatible with the embedded version.

Gradle publishes the embedded versions in theKotlin compatibility matrix.

For an example, at the time of writing, Gradle 8.10 embedskotlin-stdlib:1.9.24.

Making your code compatible with the Kotlin embedded version#

You can use a more recent version of the Kotlin Gradle Plugin, but you'll have to make sure not to call any 2.0 API:

plugins{// Use latest version of the Kotlin Gradle Pluginid("org.jetbrains.kotlin.jvm").version("2.0.21")// java-gradle-plugin creates marker publications and plugin descriptorsid("java-gradle-plugin")}tasks.withType<KotlinCompile>().configureEach{// But make sure your plugin code only uses 1.9 APIscompilerOptions.apiVersion.set(KotlinVersion.KOTLIN_1_9)}kotlin{// Also make sure to depend on 1.9 kotlin-stdlib// See also https://youtrack.jetbrains.com/issue/KT-53462coreLibrariesVersion="1.9.24"}

Ensuring dependencies are compatible with the Kotlin embedded version#

In addition to your own code, your dependencies must also use a compatible version ofkotlin-stdlib.

Because the compiler doesn't run on dependencies,apiVersion does not help here, you'll have to check that the dependencies do not depend on a newer version ofkotlin-stdlib.

This can be done using a custom Gradle task:

/** * An example of a task that checks that no kotlin-stdlib > 1.9.24 is pulled * in the classpath. * Configuration cache and edge cases are left as an exercise to the reader. */tasks.register("checkGradleCompatibility"){doLast{valroot=configurations.getByName("runtimeClasspath").incoming.resolutionResult.rootComponent.get()root.dependencies.forEach{if(itisResolvedDependencyResult){valrdr=itvalrequested=rdr.requestedvalselected=rdr.selectedif(requestedisModuleComponentSelector&&requested.group=="org.jetbrains.kotlin"&&requested.module=="kotlin-stdlib"){valrequestedVersion=requested.versionvalselectedVersion=selected.moduleVersion?.versioncheck(selectedVersion==requestedVersion){"kotlin-stdlib was upgraded to$selectedVersion"}}}}}}

Alternative #1: relocating kotlin-stdlib#

If the steps above are too complicated, maybe because a required dependency uses a newer version of Kotlin, or because your own plugin code requires newer Kotlin features, you can shadow a relocated version ofkotlin-stdlib that doesn't clash with the Gradle embedded one.

To do this, you can useR8. You can read more about the processin this dedicated blog post.

Note

Shadow could be an alternative, but we have found that it doesn't work reliably becauseit relocates String constants as well

Alternative #2: using separate classloaders#

Another solution if you want to use a newerkotlin-stdlib without using relocation is to run your code in a separate, isolated, classloader. The glue code of your plugin and initialization still has to be compatible but as soon as you switch to a new classloader, you can use any dependencies without any risk of incompatibilities.

Projects such asGratatouille can help with that.

Groovy interoperability#

Because your plugin may be used from Groovy build scripts (build.gradle), it is important to have Groovy compatibility in mind.

General interoperability#

In general Groovy does not know anything about Kotlin. Avoid Kotlin-only features such as:

  • extension functions
  • default parameter values
  • function types
  • receivers
  • etc...

These features may be used in extra functionality for Kotlin callers, but it is important that all the base functionality of your plugin does not require them.

Closures#

Closure are an important piece of the Groovy build scripts. Every block is a closure under the hood.

Because dealing with Groovy closure from Kotlin (and Java) is cumbersome, Gradle allows to useAction<T> instead. For all types instantiated by Gradle (tasks, extensions,newInstance(), etc..), the Gradle runtime decorates all functions with a singleAction<T> parameter with an matching function accepting a closure (doc).

For an example, the Kotlin code below:

openclassMyExtension{fundoStuff(action:Action<Spec>){// ...}}

can be called from groovy with a closure:

myExtension{doStuff{// This is a closure even though groovy doesn't know about Action// ...}}

Difference withbuild.gradle.kts scripts#

If you are used to writingbuild.gradle.kts files, you may use thekotlin-dsl Gradle plugin to write your plugins.

kotlin-dsl configures the Kotlin compiler so that you can use precompiled scripts plugins and/or write similar syntax in your regular.kt files.

Thekotlin-dsl plugin:

  • applies"java-gradle-plugin".
  • applieskotlin-embedded to use the same Kotlin embedded version as your Gradle distribution.
  • applies the"kotlin-dsl-precompiled-script-plugins" allowing to usebuild.gradle.kts files.
  • addsgradleKotlinDsl() to thecompileOnly configuration.
  • configures thesam-with-receiver Kotlin compiler plugin to transformit. usages intothis..
  • configures thekotlin-assignment Kotlin compiler plugin to allow settingProperty with the= operator.
  • sets KotlinapiVersion andlanguageVersion according to Gradlecompatibility matrix.
  • adds the-Xsam-conversions=class compiler option.
  • adds others compiler options for compatibility:

This is a significant departure from the baseline Kotlin configuration so be aware of the trade-offs when usingkotlin-dsl.

Also,kotlin-dsl targets the Kotlin embedded version of your current distribution. If you want to be compatible with lower versions of Gradle, using thecom.jetbrains.kotlin.jvm plugin provides more flexibility.

June 30, 2025

[8]ページ先頭

©2009-2025 Movatter.jp