Gradle を Kotlin で書こう

今までなんとなくコピペとかコピペとかコピペで書いていた gradle 。
Groovy とか言う音ゲーのタイトルっぽいスクリプトで書かれています。

なんとその gradle が Kotlin で記述できるではありませんか！
これで少しは理解に身が入ると言うもの。さっそく試したいと思いました。


## Kotlin DSL ??

- Domain-Specific Language

特定の課題を解決するために特化された言語
SQL や HTML, Groovy なんかも DSL になるらしいですね。

Kotlin でスクリプトが書ける機能です。


## Migration

今回は元々あるプロジェクトへの導入方法になります。


### Environment

- Android Studio 3.5.3
- Gradle 6.0.1
- Kotlin 1.3.61
- JDK 8


### Rename and Rewrite

ほぼほぼ Android Studio の New Project で自動生成される gradle を書き換えます。

__1. `settings.gradle -> settings.gradle.kts` に Rename and Rewrite__

```settings.gradle.kts
include(":app")
rootProject.name = "MyApplication"
```

__2. ルートレベルの `build.gradle -> build.gradle.kts` に Rename and Rewrite__

```build.gradle.kts
buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:3.5.3")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61")
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task<Delete>("clean") {
    delete(rootProject.buildDir)
}
```


__3. モジュール内の `build.gradle -> build.gradle.kts` に Rename and Rewrite__

```app/build.gradle.kts
plugins {
    id("com.android.application")

    kotlin("android")

    kotlin("android.extensions")
}

android {
    compileSdkVersion(29)
    buildToolsVersion = "29.0.0"
    defaultConfig {
        applicationId = "<ApplicationId>"
        minSdkVersion(23)
        targetSdkVersion(29)
        versionCode = 1
        versionName = "0.1.0"
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
}

dependencies {
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61")
    implementation("androidx.appcompat:appcompat:1.1.0")
    implementation("androidx.core:core-ktx:1.2.0-rc01")
    implementation("androidx.constraintlayout:constraintlayout:2.0.0-beta2")
    testImplementation("junit:junit:4.12")
    androidTestImplementation("androidx.test.ext:junit:1.1.1")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.2.0")
}
```

__4. Sync Now !!!😎__

Success!!😎


## 依存関係をまとめる

これで Kotliner として一歩前に進んだ気がします。やったね。 

意気揚々と Android 開発のバイブル __DroidKaigi__ のリポジトリを見に行くと何やら見た事ない書き方が。

