🤖

gradleで複数のrootプロジェクトに属するマルチプロジェクトを設定する方法

2022/04/24に公開

TL;DR

大規模プロジェクトで、サブシステムごとに開発チームを編成し開発を進めるような場合に、各サブシステムのビルドと、それらを統合した全体ビルドの両方を実現したい場合があります。これを実現するには、同じプロジェクトのビルドを、異なる root プロジェクトから行えるようにします。なお、この説明では gradle v6.6.1 を利用しています。

  • 各サブシステムの gradle 設定を作る
  • 全体ビルドの gradle 設定では、プロジェクト階層をサブシステムに合わせるように定義する

複数のrootプロジェクトからビルドをするケース

1チームで開発するような、小規模なプロジェクトでは、今回説明するようなニーズは発生しません。 また、複数のサブシステムを複数チームで開発する場合でも、それぞれのサブシステムが十分に疎結合(サブシステムを単独リリースできる)な場合は、このニーズが出ることはないと考えられます。

実際に必要となるのは、上記に該当しない『複数チームで密結合なサブシステムを開発しており、全てのサブシステムを一括リリースを行う』ような場合です。このような場合でも、他のタスクランナーによる統合が適切な場合も多くありますので、いつでも gradle で全体ビルドをお勧めするものではありません。 とはいえ、gradle を利用した全体ビルドでは、以下のようなメリットがあります。

  • CICD 環境を利用せずに統合リリースモジュールを作成できる
  • ビルドに起因するトラブルシューティングを行い易い
  • タスクランナーのタスクをシンプルに記述できる

サンプルの構成

説明のために、今回は次のような 3 チーム構成での証券システム(SecuritiesSystem)の開発を仮定します。

  • 共通フレームワークチーム
    全てのプロジェクトから参照する、業務フレームワークを1プロジェクト(Share)で開発する。
  • 注文管理サブシステムチーム
    投資家からの注文を受け付けしラインハンドラに流すサブシステム(OrderSystem)を、2プロジェクト(OrderApi, OederHandler)で開発する。
  • 約定・資産管理サブシステムチーム
    ラインハンドラからの約定を取り込み、投資家ごとの最新資産情報を管理するサブシステム(ExecutionSystem)を、2プロジェクト(ExecutionHandler, AsssetManagement)で開発する。

これらのプロジェクトについて、開発時はチームごとにビルドを行い、リリース時には全てのプロジェクトを全体ビルドするものとし、その全てを開発環境で gradle タスクとして実行することを実現します。

これを「プロジェクト構成(依存)」と「ディレクトリ配置」で表すと、次のようなイメージです。

プロジェクト構成(依存)

ディレクトリ構成

SecuritiesSystem
┣ OrderSystem
┃ ┣ OrderApi
┃ ┗ OrderHandler
┣ ExecutionSystem
┃ ┣ ExecutionHandler
┃ ┗ AssetManagement
┗ Share

gradleの定義方法

各チームのプロジェクトは、OrderSystem、ExecutionSystem、Share を root プロジェクトとする multi-project 構成の gradle プロジェクトを用意するだけで、特別なことはありません。工夫が必要なのは、SecuritiesSystem プロジェクトの作り方です。

OrderSystem, ExecutionSystem, Shareプロジェクトのgradle設定

この 3 つのプロジェクト設定には特別なものはありませんので、細かな説明を省略し OrderSystem の setting.gradle を例示します。

/*
 * 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/6.6.1/userguide/multi_project_builds.html
 */

rootProject.name = 'OrderSystem'
include 'OrderApi', 'OrderHandler'
includeFlat 'Share'

参考のために説明すると、Share プロジェクトは OrderSystem と同階層のディレクトリにありますので、include ではなく includeFlat として定義する必要があります。このように定義をした結果、OrderSystem からは次のようにプロジェクトが参照されます。

$ ./gradlew -q projects

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

Root project 'OrderSystem'
+--- Project ':OrderApi'
+--- Project ':OrderHandler'
\--- Project ':Share'

SecuritiesSystemプロジェクトのgradle設定

SecuritiesSystem の gradle 設定は、各サブシステムのプロジェクト階層に合わせる必要がありますので、次のようになります。

/*
 * 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/6.6.1/userguide/multi_project_builds.html
 */

rootProject.name = 'SecuritiesSystem'
include 'OrderSystem', 'ExecutionSystem', 'Share'
include 'OrderApi', 'OrderHandler'
include 'ExecutionHandler', 'AssetManagement'

def orderSystemDir = project(':OrderSystem').projectDir
project(':OrderApi').projectDir =  new File(orderSystemDir, 'OrderApi')
project(':OrderHandler').projectDir =  new File(orderSystemDir, 'OrderHandler')

def executionSystemDir = project(':ExecutionSystem').projectDir
project(':ExecutionHandler').projectDir =  new File(executionSystemDir, 'ExecutionHandler')
project(':AssetManagement').projectDir =  new File(executionSystemDir, 'AssetManagement')

この setting.gradle のポイントは、次の通りです。

  • サブシステムを子プロジェクトとして include する(11 行目)
  • 孫プロジェクト(サブシステムの子プロジェクト)も子プロジェクトとして include する(12-13 行目)
  • 孫プロジェクトのプロジェクトディレクトリを指定する(15-21 行目)

これにより、SecuritiesSystem から見たプロジェクト階層と、OrderSystem, ExecutionSystem, Share の各プロジェクトから見た構成が一致するため、SecuritiesSystem から一括でビルドすることが可能になります。試しに、SecuritiesSystem で assemble タスクを実行すると、次のタスクの実行順序になります。

$ ./gradlew -m assemble
:Share:compileJava SKIPPED
:AssetManagement:compileJava SKIPPED
:AssetManagement:processResources SKIPPED
:AssetManagement:classes SKIPPED
:Share:processResources SKIPPED
:Share:classes SKIPPED
:Share:jar SKIPPED
:AssetManagement:bootJarMainClassName SKIPPED
:AssetManagement:bootJar SKIPPED
:AssetManagement:jar SKIPPED
:AssetManagement:assemble SKIPPED
:ExecutionHandler:compileJava SKIPPED
:ExecutionHandler:processResources SKIPPED
:ExecutionHandler:classes SKIPPED
:ExecutionHandler:bootJarMainClassName SKIPPED
:ExecutionHandler:bootJar SKIPPED
:ExecutionHandler:jar SKIPPED
:ExecutionHandler:assemble SKIPPED
:OrderHandler:compileJava SKIPPED
:OrderHandler:processResources SKIPPED
:OrderHandler:classes SKIPPED
:OrderHandler:jar SKIPPED
:OrderApi:compileJava SKIPPED
:OrderApi:processResources SKIPPED
:OrderApi:classes SKIPPED
:OrderApi:bootJarMainClassName SKIPPED
:OrderApi:bootJar SKIPPED
:OrderApi:jar SKIPPED
:OrderApi:assemble SKIPPED
:OrderHandler:bootJarMainClassName SKIPPED
:OrderHandler:bootJar SKIPPED
:OrderHandler:assemble SKIPPED
:Share:assemble SKIPPED

まとめ

現実にはニーズ(または必要性)の少ない、gradle で複数の root プロジェクトを定義して上手く動作させる方法を説明しました。最初にも書きましたが、この対応は便利なことがある程度であり、お勧めするものではありません。 困った時に使える便利な方法として、機会があれば使ってみて下さい。

Discussion