kotlinとkotlin-dslでAndorid用GradlePluginを作成する
kotlinとkotlin-dslを利用し、Android向けのGradle Pluginを作成したときに調査した内容をメモです。
Gradle Pluginの作成がはじめての場合、下記ドキュメントを読むと全体像が把握できます。
Writing Custom Plugins - Gradle User Manual
DroidKaigi 2018でGradleプラグインを作って開発効率を改善しようって発表をした話 – Yuki Fujisaki / tnj – Medium
kotlinでの実装はkotlin-dslリポジトリにGradlePluginのサンプルがあったので、こちらを参考にしながら進めていきました。
kotlin-dsl/samples/gradle-plugin at master · gradle/kotlin-dsl · GitHub
kotlin-dslを使用できるようにする
build.gradle.ktsへのマイグレーション
下記を参考にしながら、既存のbuild.gradleをbuild.gradle.ktsにマイグレーションをしました。
Migrating build logic from Groovy to Kotlin
最終的にすべて書き換えましたが、最初はkotlin-dslにあったgroovy-interopサンプルを参考に、既存のbuild.gradleと併用しつつ徐々に書き換えていきました。
kotlin-dsl/samples/groovy-interop at master · gradle/kotlin-dsl · GitHub
apply(from = "build.gradle")
また、settings.gradleに下記を追加することで、settings.gradle.ktsへの置き換えも後回しにできます。
rootProject.buildFileName = 'build.gradle.kts'
Pluginの追加
build.gradle.ktsではいくつかpluginのエイリアスを用意しており、id指定なしで使用することができます。
plugins {
`kotlin-dsl`
`java-gradle-plugin`
`maven-publish`
}
kotlin-dsl
を追加すると、build.gradle.kts以外のkotlinのコード上でもorg.gradle.kotlin.dsl
パッケージを使用することができるようになります。
Pluginの実装
Pluginクラスの作成
org.gradle.api.Pluginを継承したクラスを作成します。
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
}
}
タスクの追加
タスクの作成は下記のように実装することができます。
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.run {
tasks.create("myTask").doLast {
println("Hello, myTask")
}
}
}
}
他にも数パターン定義の仕方があり、kotlin-dslリポジトリのtask-dependenciesサンプルのように定義することもできます。
kotlin-dsl/build.gradle.kts at master · gradle/kotlin-dsl · GitHub
import org.gradle.kotlin.dsl.invoke
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.run {
tasks {
"myTask" {
doLast { println("Hello, myTask") }
}
}
}
}
}
tasksはorg.gradle.kotlin.dsl.invoke
をimportすることで使えるようになります。
Androidのタスクにアクションを追加
例としてassemble後にアクションを実行するplugin作成します。
groovyでは下記のように実装することができます。
class MyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.android.applicationVariants.all { variant ->
variant.assemble.doLast {
//do something
}
}
}
}
kotlinでは下記のように記述しました。groovyで書くよりも少々冗長になってしまいました。
また、groovyとの差を少なくするためにextentionを作成しています。
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val android = project.extensions["android"] as AppExtension
android.applicationVariants.all { variant ->
variant.assemble.doLast {
//do something
}
}
}
}
fun <T> DomainObjectCollection<T>.all(action: T.(T) -> Unit) {
all {
action(this)
}
}
詳細を見ていきます。
AppExtensionの取得
groovyコードproject.android
で取得できる値の実体はcom.android.build.gradle.AppExtensionです。
applicationVariantsをはじめ、Androidプロジェクトのbuild.gradleで使用するプロパティが定義されています。
kotlinではproject.extensions["extensionの名前"]
で取得することができます。
AppExtensionの名前はandroid
です。
また、AppExtensionのプロパティにアクセスできるようにas AppExtension
でキャストをします。
さらにAppExtensionはcom.android.build.gradle
パッケージにあるため、dependenciesに追加してkotlinコードから参照できるようにします。
dependencies {
implementation("com.android.tools.build", "gradle", "X.X.X")
}
Variantの取得
android.applicationVariants.all { variant ->
variant.assemble.doLast {
//dosomething
}
}
}
fun <T> DomainObjectCollection<T>.all(action: T.(T) -> Unit) {
all {
action(this)
}
}
各Variantの取得にはapplicationVariants.all
を使用します。
AppExtension#applicationVariants - Android Plugin DSL Reference
allはorg.gradle.api.DomainObjectCollectionに定義されており、引数にorg.gradle.api.Actionを受け取ります。
しかし、kotlinでは引数のActionがApplicationVariant.() -> Unit
として解釈され、lambdaで引数をもらうことができませんでした。
Kotlin not able to convert gradle's Action class to a lambda - Stack Overflow
allで完結する場合はthis
が使用できますが、その後のdoLast
の引数もActionなのでthis
を区別する必要があります。
そのためApplicationVariant.(ApplicationVariant) -> Unit
を受け取るall
をextentionとして定義し、メソッドの中でもともと用意されていたallを呼び出しています。
上記のようにgroovyのコードをベースに実装を進めていくと、ところどころ修正が必要な箇所がでてきます。
リリース
java-gradle-pluginとmaven-publish pluginの設定をします。
java-gradle-pluginの設定
java_gradle_pluginを参考に設定する。
gradlePlugin {
(plugins) {
"MyPlugin" {
id = "my.plugin" //Plugin使用側でapply時に指定する値
implementationClass = "my.plugin.MyPlugin" //Plugin実装クラス
}
}
}
maven-publish pluginの設定
Maven Publish Pluginを参考に設定します。
今回は自分でたてたMaven Repositoryにリリースしたかったため以下のように記述。
publishing {
repositories {
maven(url = "http://my/repo") {
credentials {
username = "my_maven_user"
password = "my_maven_pass"
}
}
}
}
設定後はkotlin-dslを使用していないときと同様、 publish
やpublishToMavenLocal
でリリースすることができます。
また、使用する側もkotlin-dslで作成されたpluginかを区別することなく使用することができます。
参考資料
gradle pluginの作り方ガイド
Writing Custom Plugins - Gradle User Manual
android用gradle pluginについて説明があり参考になりました。
DroidKaigi 2018でGradleプラグインを作って開発効率を改善しようって発表をした話 – Yuki Fujisaki / tnj – Medium
kotlin-dslのサンプル集。特にGradle Pluginのサンプルが参考になりました。
kotlin-dsl/samples at master · gradle/kotlin-dsl · GitHub
DroidconでのGradle Pluginについての発表資料。本記事では記述していないkotlinでExtensionやTaskクラスを定義する手順についても記載されています。
GitHub - StefMa/Gloc: A Gradle plugin which I used as an example at my talk at the Droidcon Italy 18