autoconfigure-gradle-plugin
autoconfigure-gradle-plugin copied to clipboard
Consider leveraging the possibilities with Settings-Plugins
As we are now using the concept a Plugin<Settings>
with the AutoConfigureSettingsPlugin
that opens some new possibilities we should consider.
The current architecture has some flaws:
- All third-party-plugins (Kotlin, Node, Swagger...) come as transitive dependencies of the AutoConfigure-Plugin, that means the initial download takes some time, and plugins that might never be applied are still being downloaded although they maybe never get used.
- Users have no (easy) chance to override the versions of those plugins.
- After creating this presentation with a comparison of the AutoConfigure-Plugin to the architecture of Spring Boot, I realized that we're not just auto-configuring plugins (like Spring Boot does) but also auto-applying plugins (which Spring Boot does not do, you have to "apply" modules manually by adding the starter-packages to the classpath).
- Some best practices including that one from @marcelkliemannel advice to never apply another plugin from a custom plugin as this violates clean code principles, and I mostly agree with that (possible exceptions see further below)
Now having the possibilities to be applied as Settings-Plugin, before projects are loaded, allows us to i.e. configure the pluginManagement
block there, specifically pre-configuring version-numbers of plugin centrally. That could then be the equivalent of Spring Boot's BOM, so that users don't need to care about the versions of third-party libraries anymore.
Basically, in a settings.gradle.kts
you can write code like this:
pluginManagement {
val kotlinVersion: String? by settings
val safeKotlinVersion = kotlinVersion ?: "1.7.20"
plugins {
kotlin("jvm") version safeKotlinVersion
kotlin("kapt") version safeKotlinVersion
kotlin("plugin.serialization") version safeKotlinVersion
}
}
This allows to define a default version for all Kotlin-Plugins (1.7.20
in that case) and users might override that version by adding a kotlinVersion
property to gradle.properties
.
In the build.gradle.kts
it is then sufficient for the user to just specify the plugin without a version:
plugin {
kotlin("jvm")
}
Still, that would mean, that users would need to apply the plugins by themselves then (instead of us applying automatically if src/main/kotlin
exists) and we'd just jump in and autoconfiguring when the plugin is applied.
Spring Boot does that that by adding 3rd-party dependencies to the compileOnly
scope and then working with annotations like @ConditionalOnClass
to configure those libraries and beans.
Now I've created a playground-repository to try if that would work with Gradle plugins as well. In the AutoConfigureSettingsPlugin
you find code like that:
class AutoConfigureSettingsPlugin : Plugin<Settings> {
override fun apply(settings: Settings) {
val kotlinVersion = "1.7.21" // TODO optionally read from Settings
settings.pluginManagement.plugins { p ->
p.id("org.jetbrains.kotlin.jvm").version(kotlinVersion)
p.id("org.jetbrains.kotlin.kapt").version(kotlinVersion)
p.id("org.jetbrains.kotlin.plugin.serialization").version(kotlinVersion)
}
settings.gradle.projectsLoaded {
it.gradle.allprojects { p ->
p.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
p.logger.quiet("Applied KOTLIN in ${p.name}")
p.logger.quiet(p.extensions.findByName("kotlin")!!.javaClass.classLoader.toString())
p.logger.quiet(this.javaClass.classLoader.toString())
val kotlin = p.extensions.getByName("kotlin") as KotlinProjectExtension
kotlin.jvmToolchain {
(it as JavaToolchainSpec).languageVersion.set(javaVersion)
}
}
}
}
}
}
So that would be quite the equivalent to how Spring Boot works. The user decides which plugins to apply and we then step in and configure those.
The problem is: it does not work this way. You will find the log Applied KOTLIN
in the code, but the problem is that in the next line you will get a NoClassDefFoundError
for KotlinProjectExtension
. The thing is that this class basically is on the classpath of the build script (as it is loaded in the build.gradle
) but it's inside another ClassLoader. The output is:
Applied KOTLIN in single-kotlin-module
VisitableURLClassLoader(ClassLoaderScopeIdentifier.Id{coreAndPlugins:settings[:]:buildSrc[:]:root-project[:](export)})
VisitableURLClassLoader(ClassLoaderScopeIdentifier.Id{coreAndPlugins:injected-plugin(local)})
Just clone the playground repository to dig deeper into that.
Don't know if we could manage to fix that, but even if, we'd need to ask ourselves how to handle the case that for some cases we do want plugins to be applied automatically (i.e. Jacoco or the License-Plugin).
I also checked the possibilities of convention-plugins but also here the official documentation tells you that in case you're applying 3rd-party plugins you need to add them as implementation
dependencies (and not compileOnly
) to your classpath, and that would end us up in the same situation as we are already.
Still I believe it's worth digging into that topic and the possibilities with Settings-Plugins more deeper.