The typical Kotlin configuration in a Gradle project is very boilerplate, and I'm looking for a way of abstracting it out into an external build script so that it can be reused.
I have a working solution (below), but it feels like a bit of a hack as the kotlin-gradle-plugin doesn't work out of the box this way.
It's messy to apply any non-standard plugin from an external script as you can't apply the plugin by id, i.e.
apply plugin: 'kotlin'
will result in Plugin with id 'kotlin' not found.
The simple (well, usually) workaround is to apply by the fully qualified classname of the plugin, i.e.
apply plugin: org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
which in this case throws a nice little exception indicating that the plugin probably wasn't meant to be called this way:
Failed to determine source cofiguration of kotlin plugin.
Can not download core. Please verify that this or any parent project
contains 'kotlin-gradle-plugin' in buildscript's classpath configuration.
So I managed to hack together a plugin (just a modified version of the real plugin) which forces it to find the plugin from the current buildscript.
kotlin.gradle
buildscript {
ext.kotlin_version = "1.0.3"
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
apply plugin: CustomKotlinPlugin
import org.jetbrains.kotlin.gradle.plugin.CleanUpBuildListener
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
import org.jetbrains.kotlin.gradle.plugin.KotlinPlugin
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
/**
* Wrapper around the Kotlin plugin wrapper (this code is largely a refactoring of KotlinBasePluginWrapper).
* This is required because the default behaviour expects the kotlin plugin to be applied from the project,
* not from an external buildscript.
*/
class CustomKotlinPlugin extends KotlinBasePluginWrapper {
@Override
void apply(Project project) {
// use String literal as KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY constant isn't available
System.setProperty("kotlin.environment.keepalive", "true")
// just use the kotlin version defined in this script
project.extensions.extraProperties?.set("kotlin.gradle.plugin.version", project.property('kotlin_version'))
// get the plugin using the current buildscript
def plugin = getPlugin(this.class.classLoader, project.buildscript)
plugin.apply(project)
def cleanUpBuildListener = new CleanUpBuildListener(this.class.classLoader, project)
cleanUpBuildListener.buildStarted()
project.gradle.addBuildListener(cleanUpBuildListener)
}
@Override
Plugin<Project> getPlugin(ClassLoader pluginClassLoader, ScriptHandler scriptHandler){
return new KotlinPlugin(scriptHandler, new KotlinTasksProvider(pluginClassLoader));
}
}
This can then be applied in any project (i.e. apply from: "kotlin.gradle"
) and you're up and running for Kotlin development.
It works, and I haven't had any issues yet, but I'm wondering if there is a better way? I'm not really keen on merging in changes to the plugin every time there's a new version of Kotlin.