https://kotlinlang.org logo
Title
c

CLOVIS

03/14/2023, 9:44 PM
I want to create convention plugins to simplify my build. However, I have projects that use either
kotlin("jvm")
,
kotlin("js")
or
kotlin("multiplatform")
. Is it possible to create a convention plugin that configures all of them? I don't want to create one plugin per Kotlin plugin…
m

mbonnin

03/14/2023, 9:46 PM
Yes, they're all in the same jar, you can do something like this:
myExtension {
  jvm {
    // stuff
  }
  js {
    // foo
  }
  multiplatform {
    // bar
  }
}
Or you can just "react" to the plugin the user has applied:
pluginManager.withId("org.jetbrains.kotlin.jvm") {
  // configure JVM
}
c

CLOVIS

03/14/2023, 9:47 PM
Oh, that's neat If I use
withId
, can I still use the
kotlin {}
block inside of it?
m

mbonnin

03/14/2023, 9:48 PM
'it depends'
c

CLOVIS

03/14/2023, 9:48 PM
Ahah
m

mbonnin

03/14/2023, 9:48 PM
If you're using the
kotlin-dsl
you can have generated accessors I think but I never use those
c

CLOVIS

03/14/2023, 9:48 PM
I'm not trying to do complicated things, I essentially just want to set the jvmTarget/language version/API version… etc in a single place for all projects I am using
kotlin-dsl
, yes
m

mbonnin

03/14/2023, 9:49 PM
(project.extensions.getByName("kotlin") as KotlinJvmExtension).apply {}
works all the time, regardless of what plugins you apply
set the jvmTarget/language version/API version…
That should be pretty doable
This is how I do it:
tasks.withType(KotlinCompile::class.java).configureEach {
    kotlinOptions {
      freeCompilerArgs = freeCompilerArgs + listOf(
          "-opt-in=kotlin.RequiresOptIn",
      )

      apiVersion = "1.5"
      languageVersion = "1.5"

      (this as? KotlinJvmOptions)?.let {
        it.jvmTarget = "1.8"
      }
    }
  }
c

CLOVIS

03/14/2023, 9:51 PM
I see, so the convention plugin needs a dependency on the Kotlin plugin jar (to get
KotlinCompile
) but doesn't need to apply it, and the final build can apply whichever it wants?
m

mbonnin

03/14/2023, 9:57 PM
yes!
Make sure to make it a
compileOnly
dependency
c

CLOVIS

03/14/2023, 9:58 PM
Why not
implementation
?
m

mbonnin

03/14/2023, 9:58 PM
Because you're not controlling the version the user will apply ultimately
You can make it
implementation
and that'd most likely work but it's sending the wrong message
c

CLOVIS

03/14/2023, 10:01 PM
Is it better? If I declare it as
compileOnly
, Gradle won't know about it in the downstream project, so it risks selecting an incompatible version, right? Whereas, if it's
implementation
, it'll select the highest requested version during resolution anyway so it doesn't block the users?
m

mbonnin

03/14/2023, 10:03 PM
If you do
implementation("kgp:1.8.20")
and I as a user do this:
plugins {
  id("org.jetbrains.kotlin.jvm").version("1.7.0")
}
2 things can happen (I'm not sure which one actually) 1. 1.8 gets selected and as a user I am very surprised 2. 1.7 gets selected and your plugin might crash on missing KGP symbols
It's different if your plugin wraps KGP completely but if you leave the decision to apply KGP to the user then I'd make it
compileOnly
c

CLOVIS

03/14/2023, 10:04 PM
thanks 👍
v

Vampire

03/15/2023, 7:53 AM
If you're using the kotlin-dsl you can have generated accessors I think but I never use those
No. You only got the generated type-safe accessors for plugins applied using the
plugins { ... }
block. For all other cases you have to use the more verbose syntax. But you can use
configure<KotlinJvmExtension> { ... }
instead of
(project.extensions.getByName("kotlin") as KotlinJvmExtension).apply { ... }
t

tapchicoma

03/15/2023, 9:02 AM
Or you can just "react" to the plugin the user has applied:
Better to react on `org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin`:
plugins.withType<org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin>() {
   // Do something
}
c

CLOVIS

03/15/2023, 9:04 AM
Can you give a simple example of how to use this way of reacting to the base plugin to configure the language API target (or another property of the `kotlin`block)?
t

tapchicoma

03/15/2023, 9:09 AM
I think for now the best way would be to use following:
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask>().configureEach {
    compilerOptions {
        languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
        // Other common compilation options
    }
}
If you want to configure specific platform compilation options - it could be a little trickier. There is
org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
task interface for Kotlin/JVM, but not for other platforms. And you need to use specific task types in this case 😞
c

CLOVIS

03/15/2023, 9:10 AM
For now I just want to set the language levels & java toolchains so they're the same in all projects of the build, so I think that's enough?
t

tapchicoma

03/15/2023, 9:13 AM
for toolchain it is a little different approach:
extensions.configure<org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension> {
     jvmToolchain(17)
}
c

CLOVIS

03/15/2023, 9:15 AM
Thanks for the help 👍
Can't I set the language versions etc from the top level extension too?
t

tapchicoma

03/15/2023, 9:16 AM
not yet. Kotlin 1.9.0 will add extension DSL for
kotlin("jvm")
and
kotlin("js")
plugins, but MPP will be done in later releases due to its complexity
c

CLOVIS

03/15/2023, 9:17 AM
👍
v

Vampire

03/15/2023, 9:38 AM
Better to react on `org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin`:
```plugins.withType<org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin>() {
// Do something
}```
Actually not. The JavaDoc of
plugins
is telling you not to use it. The better way is what @mbonnin meant to say above,
pluginManager.withPlugin(...) { ... }
t

tapchicoma

03/15/2023, 10:38 AM
ehm, I don't see anything related in JavaDoc for PluginsContainer.withId 🤔
v

Vampire

03/15/2023, 10:39 AM
No,
Project#getPlugins
🙂
c

CLOVIS

03/16/2023, 7:28 PM
Is there an easy way to figure out what the plugin class is? E.g. for the Android plugin
v

Vampire

03/16/2023, 7:33 PM
What for?
c

CLOVIS

03/16/2023, 7:34 PM
To know what I should put in
plugins.withType<???> { … }
But nevermind, there's also
plugins.withId("com.android.application") {}
v

Vampire

03/16/2023, 7:34 PM
As I said, don't use that, but
pluginManager.withPlugin
plugins
should not be used, look at its JavaDoc
c

CLOVIS

03/16/2023, 7:35 PM
Ah sorry, I misread your comment