Gradleの1プロジェクトを後から複数プロジェクトに分ける例

# 概要

このエントリでは、[Gradle](https://gradle.org/)でシングルプロジェクトとして作成したものを後付けで複数プロジェクトに分ける例を扱います。

## 想定読者

- Gradleに関して、ビルドなどの基本的な操作の知識はある方
- Gradleで、プロジェクト構成をどう分けて運用しようかの検討を始めた方

# 前提

## Gradleのマルチプロジェクト

Gradleには、マルチプロジェクトとして、複数のGradleプロジェクトを管理する機能があり、サイトのドキュメント[「Authoring Multi-Project Builds」に](https://docs.gradle.org/current/userguide/multi_project_builds.html)説明されています。

上記サイトでは、以下の3つの名前のプロジェクトに依存関係を持たせてビルドする方法が説明されています。
- shared
- api
- services

この構成にするには、プロジェクトごとにディレクトリを分け、ディレクトリの配置を決め（フラットに複数並べるか、階層構造にするか）、それに合わせてGradleの設定ファイル(build.gradle, settings.gradle)を作成することになります。ディレクトリについては、サイトの「 [Build Lifecycleのこのあたり](https://docs.gradle.org/current/userguide/build_lifecycle.html#sub:project_locations) 」に説明されています。

階層構造にする場合（特に指定しない時）

> + A(root)
>    + B
>    + C

フラットな場合（指定すれば可能）

> + A
> + B
> + C

## プロジェクト間の連携（リポジトリの単位）の議論

前述のようにプロジェクトを分けた時、Gitリポジトリの運用も同時に検討することがあると思います。

こちらは使えるツールや環境、既存のリポジトリとの関係で取りうる手段のうち、関係者が使いやすいような方法を作ることになると思います。特にそのような制約を持たないならば、例えば以下のような手段が考えられます。

|No|方法|パッケージリポジトリ|便利なところ|不便なところ|
|:---|:---|:---|:---|:---|:---|
|1|リポジトリを一つにして複数プロジェクトを格納する|使わない|複数プロジェクトをいじっても扱いがシンプル|複数の関係者がいる場合に自身に関係がないソースの変更を目にしたり気にする必要が出てくる
|2|リポジトリを複数にしてそれぞれにプロジェクトを格納する|使わない|上記No.1の「不便なところ」を解消できる|No.1に比べると、それぞれのリポジトリからチェックアウト、コミット等の操作をする手間が増える|
|3|No.2のプロジェクトの一部をパッケージとして登録して参照する|使う|build.gradle内にパッケージの依存性を書くことで、前述の"shared"相当の共通機能などを「使う」側はラクができる|No.2に比べると、"shared"相当の共通機能などを「提供する側」は、パッケージリポジトリを操作する手間が増える|

### 前述表内のNo.1のイメージ

sample-appという名称でGitリポジトリを作ったとすると、sample-app.git内に下記プロジェクトが存在するイメージです。

> shared
> api

### 前述表内のNo.2のイメージ

No.1に比べて、Gitリポジトリが二つになります。開発者が適宜それぞれのリポジトリから最新バージョンをチェックアウトするなりして、gradleでプロジェクト参照をかけたり、IDE側でもプロジェクト参照を設定するなどを実施します。

sample-app-shared.git内に下記が存在
> shared

sample-app-api.git内に下記が存在、apiプロジェクトからsharedプロジェクトを参照する
> api


### 前述表内のNo.3のイメージ

sample-app-shared.git内に下記が存在し、これをビルドしたパッケージが、Mavenリポジトリに登録されている
> shared

sample-app-api.git内に下記が存在、apiプロジェクトでは、Mavenリポジトリ内に格納されたsharedパッケージに依存関係をgradleで指定する
> api


# 例～コンソール版電卓プログラムを分割してみる

## 例にする対象

手元に、[小さなGradleのシングルプロジェクト](https://github.com/hrkt/commandline-calculator)があるので、これをマルチプロジェクトに分けてみます。

このプロジェクトは、コマンドラインで動作する電卓のプログラムのためのものであり、Javaで書かれ、SpringBootを使っています。これを2つに分けてみようと思います。

本エントリでは、分割の方法は、前述のNo.1の方法を使います。（No.2, No.3の方法も、別エントリで試したいと思います）

ソースはこれだけしかありません。

![before-split.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65002/40b581c4-c265-7664-815e-dd5f468966d8.png)

## ステップ

### ディレクトリを2つ作ります

> mkdir shared cli

### 作ったディレクトリの中に、関連するソースを移動します。

ディレクトリだけ作って、

```terminal

find src -type d | xargs -I src mkdir shared/src
find src -type d | xargs -I src mkdir cli/src
```

ソースをそれぞれ移動

```terminal

mv src/main/java/com/hrkt/commandlinecalculator/DeskCalculator.java shared/src/main/java/com/hrkt/commandlinecalculator/
mv src/test/java/com/hrkt/commandlinecalculator/DeskCalculatorTest.java shared/src/test/java/com/hrkt/commandlinecalculator/

mv src/main/java/com/hrkt/commandlinecalculator/* cli/src/main/java/com/hrkt/commandlinecalculator/
mv src/test/java/com/hrkt/commandlinecalculator/* cli/src/test/java/com/hrkt/commandlinecalculator/
mv src/main/resources/* cli/src/main/resources/

rm -rf src

```

### build.gradleを分割します

以下を方針としました。

- サブプロジェクトが自身だけで使用するものは、サブプロジェクト側に記述する
- 最小限の依存関係の記述にとどめる（使わないものは書かない）

### master/build.gradle

- 配下でJavaとlombokは使っているので、プラグインを適用しています。
- assertJも複数プロジェクトで使うためこちらで依存関係を指定しています。

```gradle
plugins {
    id 'java'
    id 'idea'
    id 'eclipse'
    id "io.freefair.lombok" version "4.1.6"
}

subprojects {
    repositories {
        mavenCentral()
    }

    apply plugin: 'java'
    apply plugin: 'io.freefair.lombok'

    group = 'com.hrkt'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '1.8'

    dependencies {
        testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.14.0'
    }
}

```

### calculator-shared/build.gradle

- このプロジェクトはSpringフレームワークには依存しないため、適用プラグインは最小限にとどめています。
- spring-boot-starter-testを使うと自分でJUnit5関係の指定はしませんが、依存関係から削ったため、自身で必要なものを指定しています。


```gradle
defaultTasks 'clean', 'jar'

dependencies {
    implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30'

    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.2'
    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.2'
    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.5.2'
}

```

### calculator-cli/build.gradle

- このプロジェクトはSpringBootを使用しているため、必要なプラグインを指定しています。
- 依存関係にもSpringBoot関係が入っています。
- このプロジェクトは、「calculator-shared」プロジェクト側のクラスを参照するため、projectに対する依存関係を指定しています。

```gradle
plugins {
    id 'org.springframework.boot' version '2.2.1.RELEASE'
    id 'io.spring.dependency-management' version '1.0.8.RELEASE'
}

apply plugin: "org.springframework.boot"
apply plugin: 'io.spring.dependency-management'

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation "org.springframework.boot:spring-boot-starter-web"

    implementation group: 'org.jline', name: 'jline', version: '3.13.2'
    implementation group: 'org.jline', name: 'jline-terminal-jna', version: '3.13.2'
    implementation group: 'net.java.dev.jna', name: 'jna', version: '5.5.0'

    implementation project(":calculator-shared")

    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}


test {
    useJUnitPlatform()
}

```

# おわりに

このエントリでは、Gradleの1プロジェクトを後から複数プロジェクトに分ける例を紹介しました。

使用したソースの、主に変更差分に関する部分について、
 [GitHubのPullRequest](https://github.com/hrkt/commandline-calculator/pull/1) として残しておきます。

# 補足

マルチプロジェクトに限らずですが、gradleの実行に関して挙動をより詳細に知りたいときには、オプション「--info」「--debug」などを指定して、ログの量を増やしてあげるとわかりやすいです。

```console
gradlew build --info
```