Android + Gradle 探訪 - 前編: Groovy → KTS (Kotlin DSL) 書き換え

![droid_gr_kt2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359439/2168c8c5-a2b4-c23b-e125-e0441a20eb00.png)

最近は Gradle の構成を Groovy ではなく KTS (一連の Gradle の Kotlin DSL) で書いたり
`buildSrc` や Version Catalog に依存をまとめるのがナウそうですが，
Android Studio で作成した新規プロジェクトはシンプルな Groovy で記述されています．

Kotlin DSL などについて まったくわかっていないので (Groovy もだけど)，
この機会に `Empty Compose Activity` Project を題材にして，
最近の Gradle の構成や記述について調べようと思いました．

:point_down: 後編: 

https://qiita.com/mangano-ito/items/7e13f1988f9da61746b8

## ガイド

公式の移行ガイドがあるので見てみます:

https://developer.android.com/studio/build/migrate-to-kts?hl=ja

https://docs.gradle.org/current/userguide/migrating_from_groovy_to_kotlin_dsl.html

あとは，具体例として KotlinConf 2018 での講演や:

https://www.youtube.com/watch?v=mAtrEPeAJSc

[gradle/kotlin-dsl-samples](https://github.com/gradle/kotlin-dsl-samples) にあるサンプルの記述と照らし合わせるのも
わかりやすくてよさそうに思いました．

https://github.com/gradle/kotlin-dsl-samples

(この Repository は Archive されているのですけれど)


## 例

例として `Empty Compose Activity` の新規プロジェクトを作成してみます
(Android Studio Bumblebee 2021.1.1 Patch 2 + Gradle 7.0)．

![Empty Compose Activity](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359439/c9704c17-32f4-6a2e-1b3e-8c9275aafba3.png)

ごく標準的な，ルートには `settings.gradle`, `build.gradle`,
そして `app` レベルには `app/build.gradle` があるような構成ですね:

<details>
<summary><code>settings.gradle</code></summary>

```groovy:settings.gradle
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}
rootProject.name = "myproj20220215"
include ':app'
```
</details>

<details>
<summary><code>build.gradle</code></summary>

```groovy:/build.gradle
buildscript {
    ext {
        compose_version = '1.0.1'
    }
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '7.1.1' apply false
    id 'com.android.library' version '7.1.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.5.21' apply false
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
```
</details>

<details>
<summary><code>app/build.gradle</code></summary>

```groovy:/app/build.gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    compileSdk 31

    defaultConfig {
        applicationId "io.github.manganoito.myproj20220215"
        minSdk 21
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion compose_version
    }
    packagingOptions {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.7.0'
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.material:material:$compose_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.3.1'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
    debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
}
```
</details>


## 基本

これをどうやって KTS にするかというと，
まず，Groovy では可能で，Kotlin ではできない記述を置き換える必要がありそうです:

1. 文字列は Groovy ではシングルクォーテーションが使えましたが，Kotlin ではダブルクオーテーションである必要があります
2. 関数呼び出しの括弧は Groovy では省略できましたが，Kotlin では省略できません
3. 変数への代入は，Groovy では `=` を省略できましたが，Kotlin では省略できません

> ▲ [Prepare your Groovy scripts | Migrating build logic from Groovy to Kotlin](https://docs.gradle.org/7.0/userguide/migrating_from_groovy_to_kotlin_dsl.html#prepare_your_groovy_scripts) から

ところで (1) は自明なのですが，(2), (3) が微妙にわからないですよね．
どれが関数呼び出しで，どれが代入なのかがパッと見わからない．
(雰囲気でわかるといえばわかるのですが)

ひとつは `Go To` > `Implementation(s)` などで調べてやっていくことですが…

もうひとつとしては，
ここで大まかに書き換えたとしても結局エラーは出てしまうので，
先に進んでしまって KTS にしてから構文エラーをひとつずつ潰していく……
という地道な作業を行うほうが楽そうです．
(もちろん `git` などで管理して失敗したら退却する準備をしておいたうえで)

なので，おおまかに書き換えたら次のステップに進んでしまいます．


## Groovy → KTS として扱うようにするには？

さて，Groovy から KTS として扱ってもらうには
`*.gradle` を `*.gradle.kts` にリネームすれば OK です．
よくわからないけど便利にできている．

しかしながら，リネームしたあとに前の段階で構文をあわせたのにもかかわらず，
まだビルドが通らない箇所があることに気づくと思います．
それぞれ対応する Kotlin DSL の記述に調整してビルドが通るようにしていきます．

`settings.gradle` → `build.gradle` → `app/build.gradle` と
ひとつずつ順を追ってエラーをつぶしながら移行させていきましょう．


## `ext`

まず，依存するライブラリ共通のバージョンを定義するのに
`/build.gradle` 内の `ext` というブロック[^1]で Groovy では書かれていたわけですが，
これは使えないということで別の記述にしてみます．

[^1]: [Extra Properties](https://docs.gradle.org/7.0/userguide/writing_build_scripts.html#sec:extra_properties) というようですね

```kotlin:build.gradle.kts
buildscript {
    extra.apply {
        set("compose_version", "1.0.1")
    }
}
```

こうするとおなじように Extra Properties を定義できるのですが，
大きなプロジェクトでは昨今の複数のモジュールの管理を考えて
別の便利なパターンを使うのもいいかなと思いました．
(後編の記事で紹介しています)


## `plugins`

次に `id "com.android.application" version "7.1.2" apply false` とあって，
これはどういう書き方なのだろう…となるのですが，

これは `id` が単純な関数呼び出しで，
`version` と `apply` がその返り値の `PluginDependencySpec` に対する
`infix` の拡張関数の呼び出しとなるので
`id` だけを括弧で囲みます:

```kotlin:build.gradle.kts
plugins {
    id("com.android.application") version "7.1.2" apply false
    id("com.android.library") version "7.1.2" apply false
    id("org.jetbrains.kotlin.android") version "1.5.21" apply false
}
```


## `task`

`task clean(type: Delete)` となっているところが構文エラーで落ちるので，
これをどう書き換えるのかな，となります:

```groovy:builde.gradle
task clean(type: Delete) {
    delete rootProject.buildDir
}
```

[Custom Task](https://docs.gradle.org/7.0/userguide/custom_tasks.html)というやつで，
この例でいえば `org.gradle.api.tasks.Delete` で定義されたものを使うようになっているということです．

公式のガイドを見ると，`tasks` あたりを使う感じになるようなので，
完全に雰囲気で `tasks.register<Delete>("clean")` に書き換えます:

https://docs.gradle.org/7.0/userguide/kotlin_dsl.html#using_the_container_api

```kotlin:build.gradle.kts
tasks.register<Delete>("clean") {
    delete(rootProject.buildDir)
}
```

変更後同期して特にエラーがみられず，
`Build` > `Clean Project` が無事できれば OK です．


## `minifyEnabled`

ここまで書き換えれば，プロジェクトルートにある `build.gradle.kts` は
いい感じになったのではないかと思います．

ビルドが通ることを確認したら，
次は `app` レベルの `app/build.gradle.kts` もリネーム & 書き換えしていきます．

そうしていくと…
いきなり `minifyEnabled` がない！ と怒られてしまうのですが，

```diff_kotlin:app/build.gradle.kts
android {
    buildTypes {
        release {
-           minifyEnabled = false
+           isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
}
```

単に `isMinifyEnabled` になっているので名前を変えてあげます．

## `compose_version`

更に `"androidx.compose.ui:ui:$compose_version"` みたいにしているところで，さっき変えたばかりの Extra Property が見つからないよというエラーになるので，明示的に定義することにします．

```kotlin:app/build.gradle.kts
val compose_version: String by project
```

こうすれば使えるようになります．
Delegated Property なのが Kotlin な感じでオシャレ．


## ビルドできるように

こうやってぬるりと書き換えていくことで，
ひとまず `Empty Compose Activity` Project については
Groovy → KTS へ移行することができたはずです．


## さらに

さて，`ext` の節で書いたように，KTS の移行とは関係はないのですが
最近ではマルチモジュールでも共通で参照できるように
依存するライブラリの記述をまとめておくやりかたがよく使われているようです．

これについては後編の記事で紹介します:

https://qiita.com/mangano-ito/items/7e13f1988f9da61746b8