[conference-app-2020/build.gradle at master · DroidKaigi/conference-app-2020 · GitHub](https://github.com/DroidKaigi/conference-app-2020/blob/master/android-base/build.gradle)


```
import dependencies.Dep

...

dependencies {
    ...

    implementation Dep.Kotlin.stdlibJvm
    implementation Dep.AndroidX.appCompat
    implementation Dep.AndroidX.coreKtx
    implementation Dep.AndroidX.constraint
    implementation Dep.AndroidX.activityKtx

    ...
}
```

Packages?? Dep?? 🤔


### 依存関係を定義して使い回す

マルチモジュールで開発する時にライブラリの情報とかがいろんなところに散りばめられて管理が大変らしい。
それらを `buildSrc` に纏めると一元管理できていいよね😂 との事。

たしかに見やすくなっているので実際に試してみました。


__1. プロジェクトのルートに `buildSrc` ディレクトリを作成__

まず object を定義するモジュール? 用のディレクトリを作成します。

- __普通にディレクトリを作成します__ 。
( Android Studio では `New -> Directory -> buildSrc` )

Add Module とかではない。(最初間違えました。。。)


__2. Sync Now !!!😎__

`.gradle`, `build` ができあがると思います。


__3. `build.gradle.kts` を作成__

```build.gradle.kts
plugins {
    `kotlin-dsl`
}
repositories {
    jcenter()
}
```


__4. 変数の定義をするファイルを作成__

今回は `src/main/kotlin/dependencies` 配下に置きました。
これも普通に `src -> main -> kotlin -> ...` とディレクトリを作成していきます。

あとはソースファイルを作成していつもの様に変数定義します。

我らが DroidKaigi さんは Package 情報も切り出していたので真似してみました。


```dependencies/Dep.kt
object Dep {
    object Plugin {
        val android = "com.android.tools.build:gradle:3.5.3"
        val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Kotlin.version}"
        val safeArgs = "androidx.navigation:navigation-safe-args-gradle-plugin:${AndroidX.Navigation.version}"
    }

    object Test {
        val junit = "junit:junit:4.12"
        val androidJunit = "androidx.test.ext:junit:1.1.1"
        val espressoCore = "androidx.test.espresso:espresso-core:3.2.0"
    }

    object Kotlin {
        const val version = "1.3.61"
        val stdlibJdk = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$version"
    }

    object AndroidX {
        val appCompat = "androidx.appcompat:appcompat:1.1.0"
        val coreKtx = "androidx.core:core-ktx:1.2.0-rc01"
        val constraint = "androidx.constraintlayout:constraintlayout:2.0.0-beta2"
    }
}
```

```dependencies/Packages.kt
object Packages {
    const val id = "<Application Id>"
    const val debugIdSuffix = ".debug"

    object SdkVersion {
        const val target = 29
        const val compile = 29
        const val min = 21
    }

    object Version {
        private const val major = 0
        private const val minor = 1
        private const val build = 0

        val code = (major * 100 + minor * 10 + build)
        val name = "$major.$minor.$build"
    }
}
```

__5. Sync Now !!!😎__

準備できました。
あとは gradle ファイル内で import して使うだけ。


__6. import__

```
import dependencies.Packages
import dependencies.Dep

android {
    compileSdkVersion(Packages.SdkVersion.compile)
    buildToolsVersion = Packages.SdkVersion.compile.toString()
    defaultConfig {
        applicationId = Packages.id
        minSdkVersion(Packages.SdkVersion.min)
        targetSdkVersion(Packages.SdkVersion.target)
        versionCode = Packages.Version.code
        versionName = Packages.Version.name
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
}

dependencies {
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
    implementation(Dep.Kotlin.stdlibJdk)
    implementation(Dep.AndroidX.appCompat)
    implementation(Dep.AndroidX.coreKtx)
    implementation(Dep.AndroidX.constraint)

    testImplementation(Dep.Test.junit)
    androidTestImplementation(Dep.Test.androidJunit)
    androidTestImplementation(Dep.Test.espressoCore)
}
```

この調子でルートレベルの gradle も書き換えます

```build.gradle.kts
import dependencies.Dep

buildscript {
    dependencies {
        classpath(Dep.Plugin.android)
        classpath(Dep.Plugin.kotlin)
    }
}
```

__7. Sync now !!!😎__


Succe...ん??

![c7b7ac52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308397/bfb225d2-ebb1-2db0-dcb1-6c1ebf532d9d.png)


```
org.gradle.internal.exceptions.LocationAwareException: Build file '../build.gradle.kts' line: 10
Script compilation errors:

  Line 10:         classpath(Dep.Plugin.android)
                             ^ Unresolved reference: Dep

  Line 11:         classpath(Dep.Plugin.kotlin)
                             ^ Unresolved reference: Dep
```

あれ？参照できない？？
補完はちゃんと効いてるしコンパイルした時だけエラーになる。。。
buildSrc の build が走ったあとの Configuration でエラーだし。。。
Android Studio を 3.6 にあげてもだめ。。。


試しに src/main/kotlin 配下に置くか build.gradle を Groovy で書くと通りました。なぜだ。。。

ひとまず Plugin 関連の定義だけ別ファイルに切り出して直下に置く対応をしました。


## Conclusion

マルチモジュールではない場合の恩恵はそこまでなのかもしれませんが、
補完が効くので普通に書きやすいでした。
あとエラーの箇所もわかりやすいのでよき。


新しい事を学習するのは楽しいですね。(遅)
~~しかし、今年の DroidKaigi リポジトリでは Groovy で書かれていましたがどうしてなんでしょう。( Plugin の参照エラーのため？？ )~~
この記事を投稿するほんの少し前に Issue あげられてました！！
名乗りあげようと思ったらもう Assigne 決まってて残念です…

今回やったことの他、Jetpack のライブラリ等、ナウなインプルをスタディするために
LINE の既読無視をするためのアプリを仕事の合間に開発中です。

[GitHub - tick-taku/NotificationWatcher](https://github.com/tick-taku/NotificationWatcher)


次回は Room の勉強か coroutine の勉強か。
たぶん Room かな。。。


## 参考

[【Android】dependenciesをIDE補完で記述する - Qiita](https://qiita.com/TaigaNatto/items/7ec9aa0f2144559c7eaa)

[The New Way of Writing Build Gradle with Kotlin DSL](https://proandroiddev.com/the-new-way-of-writing-build-gradle-with-kotlin-dsl-script-8523710c9670)

[【Android】buildSrcを使ってライブラリの定義をまとめる \| ITcowork Staff Blog](https://www.itcowork.co.jp/blog/?p=5357)

[GitHub - DroidKaigi/conference-app-2020: The Official Conference App for DroidKaigi 2020 Tokyo](https://github.com/DroidKaigi/conference-app-2020)