gradle(Java)をJavaエキスパートがわかりやすく解説します😎モダンなgradleを使用し独自のビルドプロセスを効率よく構築する迄の道のり✨

gradle(Java)をJavaエキスパートがわかりやすく解説します😎モダンなgradleを使用し独自のビルドプロセスを効率よく構築する迄の道のり✨

2023.10.29

こんにちは!

AWSサーバーレスのプロジェクトで、最近では Java を採用する事例も少しずつ増えてきました。従来のサーバーレスでの Java 開発にはコールドスタート問題がよく聞かれましたが、最近は Lambda のSnapStart機能を使用することでコールドスタートを軽減することができます。Java の開発効率の高さとサーバーレスを掛け算は私たちも最近愛用しています。

私たちは通常、Java 開発で、Java を gradle でビルドし Lambda へアップロードしますが、maven に慣れている人にとってgradleは少々とっつきにくい面があるかもしれません。

本記事では、gradle を使用した Java のビルド方法から、初心者向けに gradle をわかりやすく解説します。

想定する読者

  • gradle に興味があるヒト
  • maven 開発しているけど、gradle に挑戦したいヒト
  • java のビルドプロセスを効率よく独自にカスタマイズしたいヒト

はじめに

Gradle VS Maven

gradle は maven の後発のため Maven を拡張・改良したモジュールと言えます。結論これから始めるプロジェクトであれば圧倒的に gradle を推奨します。例えば以下の点において gradle が優れていると言えます。

gradleの高速ビルド

優れたビルドプロセスの管理

ビルドタスクを Kotlin のDSLベースで作成が可能となっており優れた開発者体験を提供してくれます。そのためビルドのプロセス定義の作業にIDEやエディターのサポートの恩恵を受けることができます。

本記事では使用するGradleのバージョン

Gradle 8.4 となります。

$ gradle -v

------------------------------------------------------------
Gradle 8.4
------------------------------------------------------------
Kotlin:       1.9.10
Groovy:       3.0.17
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          21.0.1 (Homebrew 21.0.1)
OS:           Mac OS X 13.5.2 x86_64

gradleのインストール方法

まずはじめに、gradle をOSXへインストールします。
その他JDKインストールなど当然必要なので、詳しくは公式サイト見ておきましょう。

$ brew install gradle
$ gradle -v
------------------------------------------------------------
Gradle 8.4
------------------------------------------------------------
Kotlin:       1.9.10
Groovy:       3.0.17
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          21.0.1 (Homebrew 21.0.1)
OS:           Mac OS X 13.5.2 x86_64

gradleの初期設定

細かいところはお好みでインストールしましょう。

$ gradle init

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
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6] 3

Generate multiple subprojects for application? (default: no) [yes, no] no
Select build script DSL:
  1: Kotlin
  2: Groovy
Enter selection (default: Kotlin) [1..2] 1

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

Project name (default: tutorial): gradle
Source package (default: tutorial): com.gradle.gradle
Enter target version of Java (min. 7) (default: 17): 17
Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] no

インストール後は、プロジェクトのルートディレクトリに出力された gradlew というファイルを実行することで、プロジェクトのチームメンバーが gradle をインストールせずに、gradle を実行することが可能となります。

$ bash ./gradlew [command]

gradleのタスクを理解

コトリン形式でタスクを登録できます。Javascript の npm でいうところの package.json 内の scripts みたいなものですね。(scripts よりもリッチなことができる)

タスクの一覧表示

ルートプロジェクト及びサブプロジェクトのタスクを一覧で表示します。

$ bash gradlew tasks --all

タスクの登録

サブプロジェクトのbuild.gradle.ktsへ以下を入力すると、タスクとして実行が可能です。

tasks.register("hello") {
    println("Hello!")
}

tasks.register("greet") {
    println("How are you?")
    dependsOn("hello")
}
$ bash gradlew app:greet
How are you?
Hello!

