ENGINEERING NOTEBOOK / 03 GRADLE ADVANCED
ビルドを理解し、
設計し、改善する。
動かせるようになった、その次へ。
同じプロジェクトに小さな改善を重ね、設定の理由を説明できるようになる8章です。
基礎編12章を修了し、SampleSystemのapp・commonが動くところから始めます。Javaのソースは引き継ぎ、今度はビルドそのものを育てます。
演習用のコピーを用意するBEFORE YOU START
基礎編の成果物を残して、始める。
基礎編第10章のSampleSystem(appとcommon、テスト1件が成功する状態)を使います。元の成果物を残すため、同じ親フォルダーに空の AdvancedSampleSystem を作ります。以後、第1〜6・8章はこのコピーが作業先です。第7章だけ更新実験用にさらにコピーします。
| コピー元:SampleSystem内 | コピー先:AdvancedSampleSystem内 |
|---|---|
gradlew、gradlew.bat | 同名でルートへ |
gradle/wrapper/gradle-wrapper.jar、gradle/wrapper/gradle-wrapper.properties | 同じ相対位置へ |
settings.gradle.kts、build.gradle.kts、gradle.properties | 同名でルートへ |
app/build.gradle.kts、app/src/ | app/へ。テスト用srcも含める |
common/build.gradle.kts、common/src/ | common/へ |
OSのエクスプローラーで上のファイル・フォルダーだけをコピーします。各階層の .gradle/、build/、.project、.classpath、.settings/ はコピーしません。gradle/wrapper/ と .gradle/ は別物です。Git関連ファイルもこの演習には不要です。
gradle/libs.versions.toml がSampleSystemにあれば同じ場所へコピーします。基礎編第10章の手順ではコピー対象外だったので、なければ元のSampleAppの gradle/libs.versions.toml をここへコピーしてください。基礎編第1章のinitで生成された、まだ使っていないファイルを第3章で利用します。元ファイルを失った場合だけ、空のファイルを同じ位置に作って構いません。
Set-Location 'C:\自分の練習用フォルダー\AdvancedSampleSystem'
Get-Locationパスを自分の配置に置き換えます。現在位置の末尾がAdvancedSampleSystemであることを確認してください。
settings.gradle.ktsコピー側だけをこの全文へ置換rootProject.name = "AdvancedSampleSystem"
include("app", "common")rootProject.name = 'AdvancedSampleSystem'
include 'app', 'common'.\gradlew.bat --version
.\gradlew.bat projects
.\gradlew.bat :app:build
.\gradlew.bat :app:runGradle 9.6.0、ルートAdvancedSampleSystem、app/common、テスト1件成功、Hello, Gradle!を確認します。最初からここで失敗する場合は、基礎編の状態とJDK 25を確認してから先へ進みます。
Eclipseでは基礎編と同じExisting Gradle ProjectのImportで AdvancedSampleSystem を選び、Wrapper・JDK 25を使います。ビルド定義の編集後は保存し、Gradle Refreshで反映します。以降の成否は、IDEの自動ビルド表示ではなく、ここに載せるWrapperコマンドで確かめます。
ADVANCED / 同じビルドを育てる
Gradleは、いつ設定を読み、いつTaskを実行する?
01今回やりたいこと
「buildファイルに書いた処理は、上から順に全部実行される?」を確かめます。Taskを頼んでいないのに出るメッセージと、頼んだときだけ出るメッセージを見分けましょう。
02なぜ今の状態では困る?
ファイルのトップレベルの処理と、Taskが仕事をするときの処理を混ぜると、helpを実行するだけでファイルを書き換える、といった意図しない動きにつながります。
03どういう仕組みで解決する?
tasks.register はTaskを登録し、設定を必要になるまで遅らせます。登録ブロック内は Taskの設定、doLast の中は Taskの実行時の処理 です。設定が評価されても、そのTaskが実行されるとは限りません。
PHASE LAB / 概念例
ブラウザ内の説明切替です。Gradleは実行しません。
04どのファイルを変更する?
準備を終えたルートの settings.gradle.kts と build.gradle.kts を使います。app/commonは変更しません。
05何を追加・変更する?
settings.gradle.kts既存のincludeの後ろへ1行追加println("[init] settings")println '[init] settings'build.gradle.ktsルートのコメントだけの内容を、この全文へ置換println("[config] root build")
tasks.register("observe") {
println("[config] observe")
doLast { println("[execute] observe") }
}println '[config] root build'
tasks.register('observe') {
println '[config] observe'
doLast { println '[execute] observe' }
}06実際に動かして確認する
.\gradlew.bat help --no-configuration-cache
.\gradlew.bat observe --no-configuration-cache1行目はhelpだけを頼みます。[init] settingsと[config] root buildを探し、[execute] observeが出ないことを確認。2行目はobserveを頼み、[config] observeと[execute] observeも出ることを確認します。
| 場所 | helpの場合 | observeの場合 |
|---|---|---|
| settingsのprintln | 表示される | 表示される |
| buildのトップレベル | 表示される | 表示される |
| register内のprintln | この構成では表示されない | Taskの設定時に表示 |
| doLast内のprintln | 表示されない | Taskの実行時に表示 |
07何が変わった?
buildファイルは命令をすべて順番に実行する手順書ではありません。Gradleに設定・Task・依存関係を渡し、依頼したTaskの仕事を実行させます。
後片付け:settingsへ足した観察用printlnを削除します。ルートbuildのobserveとprintlnは、第2章の全文置換で取り除きます。
「helpでは設定されない」の範囲
遅延登録したTaskを他の設定からget()などで取り出すと、そこで設定が必要になることがあります。この例の結果を、すべてのPlugin・ビルドに一般化しないでください。第5章ではConfiguration Cacheにより設定全体を再利用する場合も観察します。
printlnとdoLastを見て「設定時/実行時」を説明でき、観察用コードを残さず第2章へ進める。
ADVANCED / 同じビルドを育てる
自分でTaskを作りたい
01今回やりたいこと
ビルドで使う情報を build/info/build-info.properties に出力したい。既存Taskを使う段階から、小さな仕事を自分で定義する段階へ進みます。
02なぜ今の状態では困る?
単にファイルを書くだけでは、Gradleは「何が変わったら作り直す必要があるか」を判断できません。同じファイルを毎回書き直す例から、入力と出力を伝える例へ置き換えます。
03どういう仕組みで解決する?
Providerは、後で必要になったときに値を受け取る窓口です。providers.gradleProperty("message") は -Pmessage=... の値を扱い、orElse("hello") で未指定時の値を決めます。すぐget()せず、TaskのPropertyへsetして結びつけます。
04どのファイルを変更する?
変更するのはルートの build.gradle.kts だけです。出力は自動生成されます。入力の値はコマンドで渡すので、設定用の新しいファイルは作りません。
05何を追加・変更する?
まず1回目の置換:次の単純なTaskを貼り付けます。doLastの中だけがファイルを生成する仕事です。
build.gradle.kts第1章の観察コード全体を置換val looseFile = layout.buildDirectory.file("info/loose.txt")
tasks.register("writeLoose") {
doLast {
val file = looseFile.get().asFile
file.parentFile.mkdirs()
file.writeText("message=hello\n", Charsets.UTF_8)
}
}def looseFile = layout.buildDirectory.file('info/loose.txt')
tasks.register('writeLoose') {
doLast {
def file = looseFile.get().asFile
file.parentFile.mkdirs()
file.setText('message=hello\n', 'UTF-8')
}
}.\gradlew.bat writeLoose --no-configuration-cache
.\gradlew.bat writeLoose --no-configuration-cachewriteLooseを2回頼みます。build/info/loose.txtが作られますが、出力未宣言なので、2回目もTaskの処理が実行されます。
次に2回目の置換:次の全文へ置き換えます。入力・出力を持つTaskの型GenerateInfoと、その型を使うgenerateInfoを定義します。ここではファイルを一つだけ作るので、この型だけに絞ります。
build.gradle.ktswriteLooseを含む全内容を置換 / importsも含めるimport org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class GenerateInfo : DefaultTask() {
@get:Input
abstract val message: Property<String>
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun generate() {
val file = outputFile.get().asFile
file.parentFile.mkdirs()
file.writeText("message=" + message.get() + "\n", Charsets.UTF_8)
}
}
val generateInfo = tasks.register<GenerateInfo>("generateInfo") {
message.set(providers.gradleProperty("message").orElse("hello"))
outputFile.set(layout.buildDirectory.file("info/build-info.properties"))
}
tasks.register("prepareInfo") {
dependsOn(generateInfo)
doLast { println("Info is ready.") }
}import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class GenerateInfo extends DefaultTask {
@Input
abstract Property<String> getMessage()
@OutputFile
abstract RegularFileProperty getOutputFile()
@TaskAction
void generate() {
def file = outputFile.get().asFile
file.parentFile.mkdirs()
file.setText('message=' + message.get() + '\n', 'UTF-8')
}
}
def generateInfo = tasks.register('generateInfo', GenerateInfo) {
message.set(providers.gradleProperty('message').orElse('hello'))
outputFile.set(layout.buildDirectory.file('info/build-info.properties'))
}
tasks.register('prepareInfo') {
dependsOn(generateInfo)
doLast { println 'Info is ready.' }
}| コード | この演習での役割 |
|---|---|
| @Input / @OutputFile | 比較すべき入力値と生成するファイルをGradleへ伝える |
| Property / RegularFileProperty | 入力値と出力先を保持する。Providerも受け取れる |
| @TaskAction | この型のTaskが実際に行う仕事。先ほどのdoLastと同じく実行時 |
| tasks.register | generateInfoを遅延登録する。戻り値はTaskProvider |
| dependsOn(generateInfo) | prepareInfoに必要な先行Taskを指定する |
06実際に動かして確認する
.\gradlew.bat prepareInfo --no-configuration-cache
.\gradlew.bat prepareInfo --no-configuration-cache
.\gradlew.bat generateInfo -Pmessage=team --no-configuration-cache
Get-Content .\build\info\build-info.propertiesprepareInfoがgenerateInfoを実行対象にし、その後にInfo is ready.を表示します。2回目はgenerateInfoがUP-TO-DATE。-Pはプロジェクト用の入力プロパティを渡し、値が変わると再生成されます。ファイルのmessage=teamを確認します。
Remove-Item -LiteralPath '.\build\info\build-info.properties'
.\gradlew.bat generateInfo -Pmessage=team --no-configuration-cache今生成したファイルだけを削除して再実行します。入力が同じでも、出力が欠けているので再生成されることを確認します。
07何が変わった?
入力・出力を宣言したことで、変更のないTaskの仕事を省略できました。入出力を書いただけでBuild Cacheの対象になるわけではありません。このTaskは第5章で、結果を再利用してよい条件を確認してからキャッシュ対応にします。
入力宣言を間違えると?
出力を変える値を入力として宣言し忘れると、古い出力が正しいと判断される恐れがあります。現在時刻や乱数を勝手に出力へ混ぜないでください。この例はUTF-8・改行・messageだけから同じ結果を作り、Task実行中にProjectやbuildスクリプトの状態を参照しません。
2回目のUP-TO-DATE、入力変更時の再生成、出力削除後の再生成を確認し、dependsOnの意味を説明できる。
公式資料:Custom Tasks / Lazy Configuration / Incremental Build / Configuration Cache Requirements
ADVANCED / 同じビルドを育てる
ライブラリのバージョンを一か所で管理したい
01今回やりたいこと
複数のbuildファイルに散った依存の座標・版を、一つの一覧から参照したい。ここではcommonのCommons LangとappのJUnitを移します。
02なぜ今の状態では困る?
今はライブラリの版を調べるためにappとcommonを開きます。同じライブラリを使うProjectが増えると、更新漏れも起こりやすくなります。
03どういう仕組みで解決する?
Version Catalogは、依存やPluginを別名で参照するためのカタログです。gradle/libs.versions.toml をGradleが標準の場所から読み、libs の参照を用意します。基礎編のinitが作っていたファイルを、ここで初めて使います。
| 項目 | 入れるもの |
|---|---|
| [versions] | 再利用するバージョン名と値 |
| [libraries] | ライブラリの別名、module、必要ならversion.ref |
| [plugins] | 外部Pluginの別名、ID、版。Gradle組み込みPluginの版は書かない |
04どのファイルを変更する?
gradle/libs.versions.toml、app/build.gradle.kts、common/build.gradle.kts を編集します。settingsへのfrom指定は不要です。この標準ファイルをさらに手動importして二重登録しないでください。
05何を追加・変更する?
[versions]
commons-lang = "3.20.0"
junit = "5.13.4"
versions-plugin = "0.53.0"
[libraries]
commons-lang = { module = "org.apache.commons:commons-lang3", version.ref = "commons-lang" }
junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" }
junit-launcher = { module = "org.junit.platform:junit-platform-launcher" }
[plugins]
versions = { id = "com.github.ben-manes.versions", version.ref = "versions-plugin" }各buildの dependenciesブロックだけ を次へ置換します。Plugin・Toolchain・repositories・mainClass・test設定はこの章では残します。
app/build.gradle.ktsdependenciesだけ置換dependencies {
implementation(project(":common"))
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.launcher)
}dependencies {
implementation project(':common')
testImplementation platform(libs.junit.bom)
testImplementation libs.junit.jupiter
testRuntimeOnly libs.junit.launcher
}common/build.gradle.ktsdependenciesだけ置換dependencies {
implementation(libs.commons.lang)
}dependencies {
implementation libs.commons.lang
}外部Pluginの版も管理するとき
更新候補を調べるVersions Pluginを例にします。カタログの[plugins]へ書くだけでは適用されません。試す場合は次をルートbuildのimportsの後・GenerateInfoクラスの前へ追加し、tasks --allでdependencyUpdatesの登録を確認します。依存の更新調査自体はこの演習では実行不要です。確認後はこのpluginsブロックだけ取り除きます。カタログ内の登録は残して構いません。
build.gradle.kts任意の追加 / 既存Taskは消さないplugins {
alias(libs.plugins.versions)
}plugins {
alias(libs.plugins.versions)
}.\gradlew.bat tasks --all --no-configuration-cache外部Pluginの取得・適用後、追加Task名を確認します。Pluginの取得には通信が必要です。
Plugin Portalの0.53.0を演習用に固定します。これはGradle自体の更新とは別です。
06実際に動かして確認する
.\gradlew.bat :app:build --no-configuration-cache
.\gradlew.bat :common:dependencies --configuration runtimeClasspath --no-configuration-cacheビルド・テストが変わらず成功することと、commonの実行時依存にCommons Lang 3.20.0があることを確認します。保存後、EclipseでもGradle Refreshしてください。
07何が変わった?
参照元はlibsへ変わりましたが、使うライブラリは同じです。commons-lang は libs.commons.lang のような参照になります。JUnitの版の整合は引き続きBOMが担当します。
Catalogの変更場所と利用側のlibs参照を結びつけ、ビルド・依存一覧で移行前と同じ結果を確認できる。
公式資料:Version Catalogs
ADVANCED / 同じビルドを育てる
複数プロジェクトの共通設定を整理したい
01今回やりたいこと
appとcommonに重複するrepositoriesとToolchainをまとめたい。テストの起動方式とコンパイル文字コードも、Javaプロジェクトの共通ルールとして管理します。
02なぜ今の状態では困る?
同じ設定をコピーし続けると、一方だけJDKの版が変わるなどのずれが生まれます。一方、appのmainClassとcommonのライブラリ依存は、それぞれに残すべきです。
03どういう仕組みで解決する?
Convention Pluginは、チームで使う設定の約束をPluginとしてまとめたものです。適用したProjectだけがルールを受け取ります。今回はJava開発者向けに、両DSLから同じJavaクラスを使う小さなPluginにします。
04どのファイルを変更する?
| ファイル | 役割 |
|---|---|
settings.gradle.kts | Pluginを探す場所としてbuild-logicを登録 |
build-logic/settings.gradle.kts | 共通設定を作る別ビルドの名前 |
build-logic/build.gradle.kts | Pluginをビルドし、IDと実装クラスを対応付ける |
build-logic/src/main/java/guide/JavaConventionsPlugin.java | 共通ルールの実装。両DSLで同じJavaコード |
app/build.gradle.kts / common/build.gradle.kts | Pluginを適用し、各自の固有設定を残す |
build-logicはアプリのsubprojectにはしません。includeBuild でPluginを提供する別ビルドとして参加させます。なぜsettingsが別にあるのかは、第6章で同じ構成を使って確かめます。
05何を追加・変更する?
settings.gradle.ktsルートsettingsを全文置換 / pluginManagementは先頭pluginManagement {
includeBuild("build-logic")
}
rootProject.name = "AdvancedSampleSystem"
include("app", "common")pluginManagement {
includeBuild 'build-logic'
}
rootProject.name = 'AdvancedSampleSystem'
include 'app', 'common'build-logic/settings.gradle.ktsフォルダーとファイルを新規作成rootProject.name = "build-logic"rootProject.name = 'build-logic'build-logic/build.gradle.kts新規作成plugins { id("java-gradle-plugin") }
java { toolchain { languageVersion = JavaLanguageVersion.of(25) } }
gradlePlugin {
plugins {
create("javaConventions") {
id = "guide.java-conventions"
implementationClass = "guide.JavaConventionsPlugin"
}
}
}plugins { id 'java-gradle-plugin' }
java { toolchain { languageVersion = JavaLanguageVersion.of(25) } }
gradlePlugin {
plugins {
javaConventions {
id = 'guide.java-conventions'
implementationClass = 'guide.JavaConventionsPlugin'
}
}
}package guide;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.testing.Test;
import org.gradle.jvm.toolchain.JavaLanguageVersion;
public class JavaConventionsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPluginManager().apply("java");
project.getRepositories().mavenCentral();
project.getExtensions().getByType(JavaPluginExtension.class)
.getToolchain().getLanguageVersion()
.set(JavaLanguageVersion.of(25));
project.getTasks().withType(Test.class)
.configureEach(test -> test.useJUnitPlatform());
project.getTasks().withType(JavaCompile.class)
.configureEach(task -> task.getOptions().setEncoding("UTF-8"));
}
}java-gradle-plugin がPlugin作成に必要なGradle APIと登録情報の生成を用意します。applyは設定時に呼ばれます。configureEachは、その型のTaskが必要になったときに設定を加えます。ここにTask実行時のファイル生成は書きません。
app/build.gradle.kts全文置換 / mainClass・appの依存はここに残すplugins {
application
id("guide.java-conventions")
}
version = "1.0"
application { mainClass = "guide.Greeting" }
dependencies {
implementation(project(":common"))
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.launcher)
}plugins {
id 'application'
id 'guide.java-conventions'
}
version = '1.0'
application { mainClass = 'guide.Greeting' }
dependencies {
implementation project(':common')
testImplementation platform(libs.junit.bom)
testImplementation libs.junit.jupiter
testRuntimeOnly libs.junit.launcher
}common/build.gradle.kts全文置換 / commonの依存はここに残すplugins {
id("java-library")
id("guide.java-conventions")
}
dependencies {
implementation(libs.commons.lang)
}plugins {
id 'java-library'
id 'guide.java-conventions'
}
dependencies {
implementation libs.commons.lang
}06実際に動かして確認する
.\gradlew.bat :app:build :app:run --no-configuration-cache共通Pluginが先にビルドされ、app/commonへ適用されます。appのテスト1件成功とHello, Gradle!を確認します。build-logic/build/libs/にはPluginのJARができます。
EclipseはルートAdvancedSampleSystemを開いたまま保存・Gradle Refreshします。build-logic内のファイルをIDEから編集できない場合はOSのエクスプローラーから開けます。Buildshipの版や表示方法により、included buildの見え方は異なります。Wrapperでの成功を先に確認してください。
07何が変わった?
repositories・Toolchain・JUnit Platformの使用・UTF-8は一か所へ移りました。JUnitの依存自体はテストを持つappに残し、Catalogも引き続きapp/commonで使います。build-logicへCatalogを自動継承したとは説明しません。
build-logic自身のToolchainと共通ルールは別
build-logic/buildのJava 25はPluginのJavaコードをコンパイルするための指定です。Plugin内のJava 25は適用先のapp/common用です。同じ数字でも対象が違います。build-logicは独立ビルドなので、ルートのCatalog・repositories等が自動共有されるわけではありません。
subprojects { }でまとめる方式との違い
親から子の設定をまとめて変更すると、各子のファイルだけでは適用ルールが見えにくくなります。ここでは各Projectのpluginsに共通Pluginを明記して、適用範囲を分かりやすくします。precompiled script pluginという別の実装方法もありますが、この演習に重ねて追加する必要はありません。
共通設定の変更先と各Project固有の変更先を説明でき、両Projectのビルド・appのテストと起動が成功する。
ADVANCED / 同じビルドを育てる
Gradleのビルドを速くしたい
01今回やりたいこと
同じbuildを2回実行すると、なぜ2回目は速いことがあるのか。時間だけでなく「どの仕事を省略・再利用したか」をログから説明できるようにします。
02なぜ今の状態では困る?
何でもキャッシュと呼ぶと、出力が残っていて仕事を省いたのか、消えた出力を復元したのか、設定そのものを再利用したのかが分かりません。
03どういう仕組みで解決する?
| 仕組み | 省略・再利用する対象 | 今回見るもの |
|---|---|---|
| Up-to-date Check | 以前の入力と現在の出力を比較し、変更のないTaskの仕事を省略 | UP-TO-DATE |
| Incremental Build | 変更の影響がある仕事を中心に処理。対応Taskは内部の変更部分だけを処理できる | Java変更後に必要なTaskが再実行 |
| Build Cache | 同じ入力・実装等に対応するTask出力をキャッシュから復元 | FROM-CACHE |
| Configuration Cache | Task実行のための設定結果を保存・再利用 | Reusing configuration cache. |
| Gradle Daemon | 常駐するJVMを再利用し、起動やウォームアップの負担を減らす | --statusでプロセス状態 |
REUSE LAB / 条件を変える概念比較
@CacheableTask対応済み、他の条件・強制再実行なしという想定。実際のTaskログではありません。
SKIPPED はonlyIfなどの条件で実行しない場合の表示で、キャッシュ復元という意味ではありません。NO-SOURCE は処理対象のソースがない場合です。どの省略も同じ理由ではありません。
04どのファイルを変更する?
まずファイル変更なしで比較し、次に common/src/main/java/guide/NameFormatter.java を一時変更します。その後、ルート build.gradle.kts のGenerateInfoへキャッシュ対応を1行加えます。gradle.propertiesの既存値はこの実験では変えず、コマンドのフラグで条件を明示します。
05何を追加・変更する?
- まず次のbuildを2回実行し、2回目のUP-TO-DATEを記録します。
- NameFormatterの
StringUtils.capitalize(name)をStringUtils.capitalize(name.trim())に変更して保存します。前後の空白を除く実装変更で、既存のgradleという入力ではテスト結果は変わりません。 - もう一度同じbuildを実行し、commonのcompileJavaと後続Taskを観察します。確認後は元の式へ戻して保存し、もう一度buildします。
.\gradlew.bat build --no-configuration-cache --no-build-cache
.\gradlew.bat build --no-configuration-cache --no-build-cache設定再利用・出力キャッシュを無効にして、入力と手元の出力だけによる比較を観察します。ソース変更後・復旧後にもこのコマンドを使います。ルートからのbuildはapp/commonのビルドを選択します。
次に、GenerateInfoの宣言の直前へ @org.gradle.api.tasks.CacheableTask を追加します。以下はクラス宣言の先頭だけです。クラスの中身・imports・Task登録は消さずに残してください。
build.gradle.ktsこの2行は宣言部分の抜粋 / 全文置換しない@org.gradle.api.tasks.CacheableTask
abstract class GenerateInfo : DefaultTask() {@org.gradle.api.tasks.CacheableTask
abstract class GenerateInfo extends DefaultTask {同じ入力なら同じUTF-8の内容になること、出力に影響するmessageが@Inputであることを確認したうえで付けます。外部サービスの更新など、副作用を起こす仕事を安易にキャッシュ対象へ変えないでください。
06実際に動かして確認する
.\gradlew.bat generateInfo -Pmessage=cache --build-cache --no-configuration-cache
Remove-Item -LiteralPath ".\build\info\build-info.properties"
.\gradlew.bat generateInfo -Pmessage=cache --build-cache --no-configuration-cache1回目で結果をローカルBuild Cacheへ保存し、生成ファイルだけを削除。2回目にFROM-CACHEとファイルの復元を確認します。既に同じ結果があれば1回目から復元される場合もあります。--build-cacheは対象Taskの出力キャッシュを有効にします。
.\gradlew.bat build generateInfo --configuration-cache --build-cache
.\gradlew.bat build generateInfo --configuration-cache --build-cache今度は設定結果の再利用も有効にし、同じTask・同じ条件で2回実行します。初回の保存、次回のReusing configuration cache.を探します。設定を再利用していても、Task出力の判定は別に行われます。
.\gradlew.bat --status利用しているGradle版のDaemonの状態を表示します。キャッシュを消したり、ビルドしたりするコマンドではありません。
毎回フラグを書かずに有効にするなら
ルートgradle.propertiesの既存値を確認し、重複行を作らず編集します。この演習では必須ではありません。
org.gradle.caching=true
org.gradle.configuration-cache=trueConfiguration Cache非対応のPluginやTaskがある場合は、問題レポートの該当箇所を直します。単に警告を隠して対応済みにしないでください。BuildshipのImport・同期が、このCLIの設定キャッシュを使うと考えないことも大切です。
07何が変わった?
短いビルドではキャッシュの保存・復元自体の負担もあります。すべてのTaskが対応するわけでも、必ず速くなるわけでもありません。今回は秒数の優劣を結論にせず、ログと出力で各仕組みの動作を確かめます。
Javaソース変更後のログの読み方
commonのコンパイルは必要になりますが、変更の内容やABIによってappの再コンパイルが不要と判断されることもあります。「常に全Taskが再実行」とは決めつけず、実際のTask結果を比べます。インクリメンタルな内部処理も、Taskがその機能を実装している場合に限られます。
UP-TO-DATE・FROM-CACHE・設定キャッシュ再利用を別の意味として説明でき、NameFormatterを元に戻しておく。
公式資料:Incremental Build / Build Cache / Configuration Cache / Configuration Cache Requirements / Gradle Daemon
ADVANCED / 同じビルドを育てる
別々のGradleビルドを組み合わせたい
01今回やりたいこと
第4章で増えたbuild-logicは、なぜapp/commonと扱いが違うのか。同じ構成を使い、別ビルドを独立して確認したい。
02なぜ今の状態では困る?
同じディレクトリの中に置いてあっても、同じビルドとは限りません。includeとincludeBuildを混ぜると、参照できるProjectや使われる設定の境界を誤解します。
03どういう仕組みで解決する?
BUILD MAP / 構成の比較
| Multi-project | Composite | |
|---|---|---|
| 境界 | 一つのsettingsで複数Project | 別々のsettingsを持つ複数ビルド |
| 登録 | include("app", "common") | includeBuild("build-logic") |
| 設定の共有 | 親子でも自動継承ではない | 別ビルドのCatalog・properties等は独立 |
| この演習の用途 | appがcommonのコードを利用 | アプリ側が共通Pluginを利用 |
04どのファイルを変更する?
第4章の settings.gradle.kts と build-logic/settings.gradle.kts を開きます。新しいビルドやフォルダーは追加しません。この章では登録済みのincludeBuildを使い、同じコードを別の入口から実行します。
05何を追加・変更する?
ルートsettingsの先頭が pluginManagement { includeBuild("build-logic") } であることを確認します。Groovyでは第4章の表示どおりの構文を使います。Pluginの解決に使うincluded buildなので、このブロックに置いています。app/commonのincludeはその外に残します。
build-logicのsettingsは名前をbuild-logicにするだけでした。ここにはinclude("app", "common")も、アプリ側のlibsもありません。Pluginを作るビルドにアプリの構成を混ぜないでください。
06実際に動かして確認する
.\gradlew.bat projects --no-configuration-cache
.\gradlew.bat -p build-logic tasks --all --no-configuration-cache
.\gradlew.bat :build-logic:build --no-configuration-cache1行目はアプリ側の構成を表示します。app/commonのProjectと、Included buildsのbuild-logicを区別します。2行目の-pは、Wrapperを置いたルートに居たまま、対象ビルドの開始ディレクトリをbuild-logicへ切り替えます。3行目はComposite側からincluded buildのbuild Taskを指定します。いずれもアプリのrunではありません。
| 確かめる箇所 | 読み取れること |
|---|---|
| projectsのIncluded builds | build-logicは別ビルドとして参加している |
| -p build-logicで表示するTask | Pluginのビルド・検証用Taskがある。appのrunとは別 |
| build-logic/build/ | PluginのJAR等が生成される。app/buildとは別 |
07何が変わった?
Composite Buildは独立したビルドを組み合わせます。今回はPluginを提供する形でした。一般のライブラリ開発では、別ビルドのgroup:nameに一致する外部依存を、手元のプロジェクトへ置き換える用途もあります。今回はそのために別の演習プロジェクトを増やしません。
buildSrcとは? 今回は作らない
buildSrc/ は、Gradleが特別に認識し、そのビルド向けのPluginやTaskなどを置ける場所です。明示的なincludeBuildなしで扱われます。build-logicは普通の名前の独立ビルドで、どこを取り込むかをincludeBuildで明示し、単独でビルドできます。用途は重なりますが、今回の共通Pluginを両方へ複製する必要はありません。
どちらもビルド用コードの置き場所です。アプリが実行時に使う共通ライブラリを置くcommonとは目的が違います。
app/commonとbuild-logicの境界、-pの対象、includeBuildとbuildSrcの役割を区別できる。
ADVANCED / 同じビルドを育てる
Gradleのバージョンを更新したい
01今回やりたいこと
Gradle 9.6.0から 9.8.1 へ更新します。日付は2026年10月11日。更新先は公式のリリースノートで利用可能な版を確認し、この教材の両DSLで実行検証する版に固定します。
02なぜ今の状態では困る?
WrapperのURLを変えて起動できても、使っているPluginや自作のビルド設定が動くとは限りません。変更する要素を分け、更新前後の成功条件をそろえる必要があります。
03どういう仕組みで解決する?
| 更新するもの | 今回の扱い |
|---|---|
| Gradle / Wrapper | 9.6.0から9.8.1へ。WrapperのTaskで更新 |
| Java | 25のまま。公式互換表ではGradle起動・Toolchainとも対応範囲 |
| 自作Plugin | 同じソースで再ビルドし、適用先もテスト |
| 第三者Plugin | 第3章の任意Pluginは取り除いた状態。残す場合は提供元の対応情報と個別テストが必要 |
| 依存ライブラリ | Catalogの版を変更しない |
9.8.1のリリースノート、公式の互換表、9.x更新ガイドで変更点を確認します。この演習の成功は、別のプロジェクトのPluginまで互換である保証にはなりません。
04どのファイルを変更する?
元のAdvancedSampleSystemと同じ親フォルダーへ、空の AdvancedSampleSystemUpgrade を作ります。更新試験専用なので、元のフォルダーはそのまま残します。コピーするのは次のソースと設定です。
| コピー対象(同じ相対位置) | 含む内容 |
|---|---|
| 準備でコピーしたWrapper・settings・ルートbuild・gradle.properties | 今の第6章終了時点のもの |
| gradle/libs.versions.toml | 現在のCatalog |
| app / common | 各buildファイルとsrcだけ |
| build-logic | settings、buildファイル、srcだけ |
各階層のbuild/・.gradle/・Eclipseの.project/.classpath/.settings/は今回も除外します。コピー先のrootProject.nameは変えません。表示名が同じでも、作業場所がUpgradeであることをGet-Locationで確かめます。
05何を追加・変更する?
Set-Location 'C:\自分の練習用フォルダー\AdvancedSampleSystemUpgrade'
Get-Location
.\gradlew.bat --version
.\gradlew.bat build --warning-mode=all --no-configuration-cache現在位置と9.6.0を確認してから、更新前のビルド・テストを記録します。--warning-mode=allは非推奨を含む警告を個別に表示するオプションです。警告があれば更新ガイドで対象を確かめます。
.\gradlew.bat wrapper --gradle-version 9.8.1 --distribution-type bin --no-configuration-cache
.\gradlew.bat wrapper --no-configuration-cache1回目は現在のGradleで更新先を設定します。2回目は新しいGradleを取得・起動し、その版でWrapper本体と起動スクリプトも生成します。binは実行用の配布物です。新しいGradleの初回取得には通信が必要です。
変更対象は gradle/wrapper/gradle-wrapper.properties、gradle-wrapper.jar、gradlew、gradlew.bat の4ファイルです。propertiesのdistributionUrlに9.8.1が含まれることを確認します。
06実際に動かして確認する
.\gradlew.bat --version
.\gradlew.bat clean build --warning-mode=all --no-configuration-cache
.\gradlew.bat build generateInfo --configuration-cache
.\gradlew.bat build generateInfo --configuration-cache9.8.1の起動、appのテスト1件成功、JAR生成、設定キャッシュ再利用を更新前と比較します。cleanを付けるのは古い出力だけで成功したと判断しないためです。
0 / 6 確認(ページを閉じるとリセット)
07何が変わった?
更新専用コピーで同じテストが成功することを確認できました。問題があれば、そのコピーで調査し、元の9.6.0のフォルダーへ戻れば続きの演習ができます。実際のチームでは、Wrapperの4ファイルを同じ変更として版管理し、関連設定を直した場合も一緒にレビュー・切り戻しできるようにします。
Set-Location '..\AdvancedSampleSystem'
Get-Location
.\gradlew.bat --version第8章は元の演習コピーへ戻り、9.6.0と表示されることを確認します。更新専用コピーは比較用に残します。
更新前にハッシュ値の設定も確認する
gradle-wrapper.propertiesにdistributionSha256Sumを設定しているプロジェクトでは、配布物の版を変えたら、公式配布元が示す新しい版のSHA-256と整合させます。古い値をそのまま使ったり、検証を無効にして通したりせず、チームのWrapper検証手順に従ってください。
更新前後のGradle・Java・Plugin・テスト結果を分けて確認でき、9.6.0の元コピーへ戻っている。
公式資料:Gradle Wrapper / Compatibility / Upgrading 9.x
ADVANCED / 同じビルドを育てる
CI/CDと成果物の公開につなげたい
01今回やりたいこと
自分のPCで成功したビルドを、別のPCやCIでも再現したい。さらにcommonをMaven形式で渡せるよう、まずローカルの練習用リポジトリへ公開します。
02なぜ今の状態では困る?
IDEの手動設定やPC固有の出力だけに依存していると、クリーンな環境で再現できません。JARを置くだけでは、そのライブラリが必要とする依存情報も伝わりません。
03どういう仕組みで解決する?
CIはこの手順を自動で行い、終了コードが非0なら失敗として後続の公開を止めます。maven-publishはJARとPOM等のメタデータを、Maven形式の座標・配置で公開するPluginです。アプリを本番へデプロイする機能とは別です。
04どのファイルを変更する?
作業先は AdvancedSampleSystem(9.6.0) です。common/build.gradle.kts を編集します。出力先はルートの practice-repo/。外部サーバーも認証情報も使いません。CIサービス固有の設定ファイルは、この演習では作りません。
05何を追加・変更する?
common/build.gradle.kts既存のpluginsブロックだけ置換plugins {
id("java-library")
id("guide.java-conventions")
id("maven-publish")
}plugins {
id 'java-library'
id 'guide.java-conventions'
id 'maven-publish'
}dependenciesはそのまま残し、次をファイル末尾へ追加します。publicationの名前mavenJavaはGradle内の名前、公開する座標は guide.demo:common:1.0 です。
common/build.gradle.ktsdependenciesの外・ファイル末尾へ追加group = "guide.demo"
version = "1.0"
publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
}
}
repositories {
maven {
name = "practice"
url = uri(layout.projectDirectory.dir("../practice-repo"))
}
}
}group = 'guide.demo'
version = '1.0'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
maven {
name = 'practice'
url = uri(layout.projectDirectory.dir('../practice-repo'))
}
}
}| 指定 | 役割 |
|---|---|
| from(components["java"]) | Javaの成果物と依存情報を出版物へ含める |
| group / Project名 / version | guide.demo / common / 1.0という座標を決める |
| publishing.repositories | 公開先。dependenciesを取得するrepositoriesとは別 |
| ../practice-repo | commonのディレクトリから見て一つ上の練習用フォルダー |
06実際に動かして確認する
.\gradlew.bat clean build --no-daemon --no-configuration-cache
if ($LASTEXITCODE -ne 0) { throw "Build failed; stop before publish." }まずCIに近い入口としてWrapperでビルドします。PowerShellの$LASTEXITCODEで直前の外部コマンドの終了コードを確認し、失敗時はその場で止めます。--no-daemonは常駐プロセスを再利用しない指定で、単発のDaemonが起動する場合もあります。CIで必須の指定という意味ではありません。
.\gradlew.bat :common:publishMavenJavaPublicationToPracticeRepository
if ($LASTEXITCODE -ne 0) { throw "Publish failed." }
Get-ChildItem .\practice-repo\guide\demo\common\1.0\commonのmavenJava出版物を、practiceという名前のリポジトリへ公開するTaskです。前のビルドが成功した場合だけ実行します。JAR・POM・module等がこのフォルダーにできることを確認します。publish Task自体がテスト成功を保証するわけではありません。
| 確認・保存するもの | 場所 |
|---|---|
| テスト結果 | app/build/reports/tests/test/index.html |
| アプリの配布用アーカイブ | app/build/distributions/(依存JARと起動スクリプトを含む) |
| アプリのJAR | app/build/libs/app-1.0.jar(単体で全依存を含むわけではない) |
| 公開したcommon | practice-repo/guide/demo/common/1.0/common-1.0.jar |
| 依存情報 | 同じ場所のcommon-1.0.pom。Commons Langの依存を確認 |
CIでは成功した成果物と、失敗時にも調べられるテストレポート・ログを保存します。保存処理の設定名はサービスごとに異なります。JDK 25、Wrapperの4ファイル、Catalog、build-logicのソースも取得対象にし、必要な依存の取得先へアクセスできるよう準備します。
07何が変わった?
commonをJavaソースのProject間依存だけでなく、Maven形式の成果物として渡せるようになりました。練習用リポジトリはこのPCのフォルダーで、他のPCへ自動共有されたわけではありません。
実際のリポジトリへ公開するとき
公開先、上書き方針、アクセス権をチームで決めます。ユーザー名・パスワード・トークンはbuildファイルやCatalogへ書かず、CIのシークレット管理から環境変数等で渡します。ログにも値を表示しません。この教材は認証情報の入力や外部への公開を要求しません。
CDと再現性はどこまで?
CIは変更ごとのビルド・テストを自動化し、CDは検証済み成果物の配布・リリースへつなげる段階です。本番へのデプロイは今回実施しません。WrapperだけでJDK・OS・依存解決結果まで完全固定されるわけではなく、必要に応じて環境固定や依存ロック等を別途検討します。
ビルド失敗時に公開を止める意味を説明でき、ローカルMavenリポジトリのJARとPOMを確認できる。
公式資料:Maven Publish / Gradle Wrapper / Gradle Daemon
公式資料と、この教材の条件
確認日:2026年10月11日。本文の演習は9.6.0で検証し、更新章だけ9.8.1を使います。公式資料のcurrentは参照時9.8.1で、今後更新されます。新しい版だけのAPIを演習へ混ぜないよう、掲載コードの実行結果も確認しています。
実行した検証と、未検証事項(2026年10月11日)
Windows / JDK 25 / Gradle 9.6.0でKotlin・Groovy両方の第1〜8章の主要演習を実行しました。Task登録・設定の表示、入力変更と出力削除、Catalog、共通Plugin、UP-TO-DATE、FROM-CACHE、Configuration Cache再利用、included buildの実行を確認しています。
更新専用コピーではWrapperを9.8.1へ更新し、build・テスト・設定キャッシュ再利用を確認しました。9.6.0の元コピーではローカルMavenリポジトリへのJAR・POMの生成を確認しました。両DSLともappのテストは1件・失敗0件です。第3章の任意Pluginは取得・適用とTask登録を確認し、依存の更新調査Taskは実行していません。
未実施:Eclipse / Buildshipの実機Import・Refresh、macOS/Linuxでのコマンド実行、実際のCIサービス、本番デプロイ、外部リポジトリ公開。ブラウザ内の操作型図は概念例で、Gradleを実行するものではありません。手元の環境やキャッシュ状態によりログ・時間は変わります。
教材本文・図・UIはこのadvanced.htmlに内蔵されています。公式資料の参照と演習に必要な初回取得はオンラインです。基礎編へのリンクを使う場合だけ、index.htmlと同じフォルダーに置いてください。
印刷前に補足と構成比較を展開し、印刷後に元へ戻します。コードは選択中のDSLで印刷します。