GradleでJavaのマルチプロジェクト作成

Gradleでマルチプロジェクトを行う方法です。


## 単一プロジェクトでjarファイル作成

マルチプロジェクトの前に、まず単一のプロジェクトでjarファイルを作成して実行してみます。

### gradleプロジェクト作成

```java:gradle初期化
$ gradle init

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 1

Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1

Project name (default: JavaMultiProject): 

> Task :init
Get more help with your project: https://guides.gradle.org/creating-new-gradle-builds

BUILD SUCCESSFUL in 6s
2 actionable tasks: 2 executed
```

### build.gradleファイルの修正
自動で作成されたbuild.gradleファイルを少し修正します。
重要なのは、jarタスクの作成です。

```groovy:build.gradle
apply plugin: 'java'
apply plugin: 'application'

sourceCompatibility = 11
targetCompatibility = 11

repositories {
    jcenter()
}

dependencies {
}

mainClassName = 'App'
jar {
    manifest {
        attributes 'Main-Class': "App"
    }
}
```

### ソース作成
実行するソースを作成します。

```src/main/java/App.java
public class App {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}
```

### buildと実行をしてみます。

```console:buildと実行
$ gradle build

BUILD SUCCESSFUL in 2s
5 actionable tasks: 1 executed, 4 up-to-date

$ java -jar build/libs/JavaMultiProject.jar 
Hello
```

これで無事単一のプロジェクトでjarファイルの作成と実行ができました。

## マルチプロジェクトの作成

ではマルチプロジェクトの作成をしてみます。

今回のディレクトリ構成です。

```:構成
.(JavaMultiProject)
├── bar  (アプリケーションプロジェクト)
│   ├── src
│   ├── build.gradle
├── foo  (アプリケーションプロジェクト)
│   ├── src
│   ├── build.gradle
├── share  (共通ライブラリとして利用するプロジェクト)
│   ├── src
│   ├── build.gradle
├── build.gradle
├── setting.gradle
```

ディレクトリを作成しておきます。

```console:
$ mkdir -p bar/src/main/java
$ mkdir -p foo/src/main/java
$ mkdir -p share/src/main/java
```

### setting.gralde修正
マルチプロジェクトにする場合はsetting.gradleに設定を行います。

includeにサブプロジェクトを指定します。

```grade:setting.gradle
rootProject.name = 'JavaMultiProject'
include 'foo', 'bar', 'share'
```

これによって`for` `bar` `share` ディレクトリがそれぞれサブプロジェクトとして認識されるようになりました。
IntelliJ IDEAでもこのようにプロジェクトが認識されます。

|![ブラウザ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406169/aae69d04-eeb9-22b2-b615-501450b6923f.png)|
|:-:|

### build.gradleの修正

プロジェクトrootに存在するbuild.gradleでは、subprojectsを指定することでサブプロジェクト全てに適用する設定を指定します。

```build.gradle(root)
allprojects {
}

subprojects {
    apply plugin: 'java'

    sourceCompatibility = 11
    targetCompatibility = 11

    repositories {
        jcenter()
    }
}
```

続いてサブプロジェクトのbuild.gradleになります。
アプリケーションプロジェクトである`bar`から見ていきます。

```bar/build.gradle
apply plugin: 'application'

dependencies {
    implementation project(":share")
}

mainClassName = 'com.example.Bar'
jar {
    manifest {
        attributes 'Main-Class': "com.example.Bar"
    }
    from configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
```

重要な部分を解説しておくと、
dependencies 内の`implementation project(":share")`で、別のサブプロジェクトであるshareに依存していることを定義しています。

また、jarタスクの中で `from 〜`を指定していますが、これは依存ライブラリをBarプロジェクトのjarに含めることを意味しています。

ちなみに、fromメソッドが存在しない場合はjarに依存ライブラリが含まれないので`NoClassDefFoundError`エラーが出力します。私がこれで詰まったので念のため。

```console:
$ java -jar bar/build/libs/bar.jar 
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/Hello
	at com.example.Bar.main(Bar.java:5)
Caused by: java.lang.ClassNotFoundException: com.example.Hello
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
	... 1 more
```

fooプロジェクトはbarプロジェクトとほぼ同じですが念のために記載しておきます。
mainClassNameのみをFooに変更しています。

```foo/build.gradle
apply plugin: 'application'

dependencies {
    implementation project(":share")
}

mainClassName = 'com.example.Foo'
jar {
    manifest {
        attributes 'Main-Class': "com.example.Foo"
    }
    from configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
```

shareのbuild.gradleは今回は特に何も記載していません。

```share/build.gradle
```

### コード

最後にコードを記載します。

```java:bar/src/main/java/com/exmaple/Bar.java
package com.example;

public class Bar {
    public static void main(String[] args) {
        var hello = new Hello("Bar");
        System.out.println(hello.say());
    }
}
```

```java:foo/src/main/java/com/exmaple/Foo.java
package com.example;

public class Foo {
    public static void main(String[] args) {
        var hello = new Hello("Foo");
        System.out.println(hello.say());
    }
}
```

```java:share/src/main/java/com/exmaple/Hello.java
package com.example;

public class Hello {
    private final String name;
    public Hello(String name) {
        this.name = name;
    }

    public String say() {
        return name + " Hello!";
    }
}
```

### buildと実行

buildしてみます。
サブプロジェクトのタスクは`:(プロジェクト名):(タスク名)`で実行します。

また、barが依存しているshareプロジェクトのbuildもこのタイミングで同時に実行してくれます。

```console:
$ gradle :bar:build

BUILD SUCCESSFUL in 804ms
7 actionable tasks: 7 up-to-date

$ ls -l bar/build/libs/
total 8
-rw-r--r--  1 ysaito  staff  1591  4 29 21:15 bar.jar
```

参考に作成されたjarを解凍してみるとshareの中身のHello.classが作成されたbar.jarに含まれていることがわかります。

```console:jarを解凍して確認
$ jar xvf bar.jar 
  META-INF/が作成されました
 META-INF/MANIFEST.MFが展開されました
  com/が作成されました
  com/example/が作成されました
 com/example/Bar.classが展開されました
 com/example/Hello.classが展開されました
```

jarが出来たので実行してみます。

```console:
$ java -jar bar/build/libs/bar.jar 
Bar Hello!
$ java -jar foo/build/libs/foo.jar 
Foo Hello!
```


## 参考
[Gradle User Guide -> マルチプロジェクトのビルド](http://gradle.monochromeroad.com/docs/userguide/multi_project_builds.html)