Kotlin x Gradle のマルチプロジェクトの作り方

【完走賞】MxShun ひとりマラソン 🏃 Advent Calendar 2022　毎週金曜日は Kotlin の記事を書こう！1記事目です。

今回は「Kotlin x Gradle のマルチプロジェクトの作り方」を記事にしようと思います。
と思ったんですが、Gradle v6.7 で既に追加されている機能でした。知らなかった。危うく無知を晒すところでした。

早速ですが、Kotlin x Gradle のマルチプロジェクトの作り方です。

```bash
$ gradle init

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 2

Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6] 3

Split functionality across multiple subprojects?:
   1: no - only one application project
   2: yes - application and library projects
Enter selection [1..2] 2
```

最後の `Split functionality across multiple subprojects?` がマルチプロジェクト化をするオプションです。（デフォルトはシングルプロジェクトです。）

https://docs.gradle.org/6.7/release-notes.html

なので、本記事ではマルチプロジェクトの何が嬉しいのか と マルチプロジェクト化をすることで Gradle プロジェクトにどのような変化が起きたかをもうちょっと書こうと思います。

### マルチプロジェクトの何が嬉しいのか
第一に、1つの Gradle プロジェクト内で複数の Gradle プロジェクトを定義できるので、これまでは複数のリポジトリ管理していた依存するプロジェクトを一つにまとめることができますね。

第二に、レイヤードアーキテクチャに代表されるモジュール間の依存制御が必要なプロジェクトを管理しやすくなりますね。特に Kotlin x Gradle の文脈でいうとタケハタさんのブログが参考になるかと。

https://blog.takehata-engineer.com/entry/realizing-an-onion-architecture-in-kotlin-and-spring-boot-with-gradle-multi-project

### マルチプロジェクト化によって起こった変化
まずはディレクトリ構造。同じ名前で one application と multiple subprojects を作成し tree を比較してみます。

- one application

うん、真っ白なプロジェクトですね。

```bash
multi-projects
|--.gitattributes
|--.gitignore
|--.gradle
|  |--　// 省略
|--app
|  |--build.gradle.kts
|  |--src
|  |  |--main
|  |  |  |--kotlin
|  |  |  |  |--multi
|  |  |  |  |  |--projects
|  |  |  |  |  |  |--App.kt
|  |  |  |--resources
|  |  |--test
|  |  |  |--kotlin
|  |  |  |  |--multi
|  |  |  |  |  |--projects
|  |  |  |  |  |  |--AppTest.kt
|  |  |  |--resources
|--gradle
|  |--wrapper
|  |  |--gradle-wrapper.jar
|  |  |--gradle-wrapper.properties
|--gradlew
|--gradlew.bat
|--settings.gradle.kts
```

- multiple subprojects

`app` に加え `list` と `utilities` というプロジェクトも生成されています。そして、それぞれがパッケージ構造と `build.gradle.kts` を内包しています。

```bash
multi-projects
|--.gitattributes
|--.gitignore
|--.gradle
|  |-- // 省略
|--app
|  |--build.gradle.kts
|  |--src
|  |  |--main
|  |  |  |--kotlin
|  |  |  |  |--multi
|  |  |  |  |  |--projects
|  |  |  |  |  |  |--app
|  |  |  |  |  |  |  |--App.kt
|  |  |  |  |  |  |  |--MessageUtils.kt
|  |  |  |--resources
|  |  |--test
|  |  |  |--kotlin
|  |  |  |  |--multi
|  |  |  |  |  |--projects
|  |  |  |  |  |  |--app
|  |  |  |  |  |  |  |--MessageUtilsTest.kt
|  |  |  |--resources
|--buildSrc
|  |--build.gradle.kts
|  |--src
|  |  |--main
|  |  |  |--kotlin
|  |  |  |  |--multi.projects.kotlin-application-conventions.gradle.kts
|  |  |  |  |--multi.projects.kotlin-common-conventions.gradle.kts
|  |  |  |  |--multi.projects.kotlin-library-conventions.gradle.kts
|--gradle
|  |--wrapper
|  |  |--gradle-wrapper.jar
|  |  |--gradle-wrapper.properties
|--gradlew
|--gradlew.bat
|--list
|  |--build.gradle.kts
|  |--src
|  |  |--main
|  |  |  |--kotlin
|  |  |  |  |--multi
|  |  |  |  |  |--projects
|  |  |  |  |  |  |--list
|  |  |  |  |  |  |  |--LinkedList.kt
|  |  |  |--resources
|  |  |--test
|  |  |  |--kotlin
|  |  |  |  |--multi
|  |  |  |  |  |--projects
|  |  |  |  |  |  |--list
|  |  |  |  |  |  |  |--LinkedListTest.kt
|  |  |  |--resources
|--settings.gradle.kts
|--utilities
|  |--build.gradle.kts
|  |--src
|  |  |--main
|  |  |  |--kotlin
|  |  |  |  |--multi
|  |  |  |  |  |--projects
|  |  |  |  |  |  |--utilities
|  |  |  |  |  |  |  |--JoinUtils.kt
|  |  |  |  |  |  |  |--SplitUtils.kt
|  |  |  |  |  |  |  |--StringUtils.kt
|  |  |  |--resources
|  |  |--test
|  |  |  |--resources
```

