Gradle Kotlin DSL (build.gradle.kts) で構造体っぽい定数定義をしたい


Androidなどのプロジェクトでプロジェクト全体の定数を定義するときに、以下のような構造体っぽい定義を使っていました。

buildscript {
    def versionMajor = 1
    def versionMinor = 0
    def versionPatch = 0
    ext {
        pj = [
                versions : [
                        name: "${versionMajor}.${versionMinor}.${versionPatch}",
                        code: versionMajor * 10000 + versionMinor * 100 + versionPatch
                ],
                groupId      : "com.example",
                siteUrl      : "https://github.com/example/example",
                githubUrl    : "https://github.com/example/example",
                scmConnection: "scm:git:https://github.com/example/example.git"
        ]
    }
}

これをKTSに移植しようと思ったけど、やり方が分からず右往左往しました。

結論から言うと、等価な定義をすることはできるが、Null安全でも型安全でもなく、補完も効かないものなので、
buildSrcに定数クラスを作った方が良いということになります。

ktsに置けるextと等価な記述

gradleではext以下に定義しておくと、配下のモジュールから読み出せるグローバル変数のように扱えました。
これをKTSでやるには、by extraを使って定義します。
kotlinを導入したときに必ずついてくる、kotlin_versionでは

定義

val kotlin_version: String by extra("1.3.72")

利用

val kotlin_version: String by project

implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")

のように使います。Groovy DSLでは利用側は暗黙的に使えましたが、kotlinでは宣言が必要なのでちょっと不便ですね。

構造体はMap

冒頭にだした、構造体っぽい定義の実態はMapです。
同様の定義をするなら以下のように書きます。

val pj: Map<String, Any> by extra { mapOf(
    "versions" to mapOf(
        "name" to "${versionMajor}.${versionMinor}.${versionPatch}",
        "code" to versionMajor * 10000 + versionMinor * 100 + versionPatch
    ),
    "groupId" to "com.example",
    "siteUrl" to "https://github.com/example/example",
    "githubUrl" to "https://github.com/example/example",
    "scmConnection" to "scm:git:https://github.com/example/example.git"
)}

利用箇所では以下のように使います。

val pj: Map<String, Any> by project
groupId = pj["groupId"] as String

正直言うと微妙すぎますね。
Mapなのでgetの戻り値はNullableだし、Valueの型も統一しなければAnyになるし、keyはStringだしで、Null安全でも、型安全でもなく、補完も効きません。

buildSrcを使おう

ということで、Groovy DSLのような定義をKTSで実装するのは諦めて、buildSrcを使って、定数クラスを作ってしまうのが良いと思います。

buildSrcを作るにはプロジェクト配下にbuildSrcディレクトリを作り、以下のような内容のbuild.gradle.ktsファイルを突っ込みます。

build.gradle.kts
plugins {
    `kotlin-dsl`
}

定数クラスを作るだけの場合は不要だと思いますが、buildSrc内でkotlinの拡張関数とかを使いたい場合は、kotlinプラグインやstdlibも組み込みます。

あとは、buildSrc/src/main/java 以下に適当にパッケージをつくり、クラスを作成します。
パッケージ名やクラス名は何でも良いですが、Gradleスクリプトの中で現れる名前は避けましょう。
私は散々悩んだあげく、buildというパッケージの下に、ProjectPropertiesというクラスを作りました。

ProjectProperties.kt
package build

object ProjectProperties {
    const val groupId: String = "com.example"

    private const val versionMajor: Int = 1
    private const val versionMinor: Int = 0
    private const val versionPatch: Int = 0
    const val versionName: String = "$versionMajor.$versionMinor.$versionPatch"
    const val versionCode: Int = versionMajor * 10000 + versionMinor * 100 + versionPatch

    object Url {
        const val site: String = "https://github.com/example/example"
        const val github: String = "https://github.com/example/example"
        const val scm: String = "scm:git:https://github.com/example/example.git"
    }
}

利用するときはimportの記述が必要ですが、自由に使えるようになります。
Null安全で型安全に使えます。

build.gradle.kts
group = ProjectProperties.groupId
version = ProjectProperties.versionName

以上です。