タスク作成方法のさらなる詳しい情報は、コトリンの DSL の公式ドキュメントを見ましょう。
Kotlin DSL公式サイト

結構充実しています。

ビルド方法

gradle で jar を生成します。

$ bash gradlew build
> Task :app:compileJava
> Task :app:processResources
> Task :app:classes
> Task :app:jar
> Task :app:startScripts
> Task :app:distTar
> Task :app:distZip
> Task :app:assemble
> Task :app:compileTestJava
> Task :app:processTestResources
> Task :app:testClasses
> Task :app:test
> Task :app:check
> Task :app:build

build コマンドでは、アセンブル〜テストを自動的に一式行ってくれます。
もちろん、前述のタスクの登録を応用し好みのスクリプトを追加することも可能です。
gradle の特徴にビルドのプロセスを独自にスクリプトで拡張できる点があります。柔軟なビルドプロセスを定義できるので、CICD との連携も視野に入れて柔軟なバックエンド開発環境が構築可能ですね。

jar ファイルは以下のディレクトリに出力されます。
{sub project name}/build/libs/app.jar

そして、gradle では単純に jar ファイルの出力のみを行うコマンドも搭載しています。

$ bash gradlew jar
# または
$ bash gradlew assemble

一方で、実際のCICDでのビルドは bash gradlew build を使用しましょう。以下はビルドに関連するコマンドのまとめです。

タスク説明
assembleコンパイルを実行しJAR、WAR、ZIP、TARファイル等々を生成
buildassemble 後にテストを実行
buildDependentsプロジェクトが依存するプロジェクトを含め build を実行
buildNeeded指定プロジェクトだけでなく、そのプロジェクトの testRuntime 依存関係に設定されているプロジェクトをすべてテスト
classesメインクラスを assemble
clean成果物(build ディレクトリ配下)を削除
jarメインクラスを含む Jar ファイルを生成
testClassesテストクラスを assemble

Javaの実行

サブプロジェクトの Java の実行は以下のコマンドで可能です。

$ bash gradlew run

上記のコマンドでは、{sub project name}/src/main/java/gradle/App.java が実行されます。

public class App {
    public String getGreeting() {
        return "Hello World!";
    }
    public static void main(String[] args) {
        System.out.println(new App().getGreeting());
    }
}

gradleのパッケージ管理

gradle でのパッケージで抑えるべき基本は、リポジトリと依存関係の指定をどこで行っているかです。サブプロジェクトの gradle.build.kts を見てみましょう。

repositories {
    mavenCentral()
}

dependencies {
    testImplementation("junit:junit:4.13.2")
    implementation("com.google.guava:guava:32.1.2-jre")
}

repositories にはパッケージ取得先のリポジトリを指定し、dependencies には依存関係(使用するパッケージ)を指定します。

上記の設定はデフォルトの設定ですが、まずリポジトリには Java のデファクトスタンダードのMavenを指定しています。つまり、gradle のプロジェクトにおいては、Maven のパッケージを制限なく利用できるということです。repositories の設定は基本的に変更しませんが、リポジトリを独自運用していたりする場合は、適宜変更します。(大手企業で社内で開発資産を蓄積しているようなケース)

そして repositories にはパッケージを指定しますが、前述の設定例では Java のテストモジュールとしてデファクトスタンダードな JUnit がインストールされています。依存関係の構文は以下の通りです。

com.google.guava:guava:32.1.2-jrejunit:junit:4.13.2
グループcom.google.guavajunit
名称guavajunit
バージョン32.1.2-jre
4.13.2

依存関係のツリーを出力

gradle を使用して依存関係を可視化してみましょう。インストールしたパッケージが依存しているパッケージがツリーになって表示されます。

$ bash gradlew app:dependencies

> Task :app:dependencies

------------------------------------------------------------
Project ':app'
------------------------------------------------------------

annotationProcessor - Annotation processors and their dependencies for source set 'main'.
No dependencies

