build.gradleをbuild.gradle.ktsへ移行したい！

[ビルド構成を Groovy から KTS に移行する](https://developer.android.com/studio/build/migrate-to-kts?hl=ja)のドキュメントによると、
> Kotlin の方が可読性が高く、コンパイル時のチェックと IDE サポートが優れているため、今後、Gradle スクリプトの記述には Groovy よりも KTS が優先されます。

とあります。今後はGroovyのビルドスクリプトよりもKotlinビルドスクリプトの方が主流になっていくものと思われます。

とはいえ、未だにAndroid Studioで新規作成したプロジェクトはbuild.gradleで作られるし、Groovyのスクリプトで運用している開発環境はたくさんあると思います。
Android StudioではJavaからKotlinへは自動変換できますが、GroovyのビルドスクリプトをKotlinに変換する機能はまだありません。そのため、移行は手動で行う必要があります。

移行方法は上記リンク先にも記載がありますが、ビルドスクリプトの一部分しか紹介されていないため初見だと難しいです。

なので、今回はチュートリアルみたいな感じで、実際のbuild.gradleをbuild.gradle.ktsに置き換える手順を紹介したいと思います。

## Step 0: 元のbuild.gradle

以下のようなbuild.gradleがあったとします。これをbuild.gradle.ktsへ移行してみます。

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

android {
    compileSdk 31

    defaultConfig {
        applicationId "com.niusounds.myapplication"
        minSdk 24
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    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 {
        viewBinding true
    }
}

dependencies {
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
```

## Step 1: `'`を`"`に置き換える。

**まだファイル名は`build.gradle`のままです。**

まずはbuild.gradleの中にある`'`を`"`に置き換えましょう。
エディタの一括置換を使ったり、マルチカーソルを使ったりして機械的に置き換えると良いでしょう。

```gradle:build.gradle
plugins {
    id "com.android.application"
    id "org.jetbrains.kotlin.android"
}

android {
    compileSdk 31

    defaultConfig {
        applicationId "com.niusounds.myapplication"
        minSdk 24
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    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 {
        viewBinding true
    }
}

dependencies {
    implementation "androidx.core:core-ktx:1.7.0"
    implementation "androidx.appcompat:appcompat:1.4.1"
    implementation "com.google.android.material:material:1.5.0"
    implementation "androidx.constraintlayout:constraintlayout:2.1.3"
    testImplementation "junit:junit:4.13.2"
    androidTestImplementation "androidx.test.ext:junit:1.1.3"
    androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
}
```

## Step 2: `()`を付ける

次に、以下の箇所に`()`を付けるように修正します。

- `plugins`ブロックの`id`
- `dependencies`ブロックの`implementation`等
- `proguardFiles`
- `consumerProguardFiles`

※なぜ`()`を付けるのかというと、これらはGradleのAPI的にはメソッドとして定義されているものだからです。

```gradle:build.gradle
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    compileSdk 31

    defaultConfig {
        applicationId "com.niusounds.myapplication"
        minSdk 24
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    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 {
        viewBinding true
    }
}

dependencies {
    implementation("androidx.core:core-ktx:1.7.0")
    implementation("androidx.appcompat:appcompat:1.4.1")
    implementation("com.google.android.material:material:1.5.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.3")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.3")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
}
```


## Step 3: `=`を付ける

次に、以下の箇所に`=`を付けるように修正します。

- `compileSdk`
- `minSdk`
- `targetSdk`
- `applicationId`
- `versionCode`
- `versionName`
- `testInstrumentationRunner`
- `sourceCompatibility`
- `targetCompatibility`
- `useSupportLibrary`
- `buildFeatures`ブロックの`viewBinding`等

※なぜ`=`を付けるのかというと、これらはGradleのAPI的にはプロパティとして定義されているものだからです。

```gradle:build.gradle
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    compileSdk = 31

    defaultConfig {
        applicationId = "com.niusounds.myapplication"
        minSdk = 24
        targetSdk = 31
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    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 {
        viewBinding = true
    }
}

dependencies {
    implementation("androidx.core:core-ktx:1.7.0")
    implementation("androidx.appcompat:appcompat:1.4.1")
    implementation("com.google.android.material:material:1.5.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.3")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.3")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
}
```

ここまでの手順ではbuild.gradleのままでもビルドは通っているはずです。もし、ビルドエラーが出る箇所があったら、`=`か`()`のどちらを付けるか間違えているのだと思います。逆の方を付けてみましょう。
それでも難しい場合は、一旦コメントアウトして修正を後回しにするのも良いです。

## Step 4: ktsにリネーム

ここまで来たら、一度build.gradleをbuild.gradle.ktsにリネームします。

リネーム後に`./gradlew assemble`などを実行すると、おそらくビルドが失敗します。が、Step 1〜3の手順を行っていればエラーが出る箇所は限定的になるため、一つずつエラーの行を直していけば修正できます。
この段階では、エラーが出る行はとりあえずコメントアウトするのがおすすめです。そしてひとまず`./gradlew tasks`だけでもエラーなく通るようにしましょう。
**一度ビルドを通せる状態になればAndroid Studioがbuild.gradle.kts内をコード補完できるようになります。**

エラー箇所をコメントアウトしていき、`./gradlew tasks`が通せるようになったら、次のStepに進みます。

```gradle:build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    compileSdk = 31

    defaultConfig {
        applicationId = "com.niusounds.myapplication"
        minSdk = 24
        targetSdk = 31
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            // minifyEnabled false // TODO あとで直す
            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 {
        viewBinding = true
    }
}

dependencies {
    implementation("androidx.core:core-ktx:1.7.0")
    implementation("androidx.appcompat:appcompat:1.4.1")
    implementation("com.google.android.material:material:1.5.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.3")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.3")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
}
```

## Step 5: 壊れた箇所を置き換える

`./gradlew tasks`が通るようになったら、コメントアウトした箇所をちょっとずつコメント解除して、Kotlinの書き方に直していきます。
その際、Android Studioのコード補完が使えるはずなので、参考にしましょう。

### `minifyEnabled`を`isMinifyEnabled =`にする

```gradle:build.gradle
android {
  buildTypes {
    release {
      minifyEnabled false
    }
  }
}
```

```kotlin:build.gradle.kts
android {
  buildTypes {
    release {
      isMinifyEnabled = false
    }
  }
}
```

### task定義を置き換える

カスタムタスク、たとえばプロジェクトルートのbuild.gradleで宣言されている`clean`タスクのようなものを移行する場合は、以下のような感じになります。

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

```kotlin
tasks.create<Delete>("clean") {
    delete(rootProject.buildDir)
}
```

---

というわけで、シンプルなビルドスクリプトであればここまでの手順で置き換えが完了しているはずです。
出来上がったのがこちらになります。

```gradle:build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    compileSdk = 31

    defaultConfig {
        applicationId = "com.niusounds.myapplication"
        minSdk = 24
        targetSdk = 31
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = 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 {
        viewBinding = true
    }
}

dependencies {
    implementation("androidx.core:core-ktx:1.7.0")
    implementation("androidx.appcompat:appcompat:1.4.1")
    implementation("com.google.android.material:material:1.5.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.3")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.3")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
}
```


## Step 6: その他、いろいろ置き換える

ここから先はプロジェクトにより様々だと思うので、個別の事例を紹介していくのは難しいです。
私が趣味でメンテしているプロジェクトでbuild.gradleのkts移行を行った際の差分はこんな感じですので、よかったら参考にしてみてください。
https://github.com/niusounds/libReaStream/pull/16/files
https://github.com/niusounds/wdl-android/pull/1/files