Dev Study
← 解説「Gradle: タスクの依存関係と、変更が無ければ飛ばす仕組み」に戻る

サンプルコードで身につける: Gradle: タスクの依存関係と、変更が無ければ飛ばす仕組み

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1実行せずに順番だけ見る: build --dry-run と help --task

(-m)は「all task actions disabled」で走り、どのがどの順で実行されるはずだったかを SKIPPED 付きで出します。雛形の build は13行でした。 は、そのタスクを登録した、型、説明を出します。bootJar は プラグインが登録した BootJar 型で、build は LifecycleBasePlugin の素の Task 型(自分では何もしない束ね役)です。

$ ./gradlew --console=plain build --dry-run / $ ./gradlew -q help --task bootJar / build(Gradle 9.7.1)コマンド出力
:compileJava SKIPPED
:processResources SKIPPED
:classes SKIPPED
:resolveMainClassName SKIPPED
:bootJar SKIPPED
:jar SKIPPED
:assemble SKIPPED
:compileTestJava SKIPPED
:processTestResources SKIPPED
:testClasses SKIPPED
:test SKIPPED
:check SKIPPED
:build SKIPPED
BUILD SUCCESSFUL in 349ms
 
$ ./gradlew -q help --task bootJar
Detailed task information for bootJar
Path
:bootJar (registered by plugin 'org.springframework.boot')
Type
BootJar (org.springframework.boot.gradle.tasks.bundling.BootJar)
Options
--rerun Causes the task to be re-run even if up-to-date.
Description
Assembles an executable jar archive containing the main classes and their dependencies.
Group
build
 
$ ./gradlew -q help --task build
Detailed task information for build
Path
:build (registered by plugin class 'org.gradle.language.base.plugins.LifecycleBasePlugin')
Type
Task (org.gradle.api.Task)
...
Description
Assembles and tests this project.

22回目は UP-TO-DATE、1ファイル変えると必要な所だけ再実行

何も変えずに build すると、13行のうち12行が ( はテスト用が無いので )で、7 actionable tasks: 7 up-to-date でした。HelloController.java の文字列を1か所変えると compileJava から再実行され、processResources と compileTestJava は UP-TO-DATE のままです(入力が変わっていないので)。test は、テストのソースは同じでも上の が変わったので再実行されました。 を付けると、その理由が Input property ... has changed. の形で出ます(3つ目は別の1か所変更で --info を付けた回の抜粋です)。

$ ./gradlew --console=plain build(変更なし → HelloController.java を1か所変更 → 別の1か所変更に --info を付けた回)コマンド出力
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :resolveMainClassName UP-TO-DATE
> Task :bootJar UP-TO-DATE
> Task :jar UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test UP-TO-DATE
> Task :check UP-TO-DATE
> Task :build UP-TO-DATE
BUILD SUCCESSFUL in 343ms
7 actionable tasks: 7 up-to-date
 
$ sed -i 's/"hello")/"hello2")/' src/main/java/com/example/demo/HelloController.java; ./gradlew --console=plain build
> Task :compileJava
> Task :processResources UP-TO-DATE
> Task :classes
> Task :resolveMainClassName
> Task :bootJar
> Task :jar
> Task :assemble
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test
> Task :check
> Task :build
BUILD SUCCESSFUL in 1s
7 actionable tasks: 5 executed, 2 up-to-date
 
$ sed -i 's/"hello")/"hello3")/' src/main/java/com/example/demo/HelloController.java; ./gradlew --console=plain build --info | grep -i 'is not up-to-date\|Input property\|has changed\|Task :compileJava\|Task :bootJar\|Task :test\|Task :jar\|Task :processResources'
> Task :compileJava
Task ':compileJava' is not up-to-date because:
Input property 'stableSources' file /work/groovy/src/main/java/com/example/demo/HelloController.java has changed.
> Task :processResources UP-TO-DATE
...
> Task :bootJar
Task ':bootJar' is not up-to-date because:
Input property 'classpath' file /work/groovy/build/classes/java/main/com/example/demo/HelloController.class has changed.
...
> Task :test
Task ':test' is not up-to-date because:
Input property 'stableClasspath' file /work/groovy/build/classes/java/main/com/example/demo/HelloController.class has changed.

3clean しても戻ってくる FROM-CACHE と、構成の再利用、デーモン

を付けると、タスクの出力が ~/.gradle/caches/build-cache-1 にも保存されます。 で を消してからもう一度 clean build --build-cache を打つと、compileJava・compileTestJava・test が実行されずに で戻りました(bootJar・・processResources は実行されました。この違いの理由は文書で確かめていません)。 は2回目に Reusing configuration cache. と出ます。は で見え、 で止まります。