compileClasspath - Compile classpath for source set 'main'.
\--- com.google.guava:guava:32.1.1-jre
     +--- com.google.guava:guava-parent:32.1.1-jre
     |    +--- com.google.code.findbugs:jsr305:3.0.2 (c)
     |    +--- org.checkerframework:checker-qual:3.33.0 (c)
     |    +--- com.google.errorprone:error_prone_annotations:2.18.0 (c)
     |    \--- com.google.j2objc:j2objc-annotations:2.8 (c)
     +--- com.google.guava:failureaccess:1.0.1
     +--- com.google.code.findbugs:jsr305 -> 3.0.2
     +--- org.checkerframework:checker-qual -> 3.33.0
     +--- com.google.errorprone:error_prone_annotations -> 2.18.0
     \--- com.google.j2objc:j2objc-annotations -> 2.8

compileOnly - Compile-only dependencies for the 'main' feature. (n)
No dependencies

default - Configuration for default artifacts. (n)
No dependencies

implementation - Implementation dependencies for the 'main' feature. (n)
\--- com.google.guava:guava:32.1.1-jre (n)

mainSourceElements - List of source directories contained in the Main SourceSet. (n)
No dependencies

runtimeClasspath - Runtime classpath of source set 'main'.
\--- com.google.guava:guava:32.1.1-jre
     +--- com.google.guava:guava-parent:32.1.1-jre
     |    +--- com.google.code.findbugs:jsr305:3.0.2 (c)
     |    +--- org.checkerframework:checker-qual:3.33.0 (c)
     |    \--- com.google.errorprone:error_prone_annotations:2.18.0 (c)
     +--- com.google.guava:failureaccess:1.0.1
     +--- com.google.code.findbugs:jsr305 -> 3.0.2
     +--- org.checkerframework:checker-qual -> 3.33.0
     \--- com.google.errorprone:error_prone_annotations -> 2.18.0

runtimeElements - Runtime elements for the 'main' feature. (n)
No dependencies

runtimeOnly - Runtime-only dependencies for the 'main' feature. (n)
No dependencies

testAnnotationProcessor - Annotation processors and their dependencies for source set 'test'.
No dependencies

testCompileClasspath - Compile classpath for source set 'test'.
+--- com.google.guava:guava:32.1.1-jre
|    +--- com.google.guava:guava-parent:32.1.1-jre
|    |    +--- com.google.code.findbugs:jsr305:3.0.2 (c)
|    |    +--- org.checkerframework:checker-qual:3.33.0 (c)
|    |    +--- com.google.errorprone:error_prone_annotations:2.18.0 (c)
|    |    \--- com.google.j2objc:j2objc-annotations:2.8 (c)
|    +--- com.google.guava:failureaccess:1.0.1
|    +--- com.google.code.findbugs:jsr305 -> 3.0.2
|    +--- org.checkerframework:checker-qual -> 3.33.0
|    +--- com.google.errorprone:error_prone_annotations -> 2.18.0
|    \--- com.google.j2objc:j2objc-annotations -> 2.8
\--- org.junit.jupiter:junit-jupiter:5.9.3
     +--- org.junit:junit-bom:5.9.3
     |    +--- org.junit.jupiter:junit-jupiter:5.9.3 (c)
     |    +--- org.junit.jupiter:junit-jupiter-api:5.9.3 (c)
     |    +--- org.junit.jupiter:junit-jupiter-params:5.9.3 (c)
     |    \--- org.junit.platform:junit-platform-commons:1.9.3 (c)
     +--- org.junit.jupiter:junit-jupiter-api:5.9.3
     |    +--- org.junit:junit-bom:5.9.3 (*)
     |    +--- org.opentest4j:opentest4j:1.2.0
     |    +--- org.junit.platform:junit-platform-commons:1.9.3
     |    |    +--- org.junit:junit-bom:5.9.3 (*)
     |    |    \--- org.apiguardian:apiguardian-api:1.1.2
     |    \--- org.apiguardian:apiguardian-api:1.1.2
     \--- org.junit.jupiter:junit-jupiter-params:5.9.3
          +--- org.junit:junit-bom:5.9.3 (*)
          +--- org.junit.jupiter:junit-jupiter-api:5.9.3 (*)
          \--- org.apiguardian:apiguardian-api:1.1.2

