Android + Gradle 探訪 - 後編: buildSrc や Version Catalog

![vercat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359439/a7bc9489-13f4-8663-8655-1f08229b57de.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/0537d70701e1e85223e7

## モジュール共通の定義

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

ここで紹介するのは，
ひとつは `buildSrc` に定義をまとめておいて参照するやりかた．
もうひとつは Gradle の Version Catalog として定義をまとめて参照するやりかたです．


## `buildSrc`


https://docs.gradle.org/7.0/userguide/organizing_gradle_projects.html#sec:build_sources

`buildSrc` というディレクトリをつくりまして，
ここにプロジェクトで共通の定数・処理を置くと，
いろんなモジュールで使えて便利だねということです．

つまり，ライブラリのバージョンであるとか，
そういった定数をまとめておくのがパターンになっているということです．

なので，今回はそのパターンで `buildSrc` に定数を定義しましょう．

KotlinConf 2018 の公演でいうと，
26:55 〜 `buildSrc` での管理について紹介されています:

https://youtu.be/mAtrEPeAJSc?t=1615


## `buildSrc/build.gradle.kts`

まず `buildSrc` というディレクトリをつくります．
その中に `build.gradle.kts` をまず置きます:

```kotlin:buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}
```

同じく Kotlin DSL として記述できるようにしておきます．

ファイル作成後，リラックスして待っていると
`.gradle` ディレクトリができていて，なにやら構成されたことがわかります:

![myproj20220302_–_buildSrc_build_gradle_kts__myproj20220302_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359439/e1a84aae-c4dd-822e-e80b-9f67d72cda83.png)


## バージョンを定義する

次に `buildSrc/src/main/kotlin` というフォルダをつくり，
そこに `Deps.kt` を配置して，
このファイルにライブラリの依存についての定数をまとめることにします．

```kotlin:buildSrc/src/main/kotlin/Deps.kt
package dependencies

object Versions {
    object AndroidX {
        const val compose = "1.0.1"
    }
}
```

`dependencies` パッケージに `Versions.AndroidX.compose` という階層で，
Jetpack Compose まわりのライブラリのバージョンについて定義してみます．

そうすると，`app/build.gradle.kts` で `import` して，
そのまま使うことができるわけです:

```kotlin:app/build.gradle.kts
import dependencies.Versions

android {
    composeOptions {
        kotlinCompilerExtensionVersion = Versions.AndroidX.compose
    }
}

dependencies {
    implementation("androidx.core:core-ktx:1.7.0")
    implementation("androidx.compose.ui:ui:${Versions.AndroidX.compose}")
    implementation("androidx.compose.material:material:${Versions.AndroidX.compose}")
    implementation("androidx.compose.ui:ui-tooling-preview:${Versions.AndroidX.compose}")
    // 以下略…
}
```

## 依存ライブラリを定義する

さらに，ライブラリの定義ごと定数としてまとめることを考えます:

```diff_kotlin:buildSrc/src/main/kotlin/Dependencies.kt
package dependencies

object Versions {
    object AndroidX {
        const val compose = "1.0.1"
    }
}

object Deps {
    object AndroidX {
        const val coreKtx = "androidx.core:core-ktx:1.7.0"
        const val composeUi = "androidx.compose.ui:ui:${Versions.AndroidX.compose}"
        const val composeMaterial = "androidx.compose.material:material:${Versions.AndroidX.compose}"
        const val composeUiToolingPreview = "androidx.compose.ui:ui-tooling-preview:${Versions.AndroidX.compose}"
        const val lifecycleRuntimeKtx = "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
        const val activityCompose = "androidx.activity:activity-compose:1.3.1"
    }
    // 以下略…
}
```

書き方は自由ですが，このような階層でまとめたとしましょう．
そうすると，`app/build.gradle.kts` はこのように書けることになります:

```kotlin:app/build.gradle.kts
import dependencies.Deps

dependencies {
    implementation(Deps.AndroidX.coreKtx)
    implementation(Deps.AndroidX.composeUi)
    implementation(Deps.AndroidX.composeMaterial)
    implementation(Deps.AndroidX.composeUiToolingPreview)
    implementation(Deps.AndroidX.lifecycleRuntimeKtx)
    implementation(Deps.AndroidX.activityCompose)
    // 以下略…
}
```

こうしておくことで，ライブラリ関係の依存が一箇所にまとまるので，
複数モジュールで構成されたプロジェクトでは，
それぞれの `build.gradle.kts` に定義が分散せずに
管理が楽になることが期待できますね．
(あと，頑張れば共通の Gradle の記述自体を追い出すこともできそうです)


## Version Catalog

別の手法としては Gradle の Version Catalog を使うことができそうです．

https://docs.gradle.org/7.0/userguide/platforms.html

ただ，Version Catalog は Gradle バージョン 7.4 より前では Feature Preview です:

https://docs.gradle.org/current/release-notes.html#version-catalogs

そのため，使用するにはまず `settings.gradle.kts` で Feature Preview を有効にします:

```kotlin:settings.gradle.kts
enableFeaturePreview("VERSION_CATALOGS")
```

次に，`gradle/libs.versions.toml` に Compose のバージョンを定義してみます:

```toml:gradle/libs.versions.toml
[versions]
compose = "1.0.1"
```

こうすれば Gradle 側で `libs.*` として参照できるので，
複数のモジュールで共通定義のバージョンを記述することができます:

```kotlin:app/build.gradle.kts
android {
    composeOptions {
        kotlinCompilerExtensionVersion = libs.versions.compose.get()
    }
}
```

`libs.versions.compose` は `org.gradle.api.provider.Provider<String>` が返ってくるので，
`get()` で `String` として取り出せば使うことができます． 

さらに依存ライブラリの定義を追加していきます．
今回は例としてこういうかたちにしてみました (どういう構成にするかは自由です):

```toml:gradle/libs.versions.toml
[versions]
compose = "1.0.1"

[libraries]
androidx-core-ktx = "androidx.core:core-ktx:1.7.0"
androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "compose" }
androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
androidx-lifecycle-runtime-ktx = "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
androidx-activity-compose = "androidx.activity:activity-compose:1.3.1"

junit = "junit:junit:4.13.2"
androidx-test-ext-junit = "androidx.test.ext:junit:1.1.3"
androidx-test-espresso-core = "androidx.test.espresso:espresso-core:3.4.0"
androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }

[bundles]
base = ["androidx-core-ktx", "androidx-lifecycle-runtime-ktx"]
compose = ["androidx-compose-ui", "androidx-compose-material", "androidx-compose-ui-tooling-preview", "androidx-activity-compose"]
test-base = ["junit"]
android-test-base = ["androidx-test-ext-junit", "androidx-test-espresso-core"]
android-test-compose = ["androidx-compose-ui-test-junit4"]
debug-compose = ["androidx-compose-ui-tooling"]
```

直接 `androidx-core-ktx = "androidx.core:core-ktx:1.7.0"` のように定義したり，
共通のバージョンを使うときは `androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }` のように
`[versions]` で定義したものを参照して書くことができます．
(ちなみに後者は TOML の [Inline Table](https://toml.io/en/v1.0.0#inline-table) なので 1 行で書かなければいけないようです)

最後の `[bundles]` は `[libraries]` で定義したライブラリを
任意のグループとして名前をつけてまとめることができるので便利です．

こうすれば複数のモジュールで共通定義の依存を記述することができます
(できていれば `libs.` あたりで補完が出てきてくれます):

```kotlin:app/build.gradle.kts
dependencies {
    versionCatalogs {
        implementation(libs.bundles.androidx.base)
        implementation(libs.bundles.androidx.compose)
        testImplementation(libs.bundles.testing.base)
        testImplementation(libs.bundles.testing.androidx.compose)
        debugImplementation(libs.bundles.debug.androidx.compose)
    }
}
```

`[bundles]` に定義した依存がそのまま 1 つのエントリのように記述できるので便利ですね．


## 最後に

いろいろ紹介したのですが，
IDE の対応が十分ではないなどデメリットもあるということで，
プロジェクトにあったものを選択できるとよいですね．
独断と偏見では KTS + `buildSrc` で依存管理というプロジェクトが最近は多いのではないでしょうか．
(データはありません)

そのうち Android Studio のスケルトンプロジェクトが
ナウい感じを示してくれると勝手に期待しています．