Gradle のマルチプロジェクト機能を試す

June 30, 2020 - 読了時間 2 分
gradle java build

モダンな Java のプロジェクトでは Gradle というビルドツールがよく使われているようにみえます。 maven のように xml を記述しないだけでも好みかなと思って私も2-3年前ぐらいから新規に開発するプロジェクトでは Gradle を採用しています。

あるプロジェクトで Gradle の multi-project を採用する機会があったので調べてみました。サンプルコードは次になります。

multi-project とは

1つのリポジトリ内に複数のプロジェクト (ビルド設定) をもつようなプロジェクト構成を指します。 通常の1つのリポジトリに1つのプロジェクトではなく、multi-project を採用する背景としては次のようなものでしょうか。

他にもあるでしょうけど、私がパッと思いつくものをあげました。

好みで言えば、私はあまり multi-project な構成が好きではありません。 小さいプロジェクトであれば、1つのプロジェクト内でうまく設計すればよいし、 プラグインのようなものを明確に分けたいならリポジトリごと分けたほうがわかりやすくてよい気がします。 プロジェクトの数があまり増えていかないのがわかっているのであれば、こういった構成も機能するとは思います。 なんとなく1つのリポジトリが肥大化する機構そのものが好ましくないと思ってしまうため、 私は Multi-Project な構成を好ましく思わないのかもしれません。

root project と sub-projects

リポジトリのトップディレクトリの階層のことを root project と言います。 settings.gradle に root project と sub-projects の設定をします。 次の設定では include というキーワードに続くものが sub-project を指します。

settings.gradle

rootProject.name = 'gradle-multi-project-sample'

include 'common'
include 'base'
include 'app'

root project の build.gradle はいくつか異なる設定をすることになります。

build.gradle

plugins {
    id 'com.github.sherter.google-java-format' version '0.9' apply false
}

allprojects {
    version = '1.0'
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'com.github.sherter.google-java-format'

    dependencies {
        // for type inference
        annotationProcessor 'org.projectlombok:lombok:1.18.12'
        compileOnly 'org.projectlombok:lombok:1.18.12'
        testAnnotationProcessor 'org.projectlombok:lombok:1.18.12'
        testCompileOnly 'org.projectlombok:lombok:1.18.12'

        // for logging
        implementation 'ch.qos.logback:logback-classic:1.2.3'
        implementation 'org.slf4j:slf4j-api:1.7.26'

        implementation 'com.google.guava:guava:29.0-jre'
        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2'
    }

    repositories {
        jcenter()
    }

    test {
        useJUnitPlatform()
    }

    compileJava.dependsOn tasks.googleJavaFormat
}

allprojects というブロックは root project とすべての sub-projects に適用される設定を記述します。 そして subprojects というブロックは sub-projects のみに適用される設定を記述するところです。 この例では適用するプラグインや依存パッケージ、テストライブラリなどの共通設定を記述しています。

ここで google-java-format のプラグイン設定は一工夫が必要になります。

plugins {
    id 'com.github.sherter.google-java-format' version '0.9' apply false
}

subprojects {
    apply plugin: 'com.github.sherter.google-java-format'

    compileJava.dependsOn tasks.googleJavaFormat
}

plugins ブロックでプラグイン設定を記述する方法は新しい構文になり、 現時点では subprojects ブロックではその構文に対応していないようです。 そのため、plugins ブロックでプラグインのバージョン指定を行い、 apply false を指定することでこのプラグインを root project には適用しないことを宣言しています。 このようにして subprojects ブロックでは apply plugin: という古い構文で設定を適用できます。

サブプロジェクトの設定

サブプロジェクトの build.gradle の設定は普通の Gradle プロジェクトのように設定できます。 plugins にある id 'java' は root project の build.gradle にも記述されているのでこちらに記述しなくても問題はないようです。 ただサブプロジェクトの設定だけをみたときに適用されているプラグインを すべて記述した方がわかりやすいという考え方もあるので二重に設定しても問題にはならないようです。

app というサブプロジェクトはアプリケーションを意図しているので メインクラスや実行可能な jar を生成する設定などを追加しています。

app/build.gradle

plugins {
    id 'java'
    id 'application'
}

mainClassName = 'app.Main'

dependencies {
    compile project(':base')
}

task customFatJar(type: Jar) {
    manifest {
        attributes 'Main-Class': 'app.Main'
    }
    baseName = 'executable'
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

dependencies のブロックに一つ見慣れない構文が出てきます。

dependencies {
    compile project(':base')
}

これは app というサブプロジェクトが base という別のサブプロジェクトに依存していることを表しています。

gradle コマンドの操作

gradle コマンドの操作もほとんど違和感がないので簡単です。

プロジェクト名を表示する

$ gradle -q projects

------------------------------------------------------------
Root project
------------------------------------------------------------

Root project 'gradle-multi-project-sample'
+--- Project ':app'
+--- Project ':base'
\--- Project ':common'

コンパイルする

トップディレクトリで gradle コマンドを操作すると基本的にはすべてのサブプロジェクトに適用されます。

$ gradle compileJava

特定のサブプロジェクトを指定して操作したいときは :base: のようにサブプロジェクト名に続けてタスクを指定します。

$ gradle :base:compileJava

もちろん特定のサブプロジェクトのディレクトリに移動したときは、 そこで行う gradle コマンドの操作はそのサブプロジェクトのみに適用されます。

$ cd base/
$ gradle compileJava  # base サブプロジェクトをコンパイル

接頭辞としてサブプロジェクト名を指定する以外、 違和感なく gradle の操作が行えるのでとてもよくできているように思いました。

実際のプロジェクトの実用例

実際のプロジェクトの実用例として embulk で使われているようです。 こういった実用例からどのような設定をしているかを参考にするのもよいと思います。

リファレンス

Presto のビルド環境を構築する Java のアノテーションプロセッサを試す

関連記事

Java で作るカスタム GitHub Actions

June 19, 2022 - 読了時間 1 分
java event github

Java のアノテーションプロセッサを試す

July 12, 2020 - 読了時間 2 分
java code-generate gradle

Presto のビルド環境を構築する

June 17, 2020 - 読了時間 4 分
presto java build