続いて、Gradle の設定ファイルの差異を見てみます。まずはプロジェクトルートディレクトリの `settings.gradle.kts` を。

- one application

```
/*
 * This file was generated by the Gradle 'init' task.
 *
 * The settings file is used to specify which projects to include in your build.
 *
 * Detailed information about configuring a multi-project build in Gradle can be found
 * in the user manual at https://docs.gradle.org/7.0/userguide/multi_project_builds.html
 */

rootProject.name = "multi-projects"
include("app")
```

- multiple subprojects

`app` に加え `list` `utilities` プロジェクトも内包することが追加定義されています。

```
/*
 * This file was generated by the Gradle 'init' task.
 *
 * The settings file is used to specify which projects to include in your build.
 *
 * Detailed information about configuring a multi-project build in Gradle can be found
 * in the user manual at https://docs.gradle.org/7.0/userguide/multi_project_builds.html
 */

rootProject.name = "multi-projects"
include("app", "list", "utilities")
```

次に `build.gradle.kts` を。

- one application

```
/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Kotlin application project to get you started.
 * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
 * User Manual available at https://docs.gradle.org/7.0/userguide/building_java_projects.html
 */

plugins {
    // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
    id("org.jetbrains.kotlin.jvm") version "1.4.31"

    // Apply the application plugin to add support for building a CLI application in Java.
    application
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Align versions of all Kotlin components
    implementation(platform("org.jetbrains.kotlin:kotlin-bom"))

    // Use the Kotlin JDK 8 standard library.
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    // This dependency is used by the application.
    implementation("com.google.guava:guava:30.0-jre")

    // Use the Kotlin test library.
    testImplementation("org.jetbrains.kotlin:kotlin-test")

    // Use the Kotlin JUnit integration.
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}

application {
    // Define the main class for the application.
    mainClass.set("multi.projects.AppKt")
}
```

- multiple subprojects

こちらの方がむしろシンプルになっていますね。なぜでしょう。
答えは、各プロジェクトが共通して依存するパッケージやライブラリを `kotlin-common-conventions.gradle.kts` で定義しているためです。

```
/*
 * This file was generated by the Gradle 'init' task.
 */

plugins {
    id("multi.projects.kotlin-application-conventions")
}

dependencies {
    implementation("org.apache.commons:commons-text")
    implementation(project(":utilities"))
}

application {
    // Define the main class for the application.
    mainClass.set("multi.projects.app.AppKt")
}
```

下記が `kotlin-common-conventions.gradle.kts` の中身です。

```
/*
 * This file was generated by the Gradle 'init' task.
 */

plugins {
    // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
    id("org.jetbrains.kotlin.jvm")
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    constraints {
        // Define dependency versions as constraints
        implementation("org.apache.commons:commons-text:1.9")

        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    }

    // Align versions of all Kotlin components
    implementation(platform("org.jetbrains.kotlin:kotlin-bom"))

    // Use the Kotlin JDK 8 standard library.
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    // Align versions of all Kotlin components
    implementation(platform("org.jetbrains.kotlin:kotlin-bom"))

    // Use JUnit Jupiter API for testing.
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.1")

    // Use JUnit Jupiter Engine for testing.
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}

tasks.test {
    // Use junit platform for unit tests.
    useJUnitPlatform()
}
```

いまさらですが、Gradle マルチプロジェクトを作るのが楽だったということで。ときとばに応じて作り分けていきましょう。