Gradleマルチプロジェクトとは?基本構造とsettings.gradleの役割

マルチプロジェクトの基本構造

Gradleのマルチプロジェクトビルドは、1つのルートプロジェクトと複数のサブプロジェクトで構成されます。ルート直下にsettings.gradle(.kts)を配置し、各モジュールはサブディレクトリとして展開するのが基本的な形です。

典型的な構成は、ルート直下にapp/core/util/などのディレクトリを置き、それぞれにbuild.gradle(.kts)を持たせます。ルートプロジェクトは全体の調整役であり、サブプロジェクトが実際のコンポーネント(モジュール)として機能します。

settings.gradleの役割とinclude()

settings.gradle(.kts)プロジェクト構成の「地図」です。ここでinclude("app", "core", "util")のようにサブプロジェクトを宣言し、rootProject.nameでルートプロジェクト名を指定します。

ネスト構造の場合はinclude("services:api")とコロン区切りで記述し、これは./services/apiディレクトリに対応します。Gradle 9.0以降、includeされたプロジェクトのディレクトリが存在しないとビルドが失敗するため、宣言と物理配置を一致させる必要があります。

プロジェクトパスの仕組み

プロジェクトパスはコロン(:)区切りの階層で表されます。ルートプロジェクトは:、サブプロジェクトは:services:api:appのように表現します。デフォルトではプロジェクトパスとファイルシステム上の相対パスが一致します。

ただしproject(":legacy").projectDir = file("../old-system")のように、物理ディレクトリを論理パスと別にマッピングすることも可能です。これにより、既存のディレクトリ構成を維持したまま論理的なプロジェクト名を付けられます。

マルチプロジェクトはsettings.gradleのinclude()で宣言し、プロジェクトパス(コロン区切り)で参照する。Gradle 9.0以降はincludeされたディレクトリの存在チェックが厳格化されている点に注意。

サブプロジェクトの依存関係管理とプロジェクトパスの正しい使い方

プロジェクト間の依存関係宣言

サブプロジェクト間の依存関係は、各モジュールのbuild.gradle(.kts)implementation(project(":module2"))のように宣言します。これによりmodule2の成果物がmodule1のコンパイル・実行時にクラスパスへ追加されます。

宣言されていないプロジェクトへの依存はProject with path ':xxx' could not be foundエラーになります。依存関係を追加する前に、必ずsettings.gradleinclude()で該当プロジェクトを宣言しておく必要があります。

特定プロジェクトへのタスク実行

ルートから./gradlew buildを実行すると全サブプロジェクトで同名タスクが実行されますが、特定モジュールだけをビルドしたい場合はプロジェクトパスを指定します。

  • ./gradlew :services:webservice:build — 特定サブプロジェクトのみビルド
  • ./gradlew :api:buildNeeded — 依存関係のあるプロジェクトも含めてビルド
  • ./gradlew :api:buildDependents — 依存しているプロジェクトも含めてビルド
  • ./gradlew projects — プロジェクト階層を表示

プロジェクトパス設計の注意点

プロジェクト名にスペースや特殊文字は使用できません。依存関係参照時に失敗するため、ディレクトリ名とプロジェクト名は英数字・ハイフン・アンダースコアで構成するのが安全です。

またinclude(":subs:web:my-web-module")のように中間ディレクトリをincludeで指定すると、Gradleが:subs:subs:webという仮想的な論理プロジェクトを自動生成する場合があります。意図しない中間プロジェクトを避けるには、projectDirで物理パスを直接マッピングする方法が有効です。

プロジェクト間依存はproject(“:パス”)で宣言し、settings.gradleへのinclude宣言が前提。特定モジュールだけをビルドする場合はプロジェクトパスを指定したタスク実行が効率的。

共有ビルドロジックの設計:buildSrcとbuild-logicの使い分け

共有ビルドロジックの2つの方式

マルチプロジェクトで共通のビルド設定を共有する方法は、buildSrcbuild-logic(included build)の2通りが標準です。buildSrcはルート直下の特別なディレクトリで、Gradleが自動的に認識してビルドスクリプトのクラスパスに追加します。

一方build-logicは、settings.gradleincludeBuild("build-logic")と明示的に宣言する複合ビルド(Composite Build)です。公式ドキュメントでは大規模プロジェクトでbuild-logicを推奨する傾向があります。

buildSrcとbuild-logicの比較

観点 buildSrc build-logic(includeBuild)
構成方法 ルート直下に配置し自動認識 includeBuildで明示的に宣言
スコープ 単一プロジェクト内のみ 複数プロジェクト・外部でも再利用可能
ビルドへの影響 変更すると全ビルドスクリプトのクラスパスが変わる 必要なものだけビルド・キャッシュが効く
推奨用途 中規模以下のプロジェクト 大規模プロジェクト、複数リポジトリ横断