testCompileOnly - Compile only dependencies for source set 'test'. (n)
No dependencies

testImplementation - Implementation only dependencies for source set 'test'. (n)
\--- org.junit.jupiter:junit-jupiter:5.9.3 (n)

testRuntimeClasspath - Runtime classpath of source set 'test'.
+--- com.google.guava:guava:32.1.1-jre
|    +--- com.google.guava:guava-parent:32.1.1-jre
|    |    +--- com.google.code.findbugs:jsr305:3.0.2 (c)
|    |    +--- org.checkerframework:checker-qual:3.33.0 (c)
|    |    \--- com.google.errorprone:error_prone_annotations:2.18.0 (c)
|    +--- com.google.guava:failureaccess:1.0.1
|    +--- com.google.code.findbugs:jsr305 -> 3.0.2
|    +--- org.checkerframework:checker-qual -> 3.33.0
|    \--- com.google.errorprone:error_prone_annotations -> 2.18.0
+--- org.junit.jupiter:junit-jupiter:5.9.3
|    +--- org.junit:junit-bom:5.9.3
|    |    +--- org.junit.jupiter:junit-jupiter:5.9.3 (c)
|    |    +--- org.junit.jupiter:junit-jupiter-api:5.9.3 (c)
|    |    +--- org.junit.jupiter:junit-jupiter-engine:5.9.3 (c)
|    |    +--- org.junit.jupiter:junit-jupiter-params:5.9.3 (c)
|    |    +--- org.junit.platform:junit-platform-launcher:1.9.3 (c)
|    |    +--- org.junit.platform:junit-platform-commons:1.9.3 (c)
|    |    \--- org.junit.platform:junit-platform-engine:1.9.3 (c)
|    +--- org.junit.jupiter:junit-jupiter-api:5.9.3
|    |    +--- org.junit:junit-bom:5.9.3 (*)
|    |    +--- org.opentest4j:opentest4j:1.2.0
|    |    \--- org.junit.platform:junit-platform-commons:1.9.3
|    |         \--- org.junit:junit-bom:5.9.3 (*)
|    +--- org.junit.jupiter:junit-jupiter-params:5.9.3
|    |    +--- org.junit:junit-bom:5.9.3 (*)
|    |    \--- org.junit.jupiter:junit-jupiter-api:5.9.3 (*)
|    \--- org.junit.jupiter:junit-jupiter-engine:5.9.3
|         +--- org.junit:junit-bom:5.9.3 (*)
|         +--- org.junit.platform:junit-platform-engine:1.9.3
|         |    +--- org.junit:junit-bom:5.9.3 (*)
|         |    +--- org.opentest4j:opentest4j:1.2.0
|         |    \--- org.junit.platform:junit-platform-commons:1.9.3 (*)
|         \--- org.junit.jupiter:junit-jupiter-api:5.9.3 (*)
\--- org.junit.platform:junit-platform-launcher -> 1.9.3
     +--- org.junit:junit-bom:5.9.3 (*)
     \--- org.junit.platform:junit-platform-engine:1.9.3 (*)

testRuntimeOnly - Runtime only dependencies for source set 'test'. (n)
\--- org.junit.platform:junit-platform-launcher (n)

(c) - A dependency constraint, not a dependency. The dependency affected by the constraint occurs elsewhere in the tree.
(*) - Indicates repeated occurrences of a transitive dependency subtree. Gradle expands transitive dependency subtrees only once per project; repeat occurrences only display the root of the subtree, followed by this annotation.

(n) - A dependency or dependency configuration that cannot be resolved.

A web-based, searchable dependency report is available by adding the --scan option.

グローバルにパッケージを指定

