A plugin that will bring type-safety to your convention plugins!
dependencies {
- implementation(versionCatalogs.find("libs").get().findLibrary("kotlin-stdlib").get())
+ implementation(libs.kotlin.stdlib)
}
According to Gradle docs, it is recommended to place convention plugins inside the included build or buildSrc
(which is also treated as an included build). In the ideal world, we would just copy the contents of our build.gradle.kts
and put it inside buildSrc/src/some-convention.gradle.kts
to be reused between subprojects. However, there are some serious limitations:
- the convention plugin can't use version catalog typesafe accessors (like
libs.kotlin.stdlib
) - the buildscript of included build (e.g
buildSrc/build.gradle.kts
) doesn't have access to the version catalog of the main build - there is no built-in way to convert plugin ID (like
org.jetbrains.kotlin.jvm
) to plugin dependency (likeorg.jetbrains.kotlin:kotlin-gradle-plugin:2.1.10
)
Note
If Gradle fixes some of the issues mentioned above, the respective features will be removed from typesafe-conventions
. Ideally, all the features will be removed, and this plugin will not be needed anymore ;) In that case, this README will point to Gradle docs with the replacements.
- Gradle version is at least 8.4
- There is
gradle/*.versions.toml
file - There is an included build for build logic (we will refer to it as
buildSrc
) - At least one project within
buildSrc
has precompiled script plugins enabled (you can do this by applying thekotlin-dsl
plugin)
Apply typesafe-conventions
in buildSrc/settings.gradle.kts
:
Important
It's a settings plugin (not project plugin) so apply it in settings.gradle.kts
!
plugins {
id("dev.panuszewski.typesafe-conventions") version "0.4.1"
}
Your project structure should be similar to the following:
.
├── gradle/
│ └── libs.versions.toml # define 'kotlin-jvm' plugin and 'kotlin-stdlib' library here
├── settings.gradle.kts
├── build.gradle.kts
├── ...
└── buildSrc/
├── settings.gradle.kts # apply 'typesafe-conventions' here
├── build.gradle.kts # you can use 'pluginMarker(libs.plugin.kotlin.jvm)' here! 🚀
└── src/
└── main/
└── kotlin/
└── some-convention.gradle.kts # you can use 'libs.kotlin.stdlib' here! 🚀
In plain Gradle, applying dependency from version catalog in a convention plugin would look like this:
dependencies {
implementation(versionCatalogs.find("libs").get().findLibrary("kotlin-stdlib").get())
}
After applying typesafe-conventions
, you can benefit from type-safe syntax:
dependencies {
implementation(libs.kotlin.stdlib)
}
If your convention plugins are classes implementing the org.gradle.api.Plugin
interface, and you have them in a named package (like buildSrc/src/main/kotlin/com/myapp/gradle
), you need to explicitly import the libs
extension:
package com.myapp.gradle
+import libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
class MyConventionPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.dependencies {
add("implementation", project.libs.kotest.assertions)
}
}
}
In plain Gradle, using version catalog in buildSrc/build.gradle.kts
would require manually registering it in the buildSrc/settings.gradle.kts
:
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
After applying typesafe-conventions
, you don't need the above configuration - it works out-of-the-box.
In plain Gradle, you need to manually make up the coordinates of plugin's marker artifact:
dependencies {
val plugin = libs.plugins.kotlin.jvm.get()
implementation("${plugin.pluginId}:${plugin.pluginId}.gradle.plugin:${plugin.version}")
}
After applying typesafe-conventions
, you can use pluginMarker
helper method:
dependencies {
implementation(pluginMarker(libs.plugins.kotlin.jvm))
}
As an alternative to buildSrc
, you can use custom included build (typically named build-logic
). The typesafe-conventions
will fit nicely in this kind of setup.
As opposed to buildSrc
, the included build can have multiple subprojects with convention plugins. For example, you can have something like this:
.
├── gradle/
│ └── libs.versions.toml
├── build.gradle.kts
├── settings.gradle.kts # includeBuild("build-logic")
├── ...
└── build-logic/
├── settings.gradle.kts # apply 'typesafe-conventions' here
├── ...
├── first-convention-plugin/
│ └── build.gradle.kts # apply 'kotlin-dsl' here
└── second-convention-plugin/
└── build.gradle.kts # apply 'kotlin-dsl' here
Tip
Why would you prefer build-logic
over buildSrc
? If your build contains a lot of projects, and those projects apply different combinations of convention plugins, placing every convention plugin in its own subproject can improve performance. It's because modifying the convention plugin code will only trigger reload of the subprojects that are actually using it.