Convention Pluginとしてのカプセル化

共有ビルドロジックはConvention Plugin(慣習プラグイン)としてカプセル化するのがGradleの公式推奨です。build-logic内にmyproject.java-conventions.gradle.ktsmyproject.library-conventions.gradle.ktsのようなプラグインを定義します。

各サブプロジェクトはplugins { id("myproject.java-conventions") }と明示的に適用します。これによりプラグイン単位で単一責任を実現し、IDEのサポートやタイプ安全性も向上します。サブプロジェクト間の重複設定を排除できるのが最大の利点です。

buildSrcは小規模・単一プロジェクト向け、build-logicは大規模・再利用向け。どちらの場合も共有ロジックはConvention Pluginとしてカプセル化するのが公式推奨。

バージョンカタログとConvention Pluginで実現する効率的なモジュール管理

バージョンカタログによる依存関係の一元管理

バージョンカタログは、gradle/libs.versions.tomlに依存関係の座標とバージョンを集約する仕組みです。[versions][libraries][bundles][plugins]の4つのセクションで構成されます。

TOMLファイルでgroovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }のように定義し、ビルドスクリプトではimplementation(libs.groovy.core)とタイプセーフに参照できます。バージョンを1箇所で管理できるため、全モジュールで一貫したバージョンを維持できます。

バージョンカタログの活用パターン

バージョンカタログではバンドル(bundles)を使って関連する依存関係をグループ化できます。groovy = ["groovy-core", "groovy-json"]と定義しておけば、implementation(libs.bundles.groovy)と一行で複数のライブラリを追加できます。

IDEのオートコンプリートが有効になるため、依存関係名のタイプミスを防ぎ、利用可能なライブラリを簡単に確認できます。依存関係の追加・更新時はTOMLファイルを編集するだけで全モジュールに反映されます。

Convention Pluginとの組み合わせ

バージョンカタログとConvention Pluginを組み合わせると、依存関係管理と共有ビルド設定を分離できます。バージョンカタログは「どのライブラリをどのバージョンで使うか」を定義し、Convention Pluginは「Javaコンパイル設定やテスト設定などの共通ルール」を定義します。

例えばjava-conventionsプラグイン内でlibs.bundles.testingを参照し、全サブプロジェクトに共通のテスト依存関係を適用できます。これにより、サブプロジェクトのビルドスクリプトは最小限の記述で済み、メンテナンス性が大幅に向上します。

バージョンカタログは依存関係の「辞書」、Convention Pluginは共通設定の「ルール」。両者を組み合わせることで、全モジュールの一貫性と保守性が向上する。

Gradleマルチプロジェクト開発で失敗しないための注意ポイント

allprojects/subprojectsによる結合を避ける

allprojects{}subprojects{}による設定注入は、プロジェクト同士を結合(coupling)させ、並列実行やConfiguration-on-Demandの効果を損なう可能性があります。allprojectsはルートを含む全プロジェクトに、subprojectsはサブプロジェクトのみに適用される点も混同しやすいポイントです。

大規模プロジェクトでは、これらのブロックに設定をベタ書きするのではなく、Convention Pluginとしてカプセル化し、各サブプロジェクトで明示的に適用する方法が推奨されます。サブプロジェクトのビルドスクリプトが他のサブプロジェクトの設定を参照・変更しないよう設計することが重要です。

Gradle 9.0以降のディレクトリ存在チェック

Gradle 9.0から、include()されたプロジェクトのディレクトリが存在しない、または読み取り専用の場合、ビルドが失敗するようになりました。以前のバージョンでは欠落ディレクトリが黙って許容されていましたが、現在は厳格にチェックされます。

設定時にディレクトリを動的に作成する必要がある場合は、projectDir.mkdirs()などを使用して明示的に作成する必要があります。新規モジュールを追加する際は、settings.gradleのinclude宣言とディレクトリ作成をセットで行う習慣を付けましょう。

並列実行とConfiguration-on-Demandの活用

並列実行--parallelオプションまたはgradle.propertiesorg.gradle.parallel=trueを設定して有効化します。プロジェクトレベルの並列実行によりビルド時間を短縮できますが、プロジェクト間の結合が強いと効果が制限されます。

--configure-on-demandは必要なプロジェクトだけを設定する機能ですが、インキュベーション機能(実験的)であり、結合されたプロジェクトでは期待通り動作しない可能性があります。まずはプロジェクトを分離(decoupled)に保つことが、これらの最適化機能を活かす前提条件です。

allprojects/subprojectsによる設定注入は結合を生み、並列実行やキャッシュの効果を損なう。Gradle 9.0以降はincludeしたディレクトリの存在チェックが厳格化されているため、宣言と配置の一貫性が必須。