kotlinとkotlin-dslでAndorid用GradlePluginを作成する

# kotlinとkotlin-dslでAndorid用GradlePluginを作成する

kotlinとkotlin-dslを利用し、Android向けのGradle Pluginを作成したときに調査した内容をメモです。

Gradle Pluginの作成がはじめての場合、下記ドキュメントを読むと全体像が把握できます。
[Writing Custom Plugins - Gradle User Manual](https://docs.gradle.org/current/userguide/custom_plugins.html)
[DroidKaigi 2018でGradleプラグインを作って開発効率を改善しようって発表をした話 – Yuki Fujisaki / tnj – Medium](https://medium.com/@tnj/talked-at-droidkaigi-2018-b80a8eed9ad9)

kotlinでの実装はkotlin-dslリポジトリにGradlePluginのサンプルがあったので、こちらを参考にしながら進めていきました。
  [kotlin-dsl/samples/gradle-plugin at master · gradle/kotlin-dsl · GitHub](https://github.com/gradle/kotlin-dsl/tree/master/samples/gradle-plugin)

## kotlin-dslを使用できるようにする

### build.gradle.ktsへのマイグレーション

下記を参考にしながら、既存のbuild.gradleをbuild.gradle.ktsにマイグレーションをしました。
[Migrating build logic from Groovy to Kotlin](https://guides.gradle.org/migrating-build-logic-from-groovy-to-kotlin/)

最終的にすべて書き換えましたが、最初はkotlin-dslにあったgroovy-interopサンプルを参考に、既存のbuild.gradleと併用しつつ徐々に書き換えていきました。
  [kotlin-dsl/samples/groovy-interop at master · gradle/kotlin-dsl · GitHub](https://github.com/gradle/kotlin-dsl/tree/master/samples/groovy-interop)

```kotlin:build.gradle.kts
apply(from = "build.gradle")
```

また、settings.gradleに下記を追加することで、settings.gradle.ktsへの置き換えも後回しにできます。

```groovy:settings.gradle
rootProject.buildFileName = 'build.gradle.kts'
```

### Pluginの追加

build.gradle.ktsではいくつかpluginのエイリアスを用意しており、id指定なしで使用することができます。

```kotlin:build.gradle.kts
plugins {
    `kotlin-dsl`
    `java-gradle-plugin`
    `maven-publish`
}
```
`kotlin-dsl`を追加すると、build.gradle.kts以外のkotlinのコード上でも`org.gradle.kotlin.dsl`パッケージを使用することができるようになります。

## Pluginの実装

### Pluginクラスの作成

org.gradle.api.Pluginを継承したクラスを作成します。

```kotlin
class MyPlugin : Plugin<Project> {

    override fun apply(project: Project) {
    }
}
```

### タスクの追加

タスクの作成は下記のように実装することができます。

```kotlin
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](https://github.com/gradle/kotlin-dsl/blob/master/samples/task-dependencies/build.gradle.kts)

```kotlin
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では下記のように実装することができます。

```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を作成しています。

```kotlin
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](https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.AppExtension.html)です。
applicationVariantsをはじめ、Androidプロジェクトのbuild.gradleで使用するプロパティが定義されています。

kotlinでは`project.extensions["extensionの名前"]`で取得することができます。
AppExtensionの名前は`android`です。

また、AppExtensionのプロパティにアクセスできるように`as AppExtension`でキャストをします。

さらにAppExtensionは`com.android.build.gradle`パッケージにあるため、dependenciesに追加してkotlinコードから参照できるようにします。

```kotlin:build.gradle.kts
dependencies {
    implementation("com.android.tools.build", "gradle", "X.X.X")
}
```

#### Variantの取得

```kotlin:Variant取得部分
android.applicationVariants.all { variant ->
    variant.assemble.doLast {
        //dosomething
    } 
  }
}  
```
```kotlin:extentionのみ抜粋
fun <T> DomainObjectCollection<T>.all(action: T.(T) -> Unit) {
    all {
        action(this)
    }
}
```
各Variantの取得には`applicationVariants.all`を使用します。
  [AppExtension#applicationVariants - Android Plugin DSL Reference](https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.AppExtension.html#com.android.build.gradle.AppExtension:applicationVariants)

allは[org.gradle.api.DomainObjectCollection](https://docs.gradle.org/current/javadoc/org/gradle/api/DomainObjectCollection.html#all-org.gradle.api.Action-)に定義されており、引数に[org.gradle.api.Action](https://docs.gradle.org/current/javadoc/org/gradle/api/Action.html)を受け取ります。


しかし、kotlinでは引数のActionが`ApplicationVariant.() -> Unit`として解釈され、lambdaで引数をもらうことができませんでした。
  [Kotlin not able to convert gradle's Action class to a lambda - Stack Overflow](https://stackoverflow.com/questions/47908148/kotlin-not-able-to-convert-gradles-action-class-to-a-lambda)

allで完結する場合は`this`が使用できますが、その後の`doLast`の引数もActionなので`this`を区別する必要があります。
そのため`ApplicationVariant.(ApplicationVariant) -> Unit`を受け取る`all`をextentionとして定義し、メソッドの中でもともと用意されていたallを呼び出しています。

上記のようにgroovyのコードをベースに実装を進めていくと、ところどころ修正が必要な箇所がでてきます。

## リリース

java-gradle-pluginとmaven-publish pluginの設定をします。

### java-gradle-pluginの設定

[java_gradle_plugin](https://docs.gradle.org/current/userguide/java_gradle_plugin.html)を参考に設定する。

```kotlin:build.gradle.kts
gradlePlugin {
    (plugins) {
        "MyPlugin" {
            id = "my.plugin" //Plugin使用側でapply時に指定する値
            implementationClass = "my.plugin.MyPlugin" //Plugin実装クラス
        }
    }
}
```

### maven-publish pluginの設定

[Maven Publish Plugin](https://docs.gradle.org/current/userguide/publishing_maven.html)を参考に設定します。
今回は自分でたてたMaven Repositoryにリリースしたかったため以下のように記述。

```kotlin:build.gradle.kts
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](https://docs.gradle.org/current/userguide/custom_plugins.html)

android用gradle pluginについて説明があり参考になりました。
[DroidKaigi 2018でGradleプラグインを作って開発効率を改善しようって発表をした話 – Yuki Fujisaki / tnj – Medium](https://medium.com/@tnj/talked-at-droidkaigi-2018-b80a8eed9ad9)

kotlin-dslのサンプル集。特にGradle Pluginのサンプルが参考になりました。
[kotlin-dsl/samples at master · gradle/kotlin-dsl · GitHub](https://github.com/gradle/kotlin-dsl/tree/master/samples/)

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](https://github.com/StefMa/Gloc)