Gradle で Single-Project から Multi-Project への変換
適当に single project で始めた後に事業拡張等で複数のアプリケーションが必要になったり共通化したりしたくなった場合にどのような作業を行うといいかというの検討します。
バージョン情報
- Gradle 6.5
Gradle で Single-Project から Multi-Project への変換
Gradle での Multi Project とは?
gradle がそのプロジェクトが single か multi かを判別するのは ビルドライフサイクルでの3つあるフェーズのうちの initialize phase のときです1。
そしてこれは settings.gradle(.kts) ファイルを見つけ出し、 include で自身以外のプロジェクトが追加されているかどうかで決まります。 Multi Project に含まれるプロジェクトは Root Project や Sub Project と呼ばれます2。
Single Project というのはつまり Multi Project における root project をそのまま利用しているという状況なので、これを include で指定した場所にそのまま移動させて sub project として追加できると良さそうです。
Single-Project の開発
例として以下のようなプロジェクトを作成します。build script を Kotlin DSL で記述してありますがここで行う作業では Groovy DSL でも大きくは変わりません。
❯ gradle init --type basic --dsl kotlin --project-name single-to-multi-project > Task :init Get more help with your project: https://guides.gradle.org/creating-new-gradle-builds BUILD SUCCESSFUL in 517ms 2 actionable tasks: 2 executed
以下のコマンドでプロジェクトが root project のみであることが確認できます。
❯ ./gradlew projects > Task :projects ------------------------------------------------------------ Root project ------------------------------------------------------------ Root project 'single-to-multi-project' No sub-projects To see a list of the tasks of a project, run gradlew <project-path>:tasks For example, try running gradlew :tasks BUILD SUCCESSFUL in 458ms 1 actionable task: 1 executed
ある程度開発したとして build.gradle.kts を以下のようなものになったとします。
plugins { java id("org.springframework.boot") id("io.spring.dependency-management") } repositories { mavenCentral() } dependencies { implementation("org.springframework.boot:spring-boot-starter-web") }
settings.gradle.kts
pluginManagement { repositories { gradlePluginPortal() } plugins { id("org.springframework.boot") version "2.3.0.RELEASE" id("io.spring.dependency-management") version "1.0.9.RELEASE" } } rootProject.name = "single-to-multi-project"
Single-Project から Multi-Project へ
以下のようにして sub project を作ってみます。 このとき、実体となる build script はこの時点では必要ありません。
❯ echo 'include("single-to-multi-project")' >> settings.gradle.kts
❯ ./gradlew projects > Task :projects ------------------------------------------------------------ Root project ------------------------------------------------------------ Root project 'single-to-multi-project' \--- Project ':single-to-multi-project' To see a list of the tasks of a project, run gradlew <project-path>:tasks For example, try running gradlew :single-to-multi-project:tasks BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed
ちなみにこの時点での構成は下記であるとします。
❯ tree . ├── build.gradle.kts ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src └── main └── java └── com └── example └── ExampleApplication.java
このとき、gradle build を実行すると build/libs 以下に single-to-multi-project.jar という jar が生成されます。
❯ ./gradlew build BUILD SUCCESSFUL in 769ms 2 actionable tasks: 2 executed ❯ tree build build ├── classes │ └── java │ └── main │ └── com │ └── example │ └── ExampleApplication.class ├── generated │ └── sources │ ├── annotationProcessor │ │ └── java │ │ └── main │ └── headers │ └── java │ └── main ├── libs │ └── single-to-multi-project.jar └── tmp ├── bootJar │ └── MANIFEST.MF └── compileJava └── source-classes-mapping.txt
さてここで本題となる、sub project に中身も移動して multi project を完成させましょう。
❯ mkdir single-to-multi-project ❯ mv build.gradle.kts single-to-multi-project
❯ tree . ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── single-to-multi-project ├── build.gradle.kts └── src └── main └── java └── com └── example └── ExampleApplication.java 8 directories, 7 files
以下のコマンドでビルドしてみます。
いずれの場合であっても subproject 側の build ディレクトリに jar ファイルが生成されていると思います。
❯ ./gradlew build or ❯ ./gradlew :single-to-multi-project:build ❯ tree single-to-multi-project/build single-to-multi-project/build ├── classes │ └── java │ └── main │ └── com │ └── example │ └── ExampleApplication.class ├── generated │ └── sources │ ├── annotationProcessor │ │ └── java │ │ └── main │ └── headers │ └── java │ └── main ├── libs │ └── single-to-multi-project.jar └── tmp ├── bootJar │ └── MANIFEST.MF └── compileJava └── source-classes-mapping.txt
ここまでで一旦 multi project に変換するまでを行いました。multi project の場合の共通処理の書き方やビルドパフォーマンスを上げる方法などいくつか話題はありますが次回以降の話題とします。
Single-Project から Multi-Project へ(別解)
ある project と他の project を関連させながら開発を行う方法として gradle の Composite Build3 という仕組みがあります。 これは厳密には Multi Project ではないため、Gradleドキュメント上で Multi Project と記載されているものと混同しないように注意してください。 この方法のメリットとしては、元のプロジェクトに全く手を加えることなく他のプロジェクトを作成し、依存として利用することができます。
さて、「Single-Project の開発」からの続きであるとして、その次のステップとして root project に相当する project を作成します。これは元のプロジェクトとは別のプロジェクトとして作成します。 注意点としては、このプロジェクトは 現在の gradle バージョンにおいては composite build の制限により同名のプロジェクト名を利用できません
❯ gradle init --type basic --dsl kotlin --project-name multi-project > Task :init Get more help with your project: https://guides.gradle.org/creating-new-gradle-builds BUILD SUCCESSFUL in 492ms 2 actionable tasks: 2 executed
他のプロジェクトと連携したい project を multi-project に追加します。
❯ echo 'includeBuild("single-to-multi-project")' >> settings.gradle.kts ❯ mv path/to/single-to-multi-project .
build.gradle.kts に以下のような記述を追加します(現在の gradle の制限により(まだ)直接コマンドラインからは起動できないため4 )。
tasks.register("build") { dependsOn(gradle.includedBuild("single-to-multi-project").task(":build")) }
❯ ./gradlew build BUILD SUCCESSFUL in 1s
対象プロジェクトの jar ができていることが確認できます。
❯ tree single-to-multi-project/build/ single-to-multi-project/build/ ├── classes │ └── java │ └── main │ └── com │ └── example │ └── ExampleApplication.class ├── generated │ └── sources │ ├── annotationProcessor │ │ └── java │ │ └── main │ └── headers │ └── java │ └── main ├── libs │ └── single-to-multi-project.jar └── tmp ├── bootJar │ └── MANIFEST.MF └── compileJava └── source-classes-mapping.txt
ここまでで一旦 multi project に変換、 というより他のプロジェクトからビルドするまでを行いました。
このまま他のプロジェクトを追加する際の注意点などはいろいろありますが、構成や制限などがいろいろありややこしいため別途 composite build として話題とするか ドキュメント5 を参照してください。
以上です。