Gradle ことはじめ (Java プロジェクトを作成して、外部ライブラリをまとめてひとつの実行可能 JAR を生成するまで)

## 概要

- 公式のチュートリアル資料等を参考に Gradle を導入する
- Gradle で Java プロジェクトを作成する
- 外部ライブラリを導入し、ひとつの実行可能 JAR を生成する

## 今回の環境

- macOS Mojave
- OpenJDK 11.0.2
- Gradle 5.5.1

## Gradle をインストールする

[Gradle \| Installation](https://gradle.org/install/) にいくつかの方法が載っている。
今回は macOS Mojave なので Homebrew でインストールした。

```
$ brew install gradle
```

## Java プロジェクトを作成する

### gradle init で Java プロジェクトを作成する

[Building Java Applications](https://guides.gradle.org/building-java-applications/) を参考にする。

gradle init コマンドでプロジェクトを作成する。

```
$ gradle init
Starting a Gradle Daemon (subsequent builds will be faster)

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

Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
Enter selection (default: Java) [1..4] 3

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

Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit 4) [1..4] 1

Project name (default: gradle): my-app
Source package (default: my.app): com.example

> Task :init
Get more help with your project: https://docs.gradle.org/5.5.1/userguide/tutorial_java_projects.html

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

### 生成されたファイル一覧

```
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── App.java
    │   └── resources
    └── test
        ├── java
        │   └── com
        │       └── example
        │           └── AppTest.java
        └── resources
```

### build.gradle

ビルドするための設定ファイルその1。

```gradle

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java project to get you started.
 * For more details take a look at the Java Quickstart chapter in the Gradle
 * User Manual available at https://docs.gradle.org/5.5.1/userguide/tutorial_java_projects.html
 */

plugins {
    // Apply the java plugin to add support for Java
    id 'java'

    // Apply the application plugin to add support for building a CLI application
    id 'application'
}

repositories {
    // Use jcenter for resolving dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

dependencies {
    // This dependency is used by the application.
    implementation 'com.google.guava:guava:27.1-jre'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'
}

application {
    // Define the main class for the application
    mainClassName = 'com.example.App'
}
```

### settings.gradle

ビルドするための設定ファイルその2。

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

rootProject.name = 'my-app'
```

### src/main/java/com/example/App.java

生成されたサンプル用のソースコード。

```java
/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package com.example;

public class App {
    public String getGreeting() {
        return "Hello world.";
    }

    public static void main(String[] args) {
        System.out.println(new App().getGreeting());
    }
}
```

### src/test/java/com/example/AppTest.java

生成されたサンプル用のテストコード。

```java
/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package com.example;

import org.junit.Test;
import static org.junit.Assert.*;

public class AppTest {
    @Test public void testAppHasAGreeting() {
        App classUnderTest = new App();
        assertNotNull("app should have a greeting", classUnderTest.getGreeting());
    }
}
```

### gradle tasks で実行可能なタスク一覧を確認する

gradle tasks コマンドで見ることができる。

```
$ gradle tasks

> Task :tasks

------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------

Application tasks
-----------------
run - Runs this project as a JVM application

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Distribution tasks
------------------
assembleDist - Assembles the main distributions
distTar - Bundles the project as a distribution.
distZip - Bundles the project as a distribution.
installDist - Installs the project as a distribution as-is.

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'my-app'.
components - Displays the components produced by root project 'my-app'. [incubating]
dependencies - Displays all dependencies declared in root project 'my-app'.
dependencyInsight - Displays the insight into a specific dependency in root project 'my-app'.
dependentComponents - Displays the dependent components of components in root project 'my-app'. [incubating]
help - Displays a help message.
model - Displays the configuration model of root project 'my-app'. [incubating]
projects - Displays the sub-projects of root project 'my-app'.
properties - Displays the properties of root project 'my-app'.
tasks - Displays the tasks runnable from root project 'my-app'.

Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.

Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.
Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
```

### gradle test でテストを実行する

gradle test コマンドでテストを実行できる。

```
$ gradle test

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

### gradle jar で JAR ファイルを作成する

gradle jar コマンドで JAR ファイルを生成できる。

```
$ gradle jar

BUILD SUCCESSFUL in 872ms
2 actionable tasks: 2 executed
```

### Java アプリケーションを実行する

クラスパスに JAR ファイルのパスを指定して java コマンドで実行する。

```
$ java -classpath build/libs/my-app.jar com.example.App
Hello world.
```

## 実行可能な JAR ファイルを作成する

### JARマニフェスト属性を指定する

JAR マニフェスト属性を build.gradle で指定する。
今回は以下の内容を追加している。

```gradle
jar {
    manifest {
        attributes 'Main-Class': 'com.example.App'
    }
}
```

[JARファイルの仕様](https://docs.oracle.com/javase/jp/11/docs/specs/jar/jar.html)

> スタンドアロン・アプリケーションに対して定義する属性。この属性は、Javaランタイムからjava -jar x.jarによって直接呼び出された実行可能jarファイルに含まれる、スタンドアロン・アプリケーションによって使われる。
> Main-Class: この属性の値は、起動時に起動ツールによってロードされるメイン・アプリケーション・クラスのクラス名である。 この値には、クラス名に拡張子.classが付いていてはならない。

### build.gradle を整理

build.gradle から不要なコメント等を削除して整理する。

```gradle
plugins {
    id 'java'
    id 'application'
}

repositories {
    jcenter()
}

dependencies {
    testImplementation 'junit:junit:4.12'
}

application {
    mainClassName = 'com.example.App'
}

jar {
    manifest {
        attributes 'Main-Class': 'com.example.App'
    }
}
```

### 実行可能な JAR ファイルを実行する

gradle jar で JAR ファイルを生成する。

```
$ gradle jar

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

クラス名を指定しなくても java -jar コマンドで Java アプリケーションを実行できるようになった。

```
$ java -jar build/libs/my-app.jar
Hello world.
```

## 外部ライブラリを導入する

ここでは JSON を操作できる外部ライブラリ [Jackson Core](https://github.com/FasterXML/jackson-core) を導入する。

### build.gradle に外部ライブラリを追加する

[Maven Repository: com\.fasterxml\.jackson\.core » jackson\-core » 2\.9\.9](https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core/2.9.9) には Gradle 用の設定情報が載っている。

```gradle

compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.9'
```

ただし、 compile は現在非推奨となっている。

[The Java Plugin](https://docs.gradle.org/5.5.1/userguide/java_plugin.html#tab:configurations)

> compile(Deprecated)
> Compile time dependencies. Superseded by implementation.

そのため、依存ライブラリは implementaion で記述する。
フォーマットは group name version をコロンで区切ったものになる。

```gradle

implementation 'com.fasterxml.jackson.core:jackson-core:2.9.9'
```

build.gradle の全体はこのようになる。

```gradle

plugins {
    id 'java'
    id 'application'
}

repositories {
    jcenter()
}

dependencies {
    implementation 'com.fasterxml.jackson.core:jackson-core:2.9.9'
    testImplementation 'junit:junit:4.12'
}

application {
    mainClassName = 'com.example.App'
}

jar {
    manifest {
        attributes 'Main-Class': 'com.example.App'
    }
}
```

### Java ソースコードを修正する

Jackson Core を使用するように src/main/java/com/example/App.java を修正する。

```java
package com.example;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;

public class App {
    public String getGreeting() {
        try {
            Writer out = new StringWriter();
            JsonFactory factory = new JsonFactory();
            JsonGenerator generator = factory.createGenerator(out);
            generator.writeStartObject();
            generator.writeStringField("message", "Hello world.");
            generator.writeEndObject();
            generator.flush();
            generator.close();
            return out.toString();
        } catch (IOException e) {
            return e.toString();
        }
    }

    public static void main(String[] args) {
        System.out.println(new App().getGreeting());
    }
}
```

### gradle run でプログラムを実行する

gradle run コマンドで Java アプリケーションを実行することができる。

```
$ gradle run

> Task :run
{"message":"Hello world."}

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

## 外部ライブラリをまとめてひとつの JAR ファイルにするプラグインを導入する

### Shadow Plugin を導入する

Shadow Plugin を導入することで、外部ライブラリとソースコードをひとつの JAR にまとめることができる。

[Gradle Shadow Plugin - Introduction](https://imperceptiblethoughts.com/shadow/introduction/)

> Shadow is a Gradle plugin for combining dependency classes and resources with a project's into a single output Jar. The combined Jar is often referred to a fat-jar or uber-jar. Shadow utilizes JarInputStream and JarOutputStream to efficiently process dependent libraries into the output jar without incurring the I/O overhead of expanding the jars to disk.

build.gradle の plugins に以下を追加する。

```gradle

id 'com.github.johnrengelman.shadow' version '5.1.0'
```

build.gradle の全体はこのようになる。

```gradle

plugins {
    id 'java'
    id 'application'
    id 'com.github.johnrengelman.shadow' version '5.1.0'
}

repositories {
    jcenter()
}

dependencies {
    implementation 'com.fasterxml.jackson.core:jackson-core:2.9.9'
    testImplementation 'junit:junit:4.12'
}

application {
    mainClassName = 'com.example.App'
}

jar {
    manifest {
        attributes 'Main-Class': 'com.example.App'
    }
}
```

### Shadow Plugin のタスクを確認する

gradle tasks コマンドで Shadow Plugin のタスクが増えていることを確認できる。

```
$ gradle tasks | grep -i shadow
runShadow - Runs this project as a JVM application using the shadow jar
startShadowScripts - Creates OS specific scripts to run the project as a JVM application using the shadow jar
assembleShadowDist - Assembles the shadow distributions
installShadowDist - Installs the project as a distribution as-is.
shadowDistTar - Bundles the project as a distribution.
shadowDistZip - Bundles the project as a distribution.
Shadow tasks
shadowJar - Create a combined JAR of project and runtime dependencies
```

### gradle shadowJar で JAR ファイルを作成する

gradle shadowJar コマンドで JAR ファイルを生成できる。

```
$ gradle shadowJar

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

### Java アプリケーションを実行する

java -jar コマンドで JAR ファイルを指定して実行する。
外部ライブラリもこのひとつの JAR ファイルにまとめられている。

```
$ java -jar build/libs/my-app-all.jar 
{"message":"Hello world."}
```

## 参考資料

- [Gradle Build Tool](https://gradle.org/)
- [Building Java Applications](https://guides.gradle.org/building-java-applications/)
- [Building Java & JVM projects](https://docs.gradle.org/5.5.1/userguide/building_java_projects.html)
- [Managing Dependency Configurations](https://docs.gradle.org/5.5.1/userguide/managing_dependency_configurations.html)
- [Dependency Management for Java Projects](https://docs.gradle.org/5.5.1/userguide/dependency_management_for_java_projects.html)
- [Gradle Shadow Plugin - Getting Started](https://imperceptiblethoughts.com/shadow/getting-started/)