全てのサブプロジェクトに同じバージョンのパッケージを適用したいケースがあるかと思います。例えば同じデータベースを参照するようなサブプロジェクト同士が、データベースへ通信を行うモジュールのバージョンをサブプロジェクトごとに定義するメリットは感じません。

まず gradle/libs.versions.toml に以下を設定します。

[versions]
junitVer = "5.9.3"
guavaVer = "32.1.1-jre"

[libraries]
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junitVer" }
guava = { module = "com.google.guava:guava", version.ref = "guavaVer" }

そしてサブプロジェクトの、build.gradle.ktsgradle/libs.versions.toml に設定したパッケージのバージョンを指定します。

dependencies {
    // testImplementation("org.junit.jupiter:junit-jupiter:5.9.3")
    testImplementation(libs.junit)
    // implementation("com.google.guava:guava:32.1.1-jre")
    implementation(libs.guava)
}

試しに、run コマンドで実行してみるときちんと動作することがわかります。

$ bash gradlew run

gradle scanでトラブルシューティングを効率化

gradle の Scan という機能を用いることで、ビルドの内容(ソースコードや実行環境などの塊)を gradle の WEB にアップロードできます。アップロード後、ランダムな URL が生成されますのでチームメンバーなどへ共有が可能です。アップロード前に利用規約への同意が必要なこともあり、セキュリティには注意しましょう。(URL は後で削除できますので一時的にチームへトラブルシューティングのヘルプを出す際に役立ちます)

実行方法は簡単で、ビルドコマンドに--scan というオプションをつけるだけです。

$ bash gradlew build --scan

WEB画面はこんな感じです。依存関係などの情報もしっかり可視化されます。

build scan results
build scan dependencies
build scan trans dependencies

gradle プラグイン利用で資産共有

gradle では、タスクやモデル(Kotlin DSL)の拡張に、プラグインを適用できます。プラグインを使用することで、第三者が開発した開発資産を自分のプロジェクトに簡単に取り入れることが可能です。(たった1行追記するだけで追加が可能)プラグインでは実現できることは主に以下です。

  • タスクを自動的に追加
  • Kotlin DSL に新しいモデル定義を追加
  • Kotlin DSL の既存のモデルを拡張
  • 特定のパッケージ依存関係を自動で追加(例えば自社の開発チームがよく使用するモジュールを適用する等々)

よくあるユースケースとして、Java の開発資産が豊富に蓄積されている企業が、複数の開発プロジェクトに対して自社独自の規約やモジュール、タスクを適用する事が可能です。

自社内のLAN内に Maven リポジトリを作成し、そこからプラグインパッケージを開発チームへ提供すれば情報漏洩の危険性も薄いです。

それではプラグインの適用方法を見ていきましょう。以下は、build.gradle.kts の抜粋です。

plugins {
    application
}

gradle でプロジェクトの雛形を作成した場合、デフォルトでは上記の Java のアプリケーション開発で使用するプラグインが適用されています。

そして、ここにプラグインを一つ追加してみます。

plugins {
   application
   `maven-publish`
}

※ ダブルクオートで囲うと反応しないので注意!

maven-publish についてはこちらをご覧ください。(maven-publish の説明は本セクションの趣旨と外れますので割愛します)

上記の設定を行うと、VS-CODE か JetBrains 製品を利用していれば「ローカルの Maven リポジトリと同期しますか?」みたいな画面下部にメッセージが表示されます。同期を実行した後に、タスクの一覧を出力するとプラグインが提供するタスクが一覧に現れるはずです。(自動同期設定も可能なのでお使いのエディターでお試しください)

$ bash gradlew app:tasks

Publishing tasks
----------------
publish - Publishes all publications produced by this project.
publishToMavenLocal - Publishes all Maven publications produced by this project to the local Maven cache.

これで、基本的なプラグインの使い方は終わりです。尚、プラグイン取得先のリポジトリはデフォルトでは以下の設定で MavenCentral となっており、MavenCentral に公開されているものなら自由に使用することが可能です。

