読者です 読者をやめる 読者になる 読者になる

Gradle1.6の新機能mustRunAfterでタスク間の実行順序が制御可能になった #gradle

Gradleではタスク間の依存関係の指定ができます。

build.gradle:

task a << {
    println "A"
}
task b(dependsOn: "a") << {
    println "B"
}
task c(dependsOn:"a") << {
    println "C"
}
task d(dependsOn: ["b", "c"]) << {
    println "D"
}

実行結果:

$ gradle d
:a
A
:b
B
:c
C
:d
D

BUILD SUCCESSFUL

Total time: 1.612 secs

実はこの例のBとCの間の関係は不定であり、実装に基づいて(この場合はアルファベット順だったりしたはず)実行順序が決められます。 ここではA→B→C→Dになっていますね。

しかし、このようなグラフ的には一見どっちがさきに実行されても良いようにみえるBとCの間に、実は微妙な前後関係があって、Cの方が先に実行されて欲しい!というケースは意外とありますよね。

長い間Gradleユーザから臨まれていたこの機能がついに1.6で実装されました(ということをさっき知った)!

Task#mustRunAfter()の使い方

以下のように使います。

build.gradle:

task a << {
    println "A"
}
task b(dependsOn: "a"/*, mustRunAfter: "c" -- 残念ながら今のところここには書けないらしい*/) {
    mustRunAfter "c" // メソッド呼出として書く
    doLast {
        println "B"
    }
}
task c(dependsOn:"a") << {
    println "C"
}
task d(dependsOn: ["b", "c"]) << {
    println "D"
}

と書くと、こうなります:

$ gradle d
:a
A
:c
C
:b
B
:d
D

BUILD SUCCESSFUL

Total time: 1.64 secs

dependsOnとの違い

実は単にA→C→B→Dとしたいだけであれば、↓と書くだけでもOKです。

task a << {
    println "A"
}
task b(dependsOn: ["a", "c"]) << {
    println "B"
}
task c(dependsOn:"a") << {
    println "C"
}
task d(dependsOn: ["b", "c"]) << {
    println "D"
}

mustRunAfterとの違いは、タスクbを実行してみると見えてきます。 一つ前のサンプルだと:

$ gradle b
:a
A
:c
C
:b
B

BUILD SUCCESSFUL

Total time: 0.723 secs

となります。 bの依存先にcが入ってるので実行タスクのグラフにcが登場するのですね。

さて、mustRunAfterを使った先ほどと同じこの設定で実行してみると...

task a << {
    println "A"
}
task b(dependsOn: "a") {
    mustRunAfter "c"
    doLast {
        println "B"
    }
}
task c(dependsOn:"a") << {
    println "C"
}
task d(dependsOn: ["b", "c"]) << {
    println "D"
}

実行結果:

$ gradle b
:a
A
:b
B

BUILD SUCCESSFUL

Total time: 1.745 secs

となります。 mustRunAfterは依存関係の指定ではなく、実行タスクのグラフ上にそのタスクが存在した場合に単にその順序関係を指定しているだけなので、この場合にはタスクcは登場しないのです!

実際どういう場面で使うの?

ふもさんのサンプルがわかりやすそうです。

task clean << {
  println 'cleaning.'
}
task build << {
  println 'building.'
}
build.doLast {
  println 'finish!'
}
task local(dependsOn: [clean, build]) << {
  println 'local debug.'
}

これを実行するとこうなります:

$ gradle local
:build
building.
finish!
:clean
cleaning.
:local
local debug.

BUILD SUCCESSFUL

Total time: 1.638 secs

buildしてからcleanするとかso crazyですね。ちょっと何言ってるか分からないです。

ここで実行順序制御にdependsOnを使おうとすると、

task clean << {
  println 'cleaning.'
}
task build(dependsOn: 'clean') << {
  println 'building.'
}
build.doLast {
  println 'finish!'
}
task local(dependsOn: [clean, build]) << {
  println 'local debug.'
}

実行結果:

$ gradle local
:clean
cleaning.
:build
building.
finish!
:local
local debug.

BUILD SUCCESSFUL

Total time: 1.605 secs

うまくいった!?ようにみえますが、cleanせずに差分ビルドさせようとbuildタスクを指定すると...

$ gradle build
:clean
cleaning.
:build
building.
finish!

BUILD SUCCESSFUL

Total time: 0.755 secs

cleanまで発動してしまいました!!これでは毎回フルビルドになってしまい、開発速度が上がりません。

そこで、mustRunAfterの出番です。

build.gradle:

task clean << {
  println 'cleaning.'
}
task build {
  mustRunAfter 'clean'
  doLast {
    println 'building.'
  }
}
build.doLast {
  println 'finish!'
}
task local(dependsOn: [clean, build]) << {
  println 'local debug.'
}

実行結果:

$ gradle local
:clean
cleaning.
:build
building.
finish!
:local
local debug.

BUILD SUCCESSFUL

Total time: 0.722 secs
$ gradle build
:build
building.
finish!

BUILD SUCCESSFUL

Total time: 1.58 secs

素晴らしい!期待通りの結果です。

というわけで、依存関係のdependsOnと、実行順序の微調整のmustRunAfterをうまく使い分けましょう。