この記事について
Gradleをより理解するために公式ドキュメントを読んでメモ代わりとして私なりに訳していきます。
読んでいるドキュメントのバージョンは7.3.3です。
この記事で訳している章はRunning Gradle Builds>Executing Multi-Project Buildsとなります。
必要な章から読んでいるため、一連の記事は公式ドキュメントの章立て通りの順にはなっていません。
Running Gradle Builds>Executing Multi-Project Builds
大規模なモノリス的アプリケーションでない限りは、単独のビルドファイルとファイルツリーを持つプロジェクトは最小のものに限られます。相互に依存関係を持つ小さく分割されたプロジェクトは理解と習熟が容易になります。相互的依存関係という言葉が重要で、モジュールをひとまとめにリンクしてビルドしてしまう典型的な理由でもあります。
Gradleはこのシナリオをマルチプロジェクトビルドとしてサポートします。
マルチプロジェクトビルドの詳細についてはAuthoring Multi-Project Buildsの節を参照してください。
Identifying project structure
プロジェクトの構造を確認するにはgradle projects
コマンドを使用します。例として、下記のような構造のプロジェクトを扱います。
> gradle -q projects
------------------------------------------------------------
Root project 'multiproject'
------------------------------------------------------------
Root project 'multiproject'
+--- Project ':api'
+--- Project ':services'
| +--- Project ':services:shared'
| \--- Project ':services:webservice'
\--- Project ':shared'
タスクのリストを表示するにはgradle <project-path>:tasks
を実行してください。この例ではgradle :api:tasks
を実行してみてください。
ユーザーの視点では、マルチプロジェクトのビルドは実行可能なタスクの集合であることに変わりはありません。異なっているのはどのプロジェクトのタスクを実行するかを制御できる点です。以下の節でマルチプロジェクトビルドでのタスクの実行に2つの選択肢があることを説明します。
Executing tasks by name
gradle test
コマンドは現在の作業ディレクトリからのサブプロジェクトがもつtest
タスクを実行します。もしプロジェクトのルートディレクトリでtest
を実行すると、api,shared,services:shared,servieces:webserviceのtest
が実行されます。servicesプロジェクトディレクトリでコマンドを実行した場合、services:sharedとservices:webserviceのみが実行されます。
Gradleの挙動の背後にある基本ルールは階層下にある指定された名前のタスクを実行することです。サブプロジェクトを巡回してそのようなタスクが見つからなかった場合のみ文句をつけます。
like
やdependencies
のようなタスクセレクタはすべてのサブプロジェクトではなく起動されたプロジェクトでのみ実行されることがあります。これはこれらのタスクが、すべてのプロジェクトでの実行結果を統合すると処理が難しい情報を出力するからです。
Gradleは現在のディレクトリから階層を下って、与えられた名前のタスクを探して実行します。Gradleは常にマルチプロジェクトビルドの個々のプロジェクトを評価しながら、存在するタスクオブジェクトを作成します。そして、現在のディレクトリとタスク名の引数に従って、実行するべきタスクをフィルタリングします。なぜなら、cross project configurationのため、すべてのプロジェクトはあるタスクが実行される前に表かれていなければなりません。
Gradle Wrapperを使用している場合、サブプロジェクトのディレクトリからGradleを起動して特定のサブプロジェクトのタスクを実行すると動作しません。プロジェクトのルートにいない限りはラッパースクリプトのパスを指定する必要があるからです。例では、webserviceサブプロジェクトに居てwevserviceサブプロジェクトのbuild
タスクを実行したい場合には、../../gradlew build
とコマンドを実行する必要があります。次の節ではこれをプロジェクトルートから実行する方法を説明します。
Executing tasks by fully qualified name
特定のサブプロジェクトの特定のタスクを完全修飾名を使って実行することができます。例ではgradle :services:webservice:build
でwebserviceサブプロジェクトのbuild
タスクを実行できます。完全修飾名は単にプロジェクトパスとタスク名を結合したものです。
プロジェクトパスは下記の形式を持ちます。
- ルートプロジェクトを意味するオプションのコロンから始まる。ルートプロジェクトはプロジェクト内で唯一、パスの中で名前を指定されません。
- コロンで区切られたプロジェクト名の順列。次のプロジェクトは前のプロジェクトのサブプロジェクトです。
この手法はどのタスクでも動作します。特定のサブプロジェクトのタスクを知りたい場合、そのプロジェクトのtask
タスクを実行すればよいのです。例えばgradle :services:webservice:tasks
となります。
どのような手法でタスクを実行する場合でも、Gradleはターゲットとなるサブプロジェクトの依存関係を解決します。相互的な依存関係を憂慮する必要はありません。もしもこれについての構成方法に興味があるなら、この後のユーザーマニュアルで読むことができます。
Multi-Project Building and Testing
Javaプラグインのbuild
タスクは通常、単一のプロジェクトのコンパイル、テスト、(コード品質管理のプラグインを使用していれば)スタイルチェックに使用されます。マルチプロジェクトのビルドではこれらのタスクを様々な範囲のプロジェクトで行いたいと思うことがあります。buildNeeded
タスクとbuildDependents
タスクがこれらを支援します。
この例では:services:person-serviceプロジェクトは:apiプロジェクトと:sharedプロジェクトの双方に依存しています。
:apiプロジェクト単体で作業をしているとします。変更を行いましたが、cleanを行った後、プロジェクト全体のビルドは行っていません。必要なjarのビルドは行いますが、コード品質管理とユニットテストは変更したプロジェクトだけで実行したいと考えています。build
タスクはこのように実行されます。
単体プロジェクトのビルドとテスト
gradle :api:build
の実行結果
> gradle :api:build
> Task :shared:compileJava
> Task :shared:processResources
> Task :shared:classes
> Task :shared:jar
> Task :api:compileJava
> Task :api:processResources
> Task :api:classes
> Task :api:jar
> Task :api:assemble
> Task :api:compileTestJava
> Task :api:processTestResources
> Task :api:testClasses
> Task :api:test
> Task :api:check
> Task :api:build
BUILD SUCCESSFUL in 0s
:apiプロジェクトが依存する他のプロジェクトの変更を含む最新バージョンをバージョン管理システムから取得した場合、あなたのプロジェクトが依存しているプロジェクトのビルドだけではなく、それらのテストも行いたいと思うかもしれません。buildNeeded
タスクはtestRuntime構成に含まれる依存しているプロジェクトのテストも行います。
依存しているプロジェクトのビルドとテスト
gradle :api:buildNeeded
の実行結果
> gradle :api:buildNeeded
> Task :shared:compileJava
> Task :shared:processResources
> Task :shared:classes
> Task :shared:jar
> Task :api:compileJava
> Task :api:processResources
> Task :api:classes
> Task :api:jar
> Task :api:assemble
> Task :api:compileTestJava
> Task :api:processTestResources
> Task :api:testClasses
> Task :api:test
> Task :api:check
> Task :api:build
> Task :shared:assemble
> Task :shared:compileTestJava
> Task :shared:processTestResources
> Task :shared:testClasses
> Task :shared:test
> Task :shared:check
> Task :shared:build
> Task :shared:buildNeeded
> Task :api:buildNeeded
BUILD SUCCESSFUL in 0s
また、他のプロジェクトで使用されている:apiプロジェクトの一部をリファクタリングしたいと思うこともあるでしょう。このような変更を行う場合、:apiプロジェクトのテストを行うだけでは不十分で、:apiプロジェクトに依存しているすべてのプロジェクトをテストする必要があります。buildDependents
タスクは特定のプロジェクトの(testRuntime構成上の)依存関係にあるすべてのプロジェクトのテストも行います。
依存関係のあるプロジェクトのビルドとテスト
gradle :api:buildDependents
の実行結果
> gradle :api:buildDependents
> Task :shared:compileJava
> Task :shared:processResources
> Task :shared:classes
> Task :shared:jar
> Task :api:compileJava
> Task :api:processResources
> Task :api:classes
> Task :api:jar
> Task :api:assemble
> Task :api:compileTestJava
> Task :api:processTestResources
> Task :api:testClasses
> Task :api:test
> Task :api:check
> Task :api:build
> Task :services:person-service:compileJava
> Task :services:person-service:processResources
> Task :services:person-service:classes
> Task :services:person-service:jar
> Task :services:person-service:assemble
> Task :services:person-service:compileTestJava
> Task :services:person-service:processTestResources
> Task :services:person-service:testClasses
> Task :services:person-service:test
> Task :services:person-service:check
> Task :services:person-service:build
> Task :services:person-service:buildDependents
> Task :api:buildDependents
BUILD SUCCESSFUL in 0s
最後に、全てのプロジェクトのすべてのビルドとテストをしたいと考えました。あるタスクをプロジェクトのルートフォルダで実行すると、全ての子どもで同じ名前のタスクが実行されます。そのため、gradle build
を実行するだけですべてのプロジェクトのビルドとテストが行われます。