I want to create convention plugins to simplify my...
# gradle
c
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
Yes, they're all in the same jar, you can do something like this:
Copy code
myExtension {
  jvm {
    // stuff
  }
  js {
    // foo
  }
  multiplatform {
    // bar
  }
}
Or you can just "react" to the plugin the user has applied:
Copy code
pluginManager.withId("org.jetbrains.kotlin.jvm") {
  // configure JVM
}
c
Oh, that's neat If I use
withId
, can I still use the
kotlin {}
block inside of it?
m
'it depends'
c
Ahah
m
If you're using the
kotlin-dsl
you can have generated accessors I think but I never use those
c
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
Copy code
(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:
Copy code
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
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
yes!
Make sure to make it a
compileOnly
dependency
c
Why not
implementation
?
m
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
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
If you do
implementation("kgp:1.8.20")
and I as a user do this:
Copy code
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
thanks 👍
v
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
Copy code
configure<KotlinJvmExtension> { ... }
instead of
Copy code
(project.extensions.getByName("kotlin") as KotlinJvmExtension).apply { ... }
t
Or you can just "react" to the plugin the user has applied:
Better to react on `org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin`:
Copy code
plugins.withType<org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin>() {
   // Do something
}
c
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
I think for now the best way would be to use following:
Copy code
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
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
for toolchain it is a little different approach:
Copy code
extensions.configure<org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension> {
     jvmToolchain(17)
}
c
Thanks for the help 👍
Can't I set the language versions etc from the top level extension too?
t
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
👍
v
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
ehm, I don't see anything related in JavaDoc for PluginsContainer.withId 🤔
v
No,
Project#getPlugins
🙂
c
Is there an easy way to figure out what the plugin class is? E.g. for the Android plugin
v
What for?
c
To know what I should put in
plugins.withType<???> { … }
But nevermind, there's also
plugins.withId("com.android.application") {}
v
As I said, don't use that, but
pluginManager.withPlugin
plugins
should not be used, look at its JavaDoc
c
Ah sorry, I misread your comment
366 Views