ローカルMavenリポジトリへパッケージを公開して使用する

gradle と先ほどの maven-publish を使用して、ローカルの maven リポジトリへプラグインをアップロードします。ローカルの Maven リポジトリへプラグインをアップロードするメリットは以下です。

  • インターネット参照が規制されている作業環境の場合、ローカルの Maven にプラグインをアップロードしておくことでキャッシュできる為、オフライン環境で作業可能となる
  • ローカルの複数プロジェクト間でモジュールを共通利用できる
  • プラグインを拡張したプラグインを開発しローカルの複数プロジェクトで共通利用できる

基本的には MavenCentral や社内のLAN上のリポジトリ等々をリポジトリに指定するケースが大半かと思いますが、gradle では以下の build.gradle.kts に以下の設定を加えることでローカルリポジトリを参照することができます。

repositories {
    mavenCentral()
    mavenLocal() # ← 追加
}

そして、maven-publish パッケージの機能を用いて、ローカルにパッケージを公開します。以下の設定値をbuild.gradle.kts に設定してみましょう。

publishing {
    publications {
        create<MavenPublication>("maven") {
            groupId = "org.hoge.sample"
            artifactId = "library"
            version = "1.1"
            from(components["java"])
        }
    }
    // パブリッシュ先のローカル以外に設定する場合は以下を設定(未指定の場合はデフォルトでローカルMavenリポジトリとなる)
    repositories {
        maven {
            url '...'
        }
    }
}

上記の各設定値の説明は以下です。

プパパティ説明
groupIdパッケージのグループ
artifactIdパッケージの識別子(名称)
versionパッケージのバージョン

そして、タスク一覧を表示してみます。

$ bash gradlew app:tasks

Publishing tasks
----------------
generateMetadataFileForMavenPublication - Generates the Gradle metadata file for publication 'maven'.
generatePomFileForMavenPublication - Generates the Maven POM file for publication 'maven'.
publish - Publishes all publications produced by this project.
publishMavenPublicationToMavenLocal - Publishes Maven publication 'maven' to the local Maven repository.
publishToMavenLocal - Publishes all Maven publications produced by this project to the local Maven cache.

設定前後でいくつかタスクが追加されていると思います。これは先ほど設定したカスタム構成の結果です。各プラグインは Maven リポジトリに設定値のサンプルや設定できる項目を公開していますので、プラグインを使用する際は必ず一度目を通しましょう。

mavenローカルへプラグインをアップロード

前述の maven-publish を使用して、ローカル Maven リポジトリへライブラリーをアップロードします。

前述のプラグインはリポジトリの定義がありませんおで、を使用するには、publishToMavenLocal を実行し、ローカルMaven リポジトリへプラグインをインストールします。

$ bash gradlew publishToMavenLocal

上記のコマンドにより、ローカルの Maven リポジトリへアップロードされます。そして、build.gradle.kts 内のプロジェクトのリポジトリストに忘れずにローカルの Maven を追加しておきましょう。

repositories {
    mavenCentral()
    mavenLocal() // ←ローカルのMavenを指定
}

このローカルの Maven リポジトリのテクニックを応用することで、例えば Scala などで実装されたプロジェクトの機能をローカルで共有し合うことが可能です。(最終的に jar 形式であれば容易に Java プロジェクトに設定できる)

インクリメントビルド

gradle は、ビルドを高速化する仕組みとしてインクリメントビルドを搭載しています。

インクリメンタルビルドとは、前回のビルドから入力が変化していないタスクの実行を行スキップする機能です。ビルド時に、Gradle は入力や出力が変更されたかどうかを判断します。もし変更されていれば、Gradle はタスクを実行し、そうでなければ実行をスキップします。

具体的にどのような挙動をしているのか実際にビルドコマンドを実行して見ていきましょう。

まず初めに、ビルドの詳細を確認したいのでプロジェクトのルートに gradle.properties を実行し、ビルドの詳細出力モードを有効化します。

