日々常々

ふつうのプログラマがあたりまえにしたいこと。

Gradleのタスクは順番ちゃんとしよう

私が雰囲気でGradleを使ってる感がよくわかる。

  • Gradle 6.0.1 と 5.6.4

前置き

とあるタスクを実行するときに clean してコンパイルしてから実行したくて、こんなこと書いてました。

myTask.dependsOn(clean, compileJava, processResources)

opengl-8080さんのQiitaに書いてあるし、たぶんGradle徹底入門にもあったかとおもうけど、dependsOnの引数は実行順を制御しません。 Gradleで実行順を制御したかったら mustRunAftershouldRunAfter を併用するものです。

でもこれで今まで特に問題になったことなかったんですよね……。

起こした問題と原因

なんでこんなことを今更書いてるのかと言うと、以下のように書き直したら一部の環境で動かなくなりました。正確に言うとMacのターミナル上でコマンド叩いた時だけ動きました。

classes.dependsOn(clean)
myTask.dependsOn(classes)

ここでGradleのThe Java Pluginの図を引用。

f:id:irof:20191213161855p:plain

compileJavaprocessResourcesclasses が依存してるからこれで良いかーって。

f:id:irof:20191213163141p:plain

まあダメなんですけどね。clean がいつ実行されるか運に任せ……実際は何かで決まるんだろけど詳細は追っていません。ログ眺めてると個々のタスクの依存関係みながら何度も並び替えしてるっぽくて、結果をみると環境依存でした。

でこれが、CIでコンパイルが落ちる。 ./gradlew build を手元で叩いたら問題なくコンパイルできる。 そしてIntellJのビルド( ./gradlew classes testClasses 相当が実行される )は動かない……クラスあるのにクラスがないって怒られる。よくわからないから放置して寝た(せめて戻せ)。

で翌朝ログを眺めてたら「なんか clean 動くタイミングがおかしいな?」と気づきました。 やっぱ深夜の脳はなんかダメだし、寝起きの脳はとても役立ちますね。

CircleCIでは compileJava -> clean -> processResources -> classes -...-> compileTestJava の順で動いてました。Appveyorはまた別の順番。 IntelliJのビルドは compileJava -> processResources -> clean -> classes -...-> compileTestJava となっており、そりゃクラス(*.javaファイル)あってもクラス(*.class)がコンパイル後に消されてるんだからビルドできないよね。

なお手元の ./gradlew buildclean が一番最初に動いてました。そりゃ動く。

潜在してた問題

ていうか、元のもこうだったわけで。

f:id:irof:20191213165441p:plain

clean がいつ動くかは誰にもわからない。 よく動いてたな?

結局どうしたか

[compileJava, processResources]*.mustRunAfter(clean)
myTask.dependsOn(clean, classes)

f:id:irof:20191213165052p:plain

clean が途中で動いて欲しいことってないし。 classes から mustRunAfter しても意味ないし、並べるしかない気がしたからこんな感じ。

compileJava.dependsOn(clean) とかにしないのは、いつも clean したいかってとそうでもないから避けました。

あと、GradleのIssueに gradle clean build can run clean after tasks that produce output by depending on other tasks #2488 とかありました。問題になるのはmulti-projectの時だけなのかな。