$ ./gradlew clean build --build-cache(2回目)/ --configuration-cache(1回目と2回目)/ --status と --stopコマンド出力
> Task :clean
> Task :compileJava FROM-CACHE
> Task :processResources
> Task :classes
> Task :resolveMainClassName
> Task :bootJar
> Task :jar
> Task :assemble
> Task :compileTestJava FROM-CACHE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test FROM-CACHE
> Task :check UP-TO-DATE
> Task :build
BUILD SUCCESSFUL in 413ms
8 actionable tasks: 5 executed, 3 from cache
 
$ ./gradlew --console=plain build --configuration-cache # 1回目
Calculating task graph as no cached configuration is available for tasks: build
...
Configuration cache entry stored.
$ ./gradlew --console=plain build --configuration-cache # 2回目
Reusing configuration cache.
...
Configuration cache entry reused.
 
$ ./gradlew --status
PID STATUS INFO
117 IDLE 9.7.1
$ ./gradlew --stop
Stopping Daemon(s)
1 Daemon stopped
$ ./gradlew --status
No Gradle daemons are running.
PID STATUS INFO
117 STOPPED (stop command received)
...

4build/ に出来たものと、2つの JAR の違い

build/ の中身です。classes/java/main は の に当たりますが、java/main の2段が深くなっています。libs には が2つあり、 は jar タスクが作る自分の2個とリソース( と空の )だけのもの(2,065 B)で、も Manifest-Version の1行だけです。demo-0.0.1-SNAPSHOT.jar は bootJar が作った で、マニフェストに と があり、 に の34本が入っています。

$ find build -type f -not -path 'build/tmp/*' | sort / $ ls -l build/libs / 2つの JAR のマニフェストコマンド出力
build/classes/java/main/com/example/demo/DemoApplication.class
build/classes/java/main/com/example/demo/HelloController.class
build/classes/java/test/com/example/demo/DemoApplicationTests.class
build/libs/demo-0.0.1-SNAPSHOT.jar
build/libs/demo-0.0.1-SNAPSHOT-plain.jar
build/reports/tests/test/com.example.demo.DemoApplicationTests/contextLoads().html
build/reports/tests/test/com.example.demo.DemoApplicationTests/index.html
...
build/reports/tests/test/index.html
...
build/resolvedMainClassName
build/resources/main/application.properties
...
build/test-results/test/TEST-com.example.demo.DemoApplicationTests.xml
 
$ ls -l build/libs
-rw-r--r-- 1 root root 2065 Oct 3 00:31 demo-0.0.1-SNAPSHOT-plain.jar
-rw-r--r-- 1 root root 19903503 Oct 3 00:31 demo-0.0.1-SNAPSHOT.jar
 
$ unzip -p build/libs/demo-0.0.1-SNAPSHOT-plain.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
 
$ unzip -p build/libs/demo-0.0.1-SNAPSHOT.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.example.demo.DemoApplication
Spring-Boot-Version: 4.1.1
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Build-Jdk-Spec: 25
Implementation-Title: demo
Implementation-Version: 0.0.1-SNAPSHOT

5よくある誤解: touch すれば再ビルドされる、コメントだけなら何も起きない

どちらも では成り立ちません。touch で更新時刻だけ変えても、入力の内容が同じなので UP-TO-DATE のままでした(NO-SOURCE の1行を除く12行)。逆に、クラスの前にコメントを1行足しただけでも compileJava が走り、行番号が変わって .class の中身が変わるので、bootJar・jar・test まで再実行されました。sed で足したコメントの文言は実験前の予想で、結果はその逆です。対になるのが の タスクで、更新時刻で判定するので touch だけで再コンパイルします。

$ touch ... ; ./gradlew build(内容そのまま)/ コメント1行を足して ./gradlew buildコマンド出力
$ touch src/main/java/com/example/demo/HelloController.java src/main/resources/application.properties; ./gradlew --console=plain build
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
...(残りも UP-TO-DATE。processTestResources だけ NO-SOURCE)
7 actionable tasks: 7 up-to-date
 
$ sed -i 's|^@RestController|// a comment that does not change the bytecode\n@RestController|' src/main/java/com/example/demo/HelloController.java; ./gradlew --console=plain build
# コメントの文言は実験前の予想。実際は行番号がずれて .class が変わり、bootJar・test まで再実行された
> Task :compileJava
> Task :processResources UP-TO-DATE
> Task :classes
> Task :resolveMainClassName
> Task :bootJar
> Task :jar
> Task :assemble
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test
> Task :check
> Task :build
BUILD SUCCESSFUL in 1s
7 actionable tasks: 5 executed, 2 up-to-date
 
# 対照(Ant 1.10.18、別のレッスンの最小 build.xml): touch の後の ant は
# [javac] Compiling 1 source file to ... と再コンパイルした(更新時刻で判定)

図やコードに出てくる用語(タップで説明)