Gradle Kotlin DSL入門

# Introduction
これはQiita Kotlin Advent Calendar 2019 7日目の記事です。6日目は @yt8492 さんの[Kotlin向けgRPC/protobufライブラリKroto+を使ってみよう](https://qiita.com/yt8492/items/345f4de495feb18201ff)、8日目は@nnao45 さんのkotlin製ORM Exposed小技集です。

どうも、[株式会社 justInCase](https://justincase.jp/)の関連会社である[justInCase Technologies](http://justincase-tech.com/)でバイトとして[AWS CDKのラッパーライブラリを自動生成する奴](https://github.com/justincase-jp/AWS-CDK-Kotlin-DSL/)を作ってるトリナーと申します。

Gradle Kotlin DSLはGradle 5.0で正式版になった、GradleのビルドスクリプトをKotlinで書ける機能です。
従来のビルドスクリプトである`build.gradle`と`settings.gradle`はGroovyというJVM上で動作するスクリプト言語を用いていました。
主観的な話で申し訳ないですが、Gradleのビルドスクリプト用途以外でGroovyを用いているという話を聞いたことが無いため、「Gradleのビルドスクリプト専用言語」の様な形で使っている人が大半でしょう。そして、Groovyを勉強してGradleを使っている人はかなり少なかったのではないでしょうか？私のように「よくわからんけどコピペで動く」といった感じで使っていた人が多かったのではないでしょうか。
しかし、その様な時代はGradle Kotlin DSLの到来で終わりました。Kotlinという素晴らしい言語でGradleのビルドスクリプトが記述できるようになったのです。ビルドスクリプトとビルド対象のコードを両方Kotlinで書ける事により、ビルドスクリプトの読解と編集の障壁は圧倒的に下がりました。buildSrcとの併用で、より複雑なビルドスクリプトをより容易に記述する事ができるようになりました。
しかしながら、現在、Gradle Kotlin DSLについての日本語での入門記事はあまりありません。また、GradleのAPIとGradle Kotlin DSL特有のAPIが混在するため、学習に難易度がやや高いというのも事実です。そのため、入門記事を書いておこうと思います。

# Requirement
バージョンは執筆時の物。

- IntelliJ IDEA 2019.3
- Gradle 6.0.1(IntelliJで自動生成されるものは5.2.1)
- Kotlin 1.3.61
- JDK 8以降
- Gradleにある程度触れたことのある人

# Setup
IntelliJ IDEAでFile > New > Projectを選択し、New ProjectのウインドウでGradleタブを選択します。ここで、`Kotlin DSL build scrip`のチェックボックスにチェックを入れるとプロジェクトの作成時に`build.gradle.kts`が生成されるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222593/b1cdcb49-b1a7-52a1-f520-4f0993081128.png)

プロジェクトの作成が終わったら、IntelliJがIndexingとGradleとのSyncを完了するのを待ちます。終わったら、おもむろにTerminalを開いて `gradlew wrapper --gradle-version=6.0.1`を実行して完了するのを待ちましょう。Gradleのバージョンが6.0.1になります。

# Basic of Gradle Kotlin DSL
## 自動生成されたスクリプトを読む
現バージョンでIntelliJにより自動生成されるKotlinのバージョンは以下のような感じになります。

```kotlin:build.gradle.kts
plugins {
    kotlin("jvm") version "1.3.61"
}

group = "org.example"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib-jdk8"))
}

tasks {
    compileKotlin {
        kotlinOptions.jvmTarget = "1.8"
    }
    compileTestKotlin {
        kotlinOptions.jvmTarget = "1.8"
    }
}
```

比較に、Groovy版のbuild.gradleを掲載します。

```groovy:build.gradle
plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.61'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
```

上から順に見ていきましょう。
###  `plugin{}`
Gradle Kotlin DSLでは`kotlin("jvm")`という、Groovy版には無い専用関数でKotlinのPluginを指定しています。この`"jvm"`の部分は変更可能で、ターゲットのプラットフォームに応じて変更すれば動きます。
では、他のPluginはどの様に指定するのでしょうか？これは2通りあります。
1つは、`application`や`maven-publish`等のGradle公式のPlugin用の方法です。これらのPluginは、個別の特殊なプロパティが指定されているため、`application`や ``` `maven-publish` ```とだけ書けばこれらのPluginが適用されます。なお、Kotlinにおいて`-`はプロパティ名として使用できないため、`-`が入る場合は`` ` ``で囲う必要があります。中身としては、拡張プロパティのgetterでPluginの適用処理を書いてるっぽいです。~~頭おかしい(褒め言葉)~~
2つ目は、サードパーティのPluginを適用する方法で上記の2つの方法が内部的に呼び出している方法です。`id("plugin id")`関数を用います。これは見たまんまで、Pluginのidを指定してPluginを適用します。
Pluginのバージョンを指定するには、`version`infix関数を用います。この方法はKotlin PluginとサードパーティのPluginで共通です。Gradle公式のPluginでも使えますが、不要です。ここらへんはGroovy版と共通ですね。

ここまでの内容をまとめたサンプルコードがこちら。

```kotlin
plugins {
    kotlin("jvm") version "1.3.61"
    application
    `maven-publish`
    id("com.jfrog.bintray") version "1.8.4"
}
```

### `repository{}`
Maven CentralやJCenterはGroovy版と同様に関数を呼び出せば良いのですが、それ以外のMavenリポジトリを指定する方法が若干異なります。具体的には以下の様な感じです。

```kotlin
repositories {
    mavenCentral()
    maven(url = "https://dl.bintray.com/justincase/aws-cdk-kotlin-dsl")
}
```
`maven()`の引数にurlを指定する形ですね。

### `dependencies{}`
Pluginと同様にKotlinの標準ライブラリが優遇されているだけで、それ以外はGroovyのコードがKotlinの関数呼び出しになっただけです。以下のようにも書けます。

```kotlin
dependencies {
    // この2つは実質同じ
    implementation(kotlin("stdlib-jdk8"))
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61")
}
```

### `tasks{}`
ここでは、タスクの設定を行います。このブロック内ではそのモジュール内に存在する全てのタスクに対応する名前の拡張プロパティが存在しており、ラムダ式を引数に取る関数として呼び出すことでタスクの設定を行えます。ちなみにこのスコープ内では、`tasks.register()`の様な、通常`tasks`に対して行う操作も`tasks`を省略して行うことが可能です。
ちなみに、IntelliJに生成されるビルドスクリプトがこの関数を使うようになったのは私が知る限りKotlin 1.3.60になって以降です。私もこの記事を書くために生成するまでこの関数の存在を知らなかったのでどのバージョンのGradle及びKotlinから利用可能かは不明です。タスクに関する操作をまとめれるので何気に神機能ですね。
ちなみに、以下の様に書いた場合、上の`jar{}`は`jar`という名前のタスクにのみ適用され、下の`withType<org.gradle.jvm.tasks.Jar>{}`は`Jar`クラスを用いているタスク全てに適用されます。

```kotlin
tasks {
    jar {
        include("foo.class")
    }

    withType<org.gradle.jvm.tasks.Jar> {
        include("bar.class")
    }
}
```

### EOF
以上で自動生成される`build.gradle.kts`についての解説は終わりです。自動生成部分だけでGradleのAPIとしてGoovy版と異なる部分はほぼ網羅されていて、残るはKotlinとGroovyの言語機能の違いなので、そこら辺はKotlinに慣れていたらある程度雰囲気でできそうな感じですね。
次は、自動生成されたコードには無いGradle Kotlin DSL特有のAPIについて紹介していこうと思います。

## 自動生成されたコードに無い諸々
### 関数名とタスク名と一致しない設定
GradleのPluginを各種導入していると、直接タスクの設定を弄っているわけでは無いがタスクに関連のある設定、というのがよく出てきます。これは、Gradleの`Extension`という、Pluginに対しての設定を行う機能です。私も詳しくは理解していないので詳細は自分で調べてください。この`Extension`の例としては、`application`Pluginの`JavaApplication`や、`maven-publish`Pluginの`PublishingExtension`等があります。Groovy版だと`application{}`や`publishing{}`で呼び出している奴ですね。
こいつらの設定はGroovy版と同じです。タスクの設定は上記の通り`tasks{}`スコープ内でやる感じですが、`Extension`の設定はGroovy版と同様にトップレベルに書く形ですね。
これはKotlin DSL特有のAPIでは無いですが、上記の`tasks{}`の補足として書きました。

### Propertyの利用
みなさん、Gradleのプロパティを使っていますか？
私は依存ライブラリのバージョンの管理等に使っていますが。今の時代だと`buildSrc`使えって言われるんですかね？正直、`buildSrc`にバージョンを記述する事にあまりメリットを見いだせないのですが(特に比較的小規模なProject)。むしろGradle 6.0の時代なら`platform`を使えって感じですかね？それはともかく。
Groovy版ならプロパティは通常の変数みたいな感じでアクセスできてしまいますが、Kotlinではその方法は使えます。ここでは、Propertyの使い方について2通り紹介します。
#### ext
`ext["propertyName"] as String`
これでプロパティを取得できます。これはGroovy版と同じ仕様ですね。`"propertyName"`の部分を任意のプロパティの名前に変えて、好きな型にキャストしましょう。
#### Property Delegation
`val foo: String by project`
Kotlin DSLだとこんな風に書けます。
projectからのデリゲートでプロパティが取得できます。変数名はプロパティ名で、型はプロパティの型です。
こっちの方が簡潔でわかりやすいですね。

### Taskの登録
タスクの登録自体は特筆すべき点は`tasks{}`のところで述べた以外にはありません。しかし、タスクを登録した後の取り回しに違いがあります。
`tasks.register()`は`TaskProvider<T>`を返します。この`TaskProvider<T>`は名前の通りタスクを提供するクラスです。`invoke()`が定義されているので`fooTaskProvider {}`の様にする事でタスクの設定ができます。また、`fooTaskProvider.get()`とする事で`Task`を取得する事ができます。しかし、生の`Task`だけが欲しい場合はこの仕様は少し煩雑です。そこで現れるのがKotlinのProperty delegation。
`val barTask by register("bar")`とすることで、`barTask`には生の`Task`が代入されます。ちなみにこのProperty Delegation自体は`TaskProvider`に対して定義されているので、あらゆる`TaskProvider`に適用が可能です。
まとめるとこうです。

```kotlin
// TaskProvider<T>
val fooTask = register("foo")
// Task
val barTask by register("bar")
```
こういう細かいところにまで気を利かせて便利な構文を用意してくれるあたり、Gradle Kotlin DSLは素晴らしいですね。

# Conclusion 
以上、Gradle Kotlin DSL入門でした。
Gradle Kotlin DSLは(補完が遅い事を除けば)本当に素晴らしい物です。Gradleのビルドスクリプトを構築する上では生産性を爆上げしてくれます。これがあればもうGradleは怖くない！
この記事がGradle Kotlin DSLの普及に役立てば幸いです。

これは私見なんですが、Gradleの入門で調べて出てくる記事って、最初に「タスクの作成」を行う物が多いですよね。しかし、大抵のユースケースの場合、タスクの作成を行う事は少なく「Pluginを設定して、依存ライブラリを指定して、ビルドの設定を行う」事ができれば十分な事が多いのではないでしょうか。この記事は「既存のGradleユーザーをKotlin DSLに誘う」事を目的としているので、タスク周りはGradleのAPIって事でサラッと飛ばしましたが。

如何せん、こんな入門記事を書くのは初めてなので、「こんな内容も追加して！」って事や「ここの説明がわかりにくい！」という事があればコメントかTwitterで言ってください。