$ vim gradle.properties

org.gradle.console=verbose

そしてビルドを実行します。

$ bash gradlew app:build

> Task :app:compileJava UP-TO-DATE
> Task :app:processResources NO-SOURCE
> Task :app:classes UP-TO-DATE
> Task :app:jar UP-TO-DATE
> Task :app:startScripts UP-TO-DATE
> Task :app:distTar UP-TO-DATE
> Task :app:distZip UP-TO-DATE
> Task :app:assemble UP-TO-DATE
> Task :app:compileTestJava UP-TO-DATE
> Task :app:processTestResources NO-SOURCE
> Task :app:testClasses UP-TO-DATE
> Task :app:test UP-TO-DATE
> Task :app:check UP-TO-DATE
> Task :app:build UP-TO-DATE

各タスク毎に UP-TO-DATE というラベルが付与されているのかわかります。これは、入出力に変化がなかったためスキップされた(インクリメントビルド機能)、という事です。gradle には、以下のようなラベルの種類が存在します。

ラベル説明
UP-TO-DATE変更のないタスク(実行をスキップ)
SKIPPEDタスク実行が明示的に禁止されている状態
FROM-CACHEタスク出力をビルドキャッシュ(キャッシュ機能)内の以前のビルドからローカルディレクトリにコピー
NO-SOURCE必要な入力が渡されなかった為、タスク実行をスキップ

インクリメントの落とし穴:キャッシュ

インクリメンタルビルドは、すでに意味のないタスク(入出力に変化のないタスク)の実行を回避するのに役立つ優れてます。つまり、 開発者が1つのファイルに継続的に変更を加えている場合、プロジェクト内の他のファイルをすべてリビルドする必要はないという事です。

しかし、同じ開発者が先週作成された新しいブランチに切り替えるとどうなるでしょうか。 単純に考えれば、開発者はブランチの変化により、既にビルド済みのソースコードを再ビルドしなければいけない状況になると思います。(差分ビルドではなく完全ビルド)

そこで役立つのが gradle のビルドキャッシュ機能です。キャッシュは以前のビルド結果を保存し、ローカルでビルド済みのものを再構築するのを防いでくれます。

ではまず、ローカルビルドキャッシュを有効にしましょう。

$ vim gradle.properties

org.gradle.caching=true

そしてビルドタスクを実行し、キャッシュにデータを溜めます。

$ bash gradlew :app:clean :app:build

> Task :app:clean
> Task :app:compileJava
> Task :app:processResources NO-SOURCE
> Task :app:classes
> Task :app:jar
> Task :app:startScripts
> Task :app:distTar
> Task :app:distZip
> Task :app:assemble
> Task :app:compileTestJava
> Task :app:processTestResources NO-SOURCE
> Task :app:testClasses
> Task :app:test
> Task :app:check
> Task :app:build

そして、ブランチを切り替えてビルドを実行します。

$ bash gradlew  :app:build

> Task :app:clean
> Task :app:compileJava FROM-CACHE
> Task :app:processResources NO-SOURCE
> Task :app:classes UP-TO-DATE
> Task :app:jar
> Task :app:startScripts
> Task :app:distTar
> Task :app:distZip
> Task :app:assemble
> Task :app:compileTestJava FROM-CACHE
> Task :app:processTestResources NO-SOURCE
> Task :app:testClasses UP-TO-DATE
> Task :app:test FROM-CACHE
> Task :app:check UP-TO-DATE
> Task :app:build

FROM-CACHE というラベルを見てください。キャッシュからビルド生成物を取得することに成功しています。

まとめ

本記事では、gradle を使用したモダンな Java のビルドについてまとめました。私たちは、gradle でビルドした結果を AWS Lambda へアップロードし、サーバーレスな API の構築を日々行っています。

Java × gradle × serverless で圧倒的な開発効率を是非本記事の内容を応用してお試しください!

JavaのAWSサーバーレス開発はお気軽にお問い合わせください。