Gradle のタスクについて、基礎から応用までいろいろメモ。
前提知識
- Groovyを知らない人のためのbuild.gradle読み書き入門 - Qiita に書いていること
- Groovy の基本知識
- 各種シンタックスシュガーとか基本文法とか
環境
Gradle
5.0
Java
openjdk 11.0.1
OS
Windows 10
タスクの実体
Gradle でタスクを定義するには、次のように記述する。
task foo
この foo
タスクの定義は、 Project の task(String) メソッドを呼び出している。
task()
メソッドは、生成されたタスクを表すオブジェクトを返すので、次のようにして foo
タスクのオブジェクトを取得できる。
def foo = task foo
println(foo)
println(foo.class)
> gradle foo
task ':foo'
class org.gradle.api.DefaultTask_Decorated
Gradle のタスクの実体は Task のオブジェクトで、単純に task()
で生成した場合は DefaultTask のオブジェクトが生成される。
Action
タスクは、内部に Action のリストを保持している。
この Action
は、そのタスクで実行する具体的な処理を持っている。
そしてタスクを起動すると、リストの先頭から順番に Action
が実行されていく。
Action
は、 Task
が持つ doFirst()
, doLast()
メソッドでリストの先頭と末尾のいずれかに追加できる。
def foo = task foo
foo.doFirst {
println "two"
}
foo.doFirst {
println "one"
}
foo.doLast {
println "three"
}
foo.doLast {
println "four"
}
> gradle foo
one
two
three
four
doFirst()
はリストの先頭に Action
を追加し、 doLast()
は末尾に Action
を追加する。
上記例では doFirst()
を二回実行している。
この場合、あとで実行した "one"
を出力する Action
のほうがリストの先頭に挿入されることになるので、 "one" -> "two"
の順番で出力されている。
設定ブロックで定義する
task foo {
println(delegate)
println(delegate.class)
doFirst {
println("FOO!!")
}
}
> gradle foo
> Configure project :
task ':foo'
class org.gradle.api.DefaultTask_Decorated
> Task :foo
FOO!!
task()
メソッドでタスクを定義するときに、第二引数にクロージャを渡すことができる。
このクロージャの delegate
は、作成されたタスクオブジェクトとなっている。
したがって、クロージャ内では作成したタスクのメソッドやプロパティに暗黙的にアクセスできるようになっている。
ビルドライフサイクル
Gradle の実行は、大きく3つのフェーズに分かれている。
フェーズ | 内容 |
---|---|
初期化フェーズ (Initialization) |
プロジェクトがシングルかマルチかなどを判定し、 Project のオブジェクトを生成する。 |
設定フェーズ (Configuration) |
ビルドスクリプトを実行し、 project オブジェクトを構築していく。 |
実行フェーズ (Execution) |
コマンドラインで指定されたタスクを実際に実行する。 |
具体的に build.gradle
の中でいうと、タスクに設定している Action
の中が実行フェーズで実行され、それ以外は設定フェーズで実行される。
println("設定フェーズ1")
task foo {
println("設定フェーズ2")
doFirst {
println("実行フェーズ1")
}
println("設定フェーズ3")
doLast {
println("実行フェーズ2")
}
}
task bar {
println("設定フェーズ4")
}
println("設定フェーズ5")
> gradle foo
> Configure project :
設定フェーズ1
設定フェーズ2
設定フェーズ3
設定フェーズ4
設定フェーズ5
> Task :foo
実行フェーズ1
実行フェーズ2
設定フェーズで実行される部分は、指定しているタスクにかかわらず常に実行される。
したがって、設定フェーズで実行される部分に間違ってタスクの処理を書いてしまうと、タスクを指定していないのに処理が実行されるという状態になってしまうので注意しなければならない。
タスクの参照
task foo {
doFirst {
println foo
println project.foo
println tasks.foo
println tasks.getByName("foo")
println tasks["foo"] // getAt() メソッドのシンタックスシュガー
}
}
task ':foo'
task ':foo'
task ':foo'
task ':foo'
task ':foo'
プロジェクトにタスクを定義すると、そのプロジェクト内ではタスク名だけでタスクを参照できるようになる。
これは、 Project
オブジェクトのプロパティに、作成されたタスクオブジェクトが追加されることで実現している。
生成されたタスクのオブジェクトは Project
が持つ TaskContainer に登録されている。
TaskContainer
は Project
の tasks プロパティから参照でき、 TaskContainer
からはプロパティアクセスや getByName()
, getAt()
メソッドを使ってタスクを参照できる。
まれに、タスク名と同じ名前の拡張オブジェクトを追加するプラグインが存在する。
例えば、 Eclipse Plugin は eclipse
という名前でタスクと拡張オブジェクトの2つを定義する。
この場合、 project.eclipse
で参照したオブジェクトは拡張オブジェクトの方になる。
タスクを参照したい場合は、 tasks.eclipse
のように TaskContainer
を経由する必要がある。
なお、 getByName()
のような文字列で指定するメソッドは、 :someSubProject:fooTask
のようにして他のプロジェクトのタスクを参照することもできる。
タスクの依存関係を定義する
task foo {
doFirst {
println("foo")
}
}
task bar {
doFirst {
println("bar")
}
}
foo.dependsOn bar
> gradle foo
bar
foo
Task
の [dependsOn()] (https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:dependsOn(java.lang.Object[])) メソッドを使用することで、タスクの依存関係を定義できる。
ここでは、 foo
タスクが bar
タスクに依存するように定義している。
これにより、 foo
タスクを実行すると必ず bar
タスクが先に実行されるようになる。
Gradle のタスクの実行順序は、基本はこの依存関係の定義を利用して制御する(依存関係以外の制御方法も用意されている)。
タスクは一度だけ実行される
Gradle は、コマンドラインで指定されたタスクを実行する前に、依存するタスクを調査する。
そして、依存されている側から順番にタスクを実行していく。
このとき、たとえ複数のタスクから依存されているタスクが存在しても、全てのタスクは必ず一度だけしか実行されないように制御されている。
task foo {
doFirst { println("foo") }
}
task bar {
doFirst { println("bar") }
}
task hoge {
doFirst { println("hoge") }
}
foo.dependsOn bar
hoge.dependsOn foo
hoge.dependsOn bar
> gradle hoge
bar
foo
hoge
タスク hoge
は、 foo
, bar
の両方に依存している。
foo
も bar
に依存しているので、単純にタスクの依存関係を図にすると次のようになる。
hoge -> foo -> bar
| ^
+--------------+
単純に依存するタスクを全て実行していくと、 bar
が二回実行されてしまう。
しかし、 Gradle は前述したように複数のタスクに依存されていても一度だけしか実行されないように制御している。
dependsOn を書ける場所と注意点
dependsOn
は Task
のメソッドなので、次のように設定ブロックの中で書くこともできる。
task foo {
dependsOn "bar"
doFirst {
println("foo")
}
}
task bar {
doFirst {
println("bar")
}
}
ここでは、 bar
タスクの指定を文字列にしている。
もしこれを、文字列ではなくプロパティ参照にすると、次のようなエラーになる。
task foo {
dependsOn bar // ★プロパティ参照に置き換え
doFirst {
println("foo")
}
}
task bar {
doFirst {
println("bar")
}
}
> gradle foo
...
> Could not get unknown property 'bar' for task ':foo' of type org.gradle.api.DefaultTask.
...
BUILD FAILED in 4s
bar
というプロパティが見つからずにエラーになる。
dependsOn bar
を書いている部分は、ビルドライフサイクルにおける「設定フェーズ」で実行される部分になる。
設定フェーズでは、ビルドスクリプトが上から順番に実行されていく。
dependsOn bar
が実行される時点では、まだ task bar
が実行されていないので、タスク bar
は未定義ということになる。
したがって、 bar
タスクの定義より上にある foo
タスクの設定ブロック内では、 bar
タスクをプロパティ参照でアクセスすることはできない。
この問題は、次のいずれかの方法で回避できる。
- 前述のように、文字列指定で定義する
-
bar
タスクの定義をfoo
タスクの定義より上に持っていく
記述の順序に依存すると変更に脆くなるので、個人的には文字列指定が良い気がする。
task() メソッドの引数で指定する
task foo(dependsOn: "bar") {
doFirst {
println("foo")
}
}
task bar {
doFirst {
println("bar")
}
}
Map
を受け取る task() メソッドを使えば、引数の Map
で依存するタスクを指定することもできる。
実行順序を強制する
apply plugin: "java"
clean.doFirst { println "clean" }
build.doFirst { println "build" }
task cleanBuild(dependsOn: [clean, build])
> gradle cleanBuild
build
clean
cleanBuild
タスクの依存タスクに [clean, build]
と指定している。
しかし、 cleanBuild
タスクを実行すると build
-> clean
の順番でタスクが実行されている。
このように、単純に dependsOn
に指定しただけでは、タスクの実行順序は定まらない(名前順?)。
clean
-> build
の順で実行されるように制御するにはどうすればいいか?
まず単純に思いつくのは、 dependsOn
で build
が clean
に依存するように定義する方法。
apply plugin: "java"
clean.doFirst { println "clean" }
build.doFirst { println "build" }
build.dependsOn clean
task cleanBuild(dependsOn: [clean, build])
> gradle cleanBuild
clean
build
これで、 clean
-> build
の順序でタスクが実行されるようになった。
しかし、この方法だと build
だけを実行することができなくなる(常に clean
されてしまう)。
> gradle build
clean
build
できれば、 build
単独で実行したときは、 clean
は実行しないようにしたい。
このような、同時に指定された場合は順序を限定したいが、単独でも実行できるようにしておきたい場合は、 mustRunAfter(Object...) を使用する。
apply plugin: "java"
clean.doFirst { println "clean" }
build.doFirst { println "build" }
build.mustRunAfter clean
task cleanBuild(dependsOn: [clean, build])
> gradle cleanBuild
clean
build
> gradle build
build
taskA.mustRunAfter(taskB)
とすることで、 taskA
と taskB
が同時に実行されたときの順序を taskB
-> taskA
となるように強制できる。
後処理のタスクを指定する
task foo {
doFirst { println "foo" }
finalizedBy "finalizer"
}
task bar {
doFirst {
println "bar"
throw new Exception("test")
}
finalizedBy "finalizer"
}
task finalizer {
doFirst { println "finalizer" }
}
> gradle foo
foo
finalizer
> gradle bar
bar
finalizer
...
Execution failed for task ':bar'.
> java.lang.Exception: test
...
BUILD FAILED in 3s
finalizedBy() で設定したタスクは、そのタスクが正常終了したか異常終了したかにかかわらず、必ず実行される。
リソースの開放のような、必ず実行しないといけない処理がある場合に指定する。
実行する条件を指定する
task foo {
doFirst { println "foo" }
onlyIf { project.hasProperty("hoge") }
}
> gradle foo
【何も出力されない】
> gradle foo -Phoge
foo
onlyIf() を使うと、そのタスクのアクションを実行する条件を指定できる。
引数で渡すクロージャが true
を返したときにだけ、そのタスクのアクションが実行される。
クロージャが false
を返した場合は、そのタスクのアクションはスキップされる。
ただし、スキップされるのはあくまでそのタスクのみで、依存するタスクや finalizedBy
で指定したタスクは実行される。
task foo {
doFirst { println "foo" }
onlyIf { project.hasProperty("hoge") }
dependsOn "bar"
finalizedBy "finalizer"
}
task bar {
doFirst { println "bar" }
}
task finalizer {
doFirst { println "finalizer" }
}
> gradle foo
bar
finalizer
foo
の実行はスキップされているが、依存する bar
タスクやファイナライザに指定している finalizer
タスクは実行されている。
ファイルの選択
Gradle にはファイルやディレクトリを選択するための柔軟な API が Project
クラスに用意されている。
タスクの設定やアクションを記述するときは、この API をうまく利用することでファイルの操作が簡潔に書けるようになる。
file(Object)
import java.nio.file.Path
task foo {
doFirst {
File file1 = file("foo.txt")
println(file1)
File file2 = file(new File("foo.txt"))
println(file2)
File file3 = file(Path.of("foo.txt"))
println(file3)
}
}
> gradle foo
F:\etc\...\foo.txt
F:\etc\...\foo.txt
F:\etc\...\foo.txt
※パスは省略して記載しているだけで、実際はフルパスが出力されている。
file()
メソッドは、引数で受け取った値を解析して File
オブジェクトを返す。
文字列のパスから File
や Path
など、様々な入力に対応している(詳しくは API ドキュメントを参照)。
Gradle で任意の入力を良しなに File
オブジェクトに変換したい場合は、この file()
メソッドを使うとだいたいうまくいく。
files(Object...)
task foo {
doFirst {
FileCollection files = files("foo.txt", "bar.txt")
files.each { println it }
}
}
> gradle foo
F:\etc\...\foo.txt
F:\etc\...\bar.txt
files()
メソッドは、複数の File
をまとめたコレクションである FileCollection を生成する。
引数は可変長引数になっており、 file()
で指定できる値は同様に指定できる。
それ以外にも、 Iterable
や Collection
などを渡すこともできる。
とにかく、こちらもたいていの値を良しなに解析して FileCollection
にしてくれる。
FileCollection
には次のようなメソッドが用意されている。
FileCollection files = files("foo.txt", "bar.txt")
println "asPath = ${files.asPath}"
asPath = F:\etc\...\foo.txt;F:\etc\...\bar.txt
各パスをプラットフォームごとの区切り文字で連結した文字列を取得する。
区切り文字は、例えば Linux なら :
で、 Windows なら ;
になる。
クラスパスやモジュールパスのような、ファイルを複数指定するオプションなどで利用できる。
FileCollection files = files("foo.txt", "bar.txt", "fizz.txt", "buzz.txt")
FileCollection filtered = files.filter { it.name.startsWith("b") }
filtered.each { println it }
F:\etc\...\bar.txt
F:\etc\...\buzz.txt
filter()
に渡したクロージャが true
を返した File
のみに要素が絞られた FileCollection
を返す。
fileTree(Object)
task foo {
doFirst {
FileTree tree = fileTree("fromDir")
tree.each { println it }
}
}
> gradle foo
F:\etc\...\fromDir\hoge.txt
F:\etc\...\fromDir\hoge.xml
F:\etc\...\fromDir\sub\foo.txt
F:\etc\...\fromDir\sub\foo.xml
fileTree()
メソッドを使うと、指定したディレクトリ以下のファイルを再帰的に保持した FileTree を取得できる。
FileTree
は FileCollection
を継承しているので、 FileCollection
と同様の操作ができる。
fileTree(Object, Closure)
task foo {
doFirst {
FileTree tree = fileTree("fromDir") {
include "**/*.xml"
}
tree.each { println it }
}
}
> gradle foo
F:\etc\...\fromDir\hoge.xml
F:\etc\...\fromDir\sub\foo.xml
fileTree()
は、第二引数にクロージャを渡すことができる。
クロージャの delegate
は、第一引数で指定したディレクトリの ConfigurableFileTree オブジェクトになっている。
ConfigurableFileTree
は、名前の通り設定可能な FileTree
で、 include()
や exclude()
といったファイルの絞り込みができるようになっている。
絞り込みのパターンは Ant 形式1で指定できる。
fileTree(Map)
task foo {
doFirst {
FileTree tree = fileTree(dir: "fromDir", include: "**/*.xml")
tree.each { println it }
}
}
> gradle foo
F:\etc\...\fromDir\hoge.xml
F:\etc\...\fromDir\sub\foo.xml
引数に Map
を受け取る fileTree()
では、 ConfigurableFileTree
のプロパティに設定する値を Map
で指定できる。
ファイル関係のクラス図
あらためて、ファイル操作まわりのクラスの関係を確認する。
FileCollection
は File
のコレクション。
FileTree
は、特定のディレクトリ以下の File
を再帰的に保持したコレクション。
それぞれを継承した、 ConfigurableFileCollection
と ConfigurableFileTree
があり、対象の File
を絞り込むような設定ができるようになっている。
タスクの入出力
task foo {
doFirst {
println "foo"
file("foo.txt").text = project.hoge
}
}
タスク foo
は、 project
の hoge
プロパティに設定された値を foo.txt
に出力するタスクとなっている。
このタスクを何度か実行してみる。
> gradle foo -Phoge=HOGE
> Task :foo
foo
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
> gradle foo -Phoge=HOGE
> Task :foo
foo
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
当然のことながら、タスクを実行するとタスクのアクションが実行され、ファイル出力が行われる。
しかし、入力が変わっていない以上、このタスクは何度実行しても同じ結果しか出力しない。
出力が変わらないことがわかっているのに毎回実行するのは無駄が大きい。
もしこのタスクが時間のかかるタスクだった場合、ビルド時間に大きな影響を与える可能性がある。
Gradle では、タスクの結果が変わらないのであれば、そのタスクの実行をスキップする仕組みが用意されている。
この仕組みを利用するためには、タスクの入出力を定義する必要がある。
task foo {
def outputFile = file("foo.txt")
inputs.property "hoge", project.hoge
outputs.file outputFile
doFirst {
println "foo"
outputFile.text = hoge
}
}
> gradle foo -Phoge=HOGE
> Task :foo
foo
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
> gradle foo -Phoge=HOGE
> Task :foo UP-TO-DATE
BUILD SUCCESSFUL in 3s
1 actionable task: 1 up-to-date
> gradle foo -Phoge=HOGEHOGE
> Task :foo
foo
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
hoge
の値を一回目と同じ値にして実行した時、 foo
タスクの表示の右に UP-TO-DATE
(最新) と出力され、タスクの実行がスキップされている。
hoge
の値を変更すると、再び foo
タスクは実行された。
task foo {
...
inputs.property "hoge", project.hoge
outputs.file outputFile
...
}
タスクの入出力を定義するには、まず Task
に定義されている次の2つのプロパティを使って入出力を定義するためのオブジェクトにアクセスする。
それぞれのプロパティは、 TaskInputs と TaskOutputs を返す。
これらのクラスに用意されているメソッドを使って、具体的な入出力を定義していく。
Gradle は、タスクが実行されたときに入出力の内容を記録している。
そして、次に同じタスクが実行されたときに、入出力の内容が変化していないかをチェックする2。
もし入出力が変化していない場合、 Gradle はそのタスクの実行をスキップする。
入出力に指定した値が1つでも変化している場合は、タスクは通常どおり実行される。
入出力は、両方が定義されていなければならない。
片方だけ(入力だけ、出力だけ)の指定では、このスキップの仕組みは動かない。
入力の定義
入力には、大きく次の3つを利用できる。
- プロパティ
- ファイル
- ディレクトリ
プロパティは、前述の例で示したように、任意のキーと値を指定できる。
ただし、値はシリアライズ可能で、かつ equals()
で比較できなければならない。
ファイルを指定した場合は、ファイルの内容が変更されたかどうかがチェックされる。
ディレクトリを指定した場合は、そのディレクトリ以下の状態がチェックされる。
ディレクトリの中にファイルやディレクトリが作られたり、ファイルの内容が変更されると、入力が変化したと判断される。
この判定は、サブディレクトリ以下も再帰的に対象になる。
task foo {
...
inputs.file file("input.txt") // 入力ファイルの指定
inputs.dir file("inputDir") // 入力ディレクトリの指定
...
}
出力の定義
出力は、次の2つを利用できる。
- ファイル
- ディレクトリ
task foo {
...
outputs.file file("output.txt") // 出力ファイルの指定
outputs.dir file("outputDir") // 出力ディレクトリの指定
...
}
タスクルール
tasks.addRule("Pattern: echo<MESSAGE>") { taskName ->
if (taskName.startsWith("echo")) {
task(taskName) {
doFirst { println(taskName - "echo") }
}
}
}
> gradle echoHoge
Hoge
> gradle tasks
...
Rules
-----
Pattern: echo<MESSAGE>
TaskContainer
の addRule() メソッドを使うと、タスクルールというものを定義できる。
addRule()
は、第一引数にタスクルールの説明を渡し、第二引数にクロージャを渡す。
クロージャには、参照しようとしたタスクの名前が渡ってくる(ここでは、コマンドラインで指定した "echoHoge"
という文字列が渡ってくる)。
このクロージャの中で受け取った名前でタスクを定義すれば、その名前のタスクが事前に定義されていなくてもエラーにはならず、動的に定義されたタスクが採用される。
つまり、あらかじめ静的にタスク名を定義できないようなタスクでも、タスク名にパターン(ルール)を設けることで、実行時に動的にタスクを定義できる。
このタスクルールで定義されるルールは、 dependsOn
などでも参照できる。
tasks.addRule("Pattern: echo<MESSAGE>") { taskName ->
if (taskName.startsWith("echo")) {
task(taskName) {
doFirst { println(taskName - "echo") }
}
}
}
task foo(dependsOn: echoBar) {
doFirst { println "foo" }
}
> gradle foo
Bar
foo
ここでは、 foo
タスクの dependsOn
で echoBar
と指定している。
ここでもタスクルールが適用され、 echoBar
タスクが動的に生成されている。
標準で用意されている clean ルール
apply plugin: "base"
> gradle tasks
...
Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
base
プラグインを読み込むと、 clean
ルールが適用されるようになる。
(このプラグインは、 Java プラグインなどを適用すれば一緒に読み込まれるので、明示的に読み込むことは基本的にない)
このルールは、任意のタスクの出力ファイルを削除するタスクを生成する。
apply plugin: "base"
task foo {
def outputFile = file("foo.txt")
outputs.file outputFile
doFirst {
outputFile.text = "foo!!"
}
}
ここでは foo
というタスクを定義している。
このタスクは、 foo.txt
というファイルを出力ファイルとして定義している。
> type foo.txt
指定されたファイルが見つかりません。
> gradle foo
...
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
> type foo.txt
foo!!
> gradle cleanFoo
...
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
> type foo.txt
指定されたファイルが見つかりません。
cleanFoo
というタスクは clean
ルールによって動的に生成され、 foo
タスクの出力ファイルを削除するように定義されている。
カスタムタスク
task()
で生成するタスクは、そのプロジェクトでしか利用できない。
また、同じ処理をパラメータを変えて複数の場所で再利用する、といったこともできない。
複数のプロジェクトでパラメータだけを変えて同じタスクを再利用したいような場合は、カスタムのタスクを作成する。
class FooTask extends DefaultTask {
@TaskAction
def foo() {
println("FOO!!")
}
}
task foo(type: FooTask)
> gradle foo
FOO!!
カスタムタスクは、 DefaultTask
を継承して作成する。
任意のメソッドを定義して、 @TaskAction でアノテートすると、そのメソッドがタスクのアクションとして実行される。
作成したカスタムタスクを利用するには、 Map
を受け取る task() を使用する。
この引数の Map
で、 type
に作成したいタスクの型を指定する。
すると、指定した型でタスクのオブジェクトが生成されるようになる。
ここでは説明を簡単にするために同じビルドスクリプト内でカスタムタスクを定義している。
しかし、実際は複数のプロジェクトから参照できる外部の場所(後述する buildSrc
や、外部の jar ファイルなど)に定義することになる。
カスタムタスクの設定
class FooTask extends DefaultTask {
String message
@TaskAction
def foo() {
println("FOO!! message=${message}")
}
}
task foo(type: FooTask) {
message = "Hello World"
}
> gradle foo
FOO!! message=Hello World
type
で型を指定して生成されたタスクは、その型のオブジェクトになっている。
つまり、カスタムタスクに設定用のプロパティやメソッドを定義しておけば、生成されたタスクオブジェクトにアクセスできる任意の場所でタスクオブジェクトの設定を指定できるようになる。
ここでは、設定ブロックの中で message
プロパティに値を設定している。
ファイルの設定値は Object で定義しておく
class FooTask extends DefaultTask {
List inputFiles = []
Object output
@TaskAction
def foo() {
def out = project.file(this.output)
out.text = ""
project.files(this.inputFiles)
.each { out.append("${it.name}\n") }
}
}
task foo (type: FooTask) {
inputFiles = ["fromDir/hoge.txt", "build.gradle", file(".gitignore")]
output = file("output/test.txt")
}
タスクの入出力にファイルやディレクトリを定義する場合、型は Object
や List
のような何でも入れられる型にしておく。
そして、実際に設定値を利用するときに Project
の file()
や files()
を使って変換する。
こうすることで、文字列や File
オブジェクトなど、 Project#file()
がサポートする様々な形式でファイルを指定できるようになるので、設定の柔軟性が高まる。
入出力の指定
class FooTask extends DefaultTask {
@Input
String message
@OutputFile
Object output
@TaskAction
def foo() {
project.file(output).text = message
}
}
task foo(type: FooTask) {
message = "Hello World!!"
output = "foo.txt"
}
> gradle foo
...
BUILD SUCCESSFUL in 4s
1 actionable task: 1 executed
> type foo.txt
Hello World!!
> gradle foo
...
BUILD SUCCESSFUL in 4s
1 actionable task: 1 up-to-date
カスタムタスクでは、アノテーションでタスクの入出力を定義する方法が用意されている。
次のアノテーションでカスタムタスクのフィールドや Getter メソッドを注釈することで、タスクの入出力を定義できる。
- 入力
- 出力
標準で用意されているタスク
よく利用しそうな処理については、標準でタスククラスが用意されている。
例えば、次のようなタスクがある。
Copy
task foo(type: Copy) {
from "fromDir"
into "toDir"
}
> tree /f fromDir
...
│ hoge.txt
│
└─sub
foo.txt
> gradle foo
...
> tree /f toDir
│ hoge.txt
│
└─sub
foo.txt
Copy タスクは、ファイルやディレクトリのコピーを実行する。
コピーの方法の設定には、 Copy
が実装している CopySpec が提供している API を使用する。
CopySpec
にはコピー対象の絞り込みやコピー先を指定するための様々なメソッドが用意されており、かなり柔軟な設定ができるようになっている。
task foo(type: Copy) {
from "fooDir", "barFile"
from file("fizzDir"), file("buzzFile")
...
}
コピー元のファイルやディレクトリを指定する。
引数で複数指定することも、 from()
自体を複数回呼ぶこともできる。
引数で渡した値は最終的に Project
の files(Object...) メソッドに渡される。
したがって、文字列のパスや File
オブジェクトなど、様々な値を渡すことができる。
task foo(type: Copy) {
from("fromDir") {
include "**/*.txt"
}
...
}
from()
の第二引数にクロージャを渡すことができる。
このクロージャの delegate
は CopySpec
になっており、 from()
で指定したディレクトリ以下だけを対象にさらにコピーの条件を絞ることができるようになっている。
ここでは、 include()
でコピーするファイルを *.txt
だけに絞っている。
task foo(type: Copy) {
into "toDir"
...
}
コピー先のディレクトリを指定する。
task foo(type: Copy) {
include "*.txt", "**/*.xml"
...
}
Ant 形式のパターン指定でコピー対象に含める条件を指定できる。
task foo(type: Copy) {
exclude "**/*.class", "**/*.bk"
...
}
こちらは、同じく Ant 形式のパターンでコピーから除外する条件を指定できる。
task foo(type: Copy) {
from "fromDir"
into "toDir"
rename { name ->
name.toUpperCase()
}
}
rename()
を使用すると、コピーするときにファイルの名前を変更できる。
クロージャを受け取る rename()
の場合は、クロージャの引数にコピー前のファイル名(String
)が渡される。
そして、クロージャが返した値がコピー後のファイル名として利用される。
上記実装の場合、元ファイル名を全て大文字にしたファイル名がコピー先のファイル名に利用される。
task foo(type: Copy) {
from "fromDir"
into "toDir"
rename(/(.*)\.([a-z]+)/, '$1_copy.$2')
}
引数に String
を2つ受け取る rename()
は、第一引数に正規表現を、第二引数にリネーム後の式を渡す。
第一引数の正規表現でグループとして定義した部分(()
で括った部分)は、第二引数の式では $n
で参照できる(n
は 1 からの連番)。
上の例の場合、1つ目のグループが (.*)
で、拡張子より前のファイルのベース名を指している。
2つ目のグループは ([a-z]+)
の部分で、拡張子の部分を指している。
そして、 $1_copy.$2
とリネーム後の式で指定することで、ファイルのベース名の末尾に _copy
をつけた名前にリネームしてコピーしている。
実際に動かすと、次のようになる。
> tree /f fromDir
...
│ hoge.txt
│ hoge.xml
│
└─sub
foo.txt
foo.xml
> gradle foo
...
> tree /f toDir
...
│ hoge_copy.txt
│ hoge_copy.xml
│
└─sub
foo_copy.txt
foo_copy.xml
Project の copy()
Project
クラスには、 copy(Closure) というメソッドが用意されている。
引数のクロージャの delegate
は CopySpec
インターフェースを実装しており、 Copy
タスクと同じ要領でコピーの対象を指定できるようになっている。
task foo {
doFirst {
copy {
from "fromDir"
into "toDir"
include "**/*.txt"
}
}
}
単一のタスクの中でいくつかのコピー処理を実行したい場合は、この Project
の copy()
メソッドを使う方法もある。
Delete
task foo(type: Delete) {
delete "targetDir/aaa.txt", "targetDir/hoge"
}
> tree /f targetDir
│ aaa.txt
│ bbb.txt
│
└─hoge
│ ccc.txt
│
└─fuga
ddd.txt
> gradle foo
...
BUILD SUCCESSFUL in 5s
> tree /f targetDir
bbb.txt
Delete タスクを使うと、ファイルやフォルダの削除ができる。
Delete
クラスは DeleteSpec インターフェースを実装している。
このインターフェースで定義されている delete(Object...) メソッドで削除対象を指定する。
引数で指定した値は、 Project#files(Object...)
メソッドに渡されるので、文字列や File
など様々な型で指定できる。
Delete
自体には、「特定のディレクトリの、特定のパターンのファイル」を指定できるような API は用意されていない。
しかし、前述のように delete()
に渡す値は Project#files()
に渡されるので、 FileCollection
を渡すこともできる。
つまり、次のような感じで指定することもできる。
task foo(type: Delete) {
delete fileTree(dir: "targetDir", include: "hoge/**/*.txt")
}
> tree /f targetDir
│ aaa.txt
│ bbb.txt
│
└─hoge
│ ccc.txt
│
└─fuga
ddd.txt
> gradle foo
...
BUILD SUCCESSFUL in 5s
> tree /f targetDir
│ aaa.txt
│ bbb.txt
│
└─hoge
└─fuga
Project の delete() メソッド
copy()
同様、delete(Action) というメソッドが Project
に用意されている。
クロージャの delegate
は DeleteSpec
を実装しているので、 Delete
タスクと同じ要領で削除対象を指定できる。
task foo {
doFirst {
project.delete { delete "targetDir" }
}
}
ここで注意なのが、 delete()
メソッドを呼ぶときは必ず project.delete()
というふうに project
を前につけないといけない点。
task foo {
doFirst {
project.delete {
println "project.delete.delegate=${delegate.class}"
}
delete {
println "delete.delegate=${delegate.class}"
}
}
}
> gradle foo
project.delete.delegate=class org.gradle.api.internal.file.delete.DefaultDeleteSpec
delete.delegate=class build_32bm3o8iprxruz9mv43mbtt86$_run_closure1$_closure2
project.delete()
の方は、 delegate
が DeleteSpec
になっているが、 delete()
だけの方は DeleteSpec
になっていない。
ちなみに、 copy()
の方は project
をつけなくても delegate
は CopySpec
になるので問題ない。
どうも、引数に Action
を受け取るメソッドしかないと、 project
が必要になるもよう(copy()
は copy(Closure)
と copy(Action)
があるが、 delete()
は delete(Action)
しかない)。
なんでこういう動きになるのかは、正直よくわかってない。
Sync
task foo(type: Sync) {
from "fromDir"
into "toDir"
include "**/*.txt"
}
> tree /f fromDir
│ aaa.txt
│ bbb.txt
│ eee.xml
│
└─hoge
│ ccc.txt
│ fff.xml
│
└─fuga
ddd.txt
ggg.xml
> tree /f toDir
hoge.txt
> gradle foo
...
BUILD SUCCESSFUL in 5s
> tree /f toDir
│ aaa.txt
│ bbb.txt
│
└─hoge
│ ccc.txt
│
└─fuga
ddd.txt
Sync タスクは、 into
で指定したディレクトリを from
で指定したディレクトリと同じ状態に更新する。
Sync
は CopySpec
を実装しているので、 Copy
タスクと同じ要領でコピーの方法を制御できる。
タスク実行前に toDir
に存在した hoge.txt
が削除されている。
Sync
による同期は、デフォルトではコピー先のファイルやフォルダを全て一旦削除してから行われる。
同期先にだけ存在するファイルやフォルダで削除してほしくないモノがある場合は、 preserve を指定する。
task foo(type: Sync) {
from "fromDir"
into "toDir"
include "**/*.txt"
preserve {
include "hoge.txt"
}
}
> tree /f toDir
fuga.txt
hoge.txt
piyo.txt
> gradle foo
...
BUILD SUCCESSFUL in 5s
> tree /f toDir
│ aaa.txt
│ bbb.txt
│ hoge.txt
│
└─hoge
│ ccc.txt
│
└─fuga
ddd.txt
preserve
内の include
で指定した対象は削除されなくなる(preserve
は「保存」という意味)。
exclude
を指定することも可能で、 exclude "hoge.txt"
と指定した場合は hoge.txt
だけが削除される(保存対象ではなくなる)。
include
と exclude
を同時に指定した場合は、 include
の設定が優先されるもよう。
Project の sync() メソッド
想像通り、 sync(Action) メソッドも Project
に用意されている。
ただし、このクロージャの delegate
が実装しているのは CopySpec
インターフェースになる。
つまり、 preserve
は指定できない(リファレンスでは preserve
が指定可能と書かれているが、実際に書くとエラーになる)。
task foo {
doFirst {
project.sync {
from "fromDir"
into "toDir"
include "**/*.txt"
}
}
}
sync()
も、 sync(Action)
しかないので、 delete()
同様 project
の修飾をつけないとエラーになる。
Exec
task foo(type: Exec) {
workingDir "workDir"
environment MESSAGE: "Hello World!!"
commandLine "cmd", "/c", "echo", "%MESSAGE%", ">", "hello.txt"
}
> gradle foo
...
BUILD SUCCESSFUL in 5s
> type workDir\hello.txt
Hello World!!
Exec タスクを使うと、任意のコマンドを実行できる。
Exec
は ExecSpec インターフェースを実装しており、そこに設定用のメソッドが定義されている。
commandLine(Object...) で、実行したいコマンドを指定する。
Linux の場合は直接実行したいコマンドから始めればいいが、 Windows の場合は cmd /c <実際に実行したいコマンド>
と続ける必要がある。
workingDir(Object) で実行時のワーキングディレクトリを指定できる。
environment(Map) で、コマンドを実行しているサブプロセス内での環境変数を設定できる。
Project の exec() メソッド
task foo {
doFirst {
exec {
commandLine "cmd", "/c", "echo", "Hello World!!"
}
}
}
Project
の exec(Closure) メソッドを使えば、 Exec
と同じ要領でコマンドを実行できる。
クロージャの delegate
は ExecSpec
となっている。
Zip
task foo(type: Zip) {
baseName = "foo"
from "fromDir"
destinationDir = file("toDir")
include "**/*.txt"
}
> tree /f fromDir
│ aaa.txt
│ bbb.txt
│ eee.xml
│
└─hoge
│ ccc.txt
│ fff.xml
│
└─fuga
ddd.txt
ggg.xml
> gradle foo
...
BUILD SUCCESSFUL in 5s
> dir /b toDir
foo.zip
Zip タスクを使うと、任意のディレクトリを zip で圧縮できる。
Zip
は CopySpec
インターフェースを実装しているので、 Copy
タスクと同じように対象を絞り込むことができる。
ただし、出力先は destinationDir プロパティで指定する。
このプロパティは Object
ではなく File
なので注意。
なお、 base
プラグインを適用している場合、 destinationDir
はデフォルトで build/distributions
になる。
base
プラグインが適用されていない場合(基本ありえないが。。。)、 destinationDir
の指定は必須になる。
zip ファイルの名前は、いくつかのプロパティに設定された値が連結されてつくられる。
task foo(type: Zip) {
baseName = "baseName"
appendix = "appendix"
version = "version"
classifier = "classifier"
extension = "extension"
from "fromDir"
destinationDir = file("toDir")
include "**/*.txt"
}
> gradle foo
...
BUILD SUCCESSFUL in 5s
> dir /b toDir
baseName-appendix-version-classifier.extension
ファイル名全体は、次のようにして各プロパティをハイフンでつなげて作られる。
(設定されていない分のプロパティは無視される(extension
が未設定の場合は "zip"
になる))
baseName-appendix-version-classifier.extension
ファイル名全体そのものを一気に指定したい場合は、 archiveName プロパティを指定する。
なお、 Zip
タスクによって生成される zip ファイルのパスは archivePath プロパティで確認できる。
Project の zipTree() メソッド
残念ながら? Project
に zip()
というメソッドは存在しない。
代わりではないが、逆に zip の内容を抽出するための zipTree(Object) メソッドが用意されている。
引数で対象の zip ファイルのパスを指定すると、その zip の内容の情報を保持した FileTree
が返される。
task foo {
doFirst {
def zip = zipTree("toDir/foo.zip")
zip.each { println it }
}
}
> gradle foo
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\aaa.txt
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\bbb.txt
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\eee.xml
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\hoge\ccc.txt
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\hoge\fff.xml
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\hoge\fuga\ddd.txt
F:\etc\...\build\tmp\expandedArchives\foo.zip_d658f891babbd7031de320615856ccb1\hoge\fuga\ggg.xml
zipTree()
をした直後は、まだ zip ファイルは解凍されていない。
ZipTree
内のファイルの情報にアクセスしようとした時点で実際に zip ファイルがテンポラリディレクトリに解凍され、ファイルの情報にアクセスできるようになる。
この zipTree()
と copy()
を組み合わせることで、 zip の解凍を再現できる。
task foo {
doFirst {
copy {
from zipTree("toDir/foo.zip")
into "build/unzip"
}
}
}
これで、 zip の内容が build/unzip
ディレクトリの下に解凍される。
buildSrc
Gradle には、ビルドスクリプトで使用するクラスを配置するための専用のプロジェクトが用意されている。
|-build.gradle
`-buildSrc/
`-src/main/groovy/
`-sample/
`-HogeTask.groovy
ルートの buildSrc
ディレクトリが、専用のプロジェクトになる。
このプロジェクトの src/main/groovy
ディレクトリの下に、自作のタスクやプラグインのコードを配置できる。
package sample
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class HogeTask extends DefaultTask {
@TaskAction
def hoge() {
println("Hello Hoge.")
}
}
task foo(type: sample.HogeTask)
> gradle foo
Hello Hoge.
buildSrc
にソースコードが存在すると、タスクを実行するときに自動的にコンパイルが行われ、ビルドスクリプトで利用できるようになる。
プラグイン
カスタムタスクを使うと、タスクのロジックをあらかじめ定義しておき、複数の箇所で設定値だけを切り替えて使い回すことができるようになる。
しかし、カスタムタスクはあくまで単一のタスクであり、タスクの生成や細かい設定はすべて利用側で指定しなければならない。
複数のタスクの生成や、タスク間の依存関係などの定義など、より広範囲な処理を共通化して使い回しできるようにするためには、プラグインという仕組みを利用する。
|-build.gradle
`-buildSrc/
`-src/main/groovy/
`-sample/
`-FooPlugin.groovy
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.task("hello") {
doFirst { println("FOO!!") }
}
}
}
apply plugin: sample.FooPlugin
> gradle hello
FOO!!
プラグインは、 Plugin インターフェースを実装して作成する(型引数は Project
にしておく)。
apply(T)
メソッドを実装し、その中にプラグインの処理を記述していく。
記述する内容は、普通に build.gradle
で記述していた内容と同じような感じで問題ない。
ただし、 build.gradle
の場合は暗黙的に Project
への委譲が行われるようになっていたので task()
などを直接呼べていたが、ここでは暗黙的な委譲は行われないので、明示的に Project
のメソッドを呼び出す形で記述する必要がある。
作成したプラグインは、他のプラグインと同じように apply
で読み込むことができる。
プラグインの設定値
プラグインに設定値を渡したい場合は、拡張オブジェクトの仕組みを利用する。
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
def foo = project.extensions.create("foo", FooPluginExtension)
project.task("hello") {
doFirst { println(foo.message) }
}
}
}
class FooPluginExtension {
String message
}
拡張オブジェクトを生成するには、 ExtensionContainer の create()
メソッドを使用する。
ExtensionContainer
は、 Project
の extensions プロパティから取得できる。
create()
メソッドは、第一引数に拡張オブジェクトの名前を、第二引数に拡張オブジェクトの型を渡す。
ここでは、名前に "foo"
を指定し、型に FooPluginExtension
を指定している。
create()
の戻り値は第二引数で指定したクラスのオブジェクトになっており、このオブジェクトを介してビルドスクリプトで指定された設定値にアクセスできる。
生成された拡張オブジェクトは Project
にプロパティとして追加されており、 create()
のときに指定した名前で参照できるようになっている。
apply plugin: sample.FooPlugin
foo.message = "Hello Foo!!"
> gradle hello
Hello Foo!!
拡張オブジェクトは設定ブロックが利用できるようになっているので、次のように記述することもできる。
apply plugin: sample.FooPlugin
foo {
message = "Hello Foo!!"
}
設定値が複数存在する場合は、設定ブロックを利用して記述を簡潔にすることができる。
拡張オブジェクトのコンストラクタ引数
拡張オブジェクトのコンストラクタで引数を受け取るようにしたい場合は、次のように記述する。
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
def foo = project.extensions.create("foo", FooPluginExtension, project)
project.task("hello") {
doFirst { println(foo.outputDir) }
}
}
}
class FooPluginExtension {
Object outputDir
FooPluginExtension(Project project) {
this.outputDir = project.file("${project.buildDir}/foo")
}
}
拡張オブジェクトの FooPluginExtension
は Project
オブジェクトを受け取り、 outputDir
のデフォルト値を Project
から生成した値にしている。
このコンストラクタ引数に値を渡すには、 create()
メソッドの第三引数以降に対応する値を渡すように実装する。
create()
の第三引数は可変長引数になっており、ここで指定した値はそのまま拡張オブジェクトのコンストラクタに渡されるようになっている。
拡張オブジェクトで設定した値をタスクの設定値に利用する
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
def foo = project.extensions.create("foo", FooPluginExtension)
project.task("copyFile") {
inputs.file foo.inputFile
outputs.dir foo.outputDir
doFirst {
project.copy {
from foo.inputFile
into foo.outputDir
}
}
}
}
}
class FooPluginExtension {
Object inputFile
Object outputDir
}
copyFile
というタスクを作成し、拡張オブジェクトの inputFile
を outputDir
にコピーするように実装している。
また、 copyFile
タスクの入出力には、それぞれ inputFile
と outputDir
を設定している。
apply plugin: sample.FooPlugin
foo {
inputFile = "fromDir/aaa.txt"
outputDir = "build"
}
これを実行すると、次のようにビルドは失敗する。
> gradle copyFile
...
> Task :copyFile FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Some problems were found with the configuration of task ':copyFile'.
> No value has been specified for property '$1'.
...
これだけだと、ちょっと何を言っているかわからないが、次のようなデバッグ用の出力を追加してみると何が起こっているかわかってくる。
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "FooPlugin.apply() 開始"
def foo = project.extensions.create("foo", FooPluginExtension)
project.task("copyFile") {
inputs.file foo.inputFile
outputs.dir foo.outputDir
println "foo.inputFile=${foo.inputFile}"
println "foo.outputDir=${foo.outputDir}"
doFirst {
project.copy {
from foo.inputFile
into foo.outputDir
}
}
}
println "FooPlugin.apply() 終了"
}
}
class FooPluginExtension {
Object inputFile
Object outputDir
}
println "FooPlugin 適用前"
apply plugin: sample.FooPlugin
println "FooPlugin 適用後"
println "foo 拡張オブジェクトに設定値を指定"
foo {
inputFile = "fromDir/aaa.txt"
outputDir = "build"
}
> gradle createFile
...
FooPlugin 適用前
FooPlugin.apply() 開始
foo.inputFile=null
foo.outputDir=null
FooPlugin.apply() 終了
FooPlugin 適用後
foo 拡張オブジェクトに設定値を指定
> Task :copyFile FAILED
...
createFile
タスクの入出力を設定している段階では、まだ foo
拡張オブジェクトに値が設定されていない。
このため、入出力の指定が null
になってしまっている。
プラグインの適用はビルドライフサイクルの設定フェーズで行われており、基本的に上から順番に設定処理が実行されていく。
したがって、カスタムプラグインの apply()
メソッドの中では、まだ foo
拡張オブジェクトが設定されていないので値を参照できないことになる。
要するに、プラグインの適用後に設定される予定の値を、プラグイン適用中に参照しようとしているのでおかしな状態になっているということになる。
もし拡張オブジェクトに設定している値が、全て実行フェーズで参照されるのであれば、このような問題はおこならない。
しかし、入出力の設定は設定フェーズで指定しなければならないので、このままだと入出力の設定が不可能ということになってしまう。
Property 使ってプロパティを定義する
この問題は、 Property
を利用すると回避できる。
package sample
import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.api.provider.Property
class FooPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
def foo = project.extensions.create("foo", FooPluginExtension, project)
project.task("copyFile") {
inputs.file foo.inputFile
outputs.dir foo.outputDir
doFirst {
project.copy {
from foo.inputFile
into foo.outputDir
}
}
}
}
}
class FooPluginExtension {
Property<Object> inputFile
Property<Object> outputDir
FooPluginExtension(Project project) {
this.inputFile = project.objects.property(Object)
this.outputDir = project.objects.property(Object)
}
}
apply plugin: sample.FooPlugin
foo {
inputFile = "fromDir/aaa.txt"
outputDir = "build"
}
> gradle copyFile
...
BUILD SUCCESSFUL in 6s
inputFile
, outputDir
プロパティの型に Property という型を使用している。
Property
のインスタンスは ObjectFactory の property(Class)
メソッドで生成できる。
ObjectFactory
は、 Project
の getObjects()
メソッドから取得できる。
Project
の file()
などは、この Property
型をサポートしているのでそのまま渡せているが、中身の値が必要な場合は get()
メソッドを使えば取得できる。
また、 set()
メソッドで値を変更することもできる。
ところで、上の例では Property
型であるはずのプロパティに対して、代入演算子で文字列(String
型)を設定している。
普通であれば、型が違うためにこのような記述はエラーになる。
これは、 Gradle が Property
型のプロパティに対してセッターメソッドを自動的に生成してくれているために実現できている。
apply plugin: sample.FooPlugin
foo {
inputFile = "fromDir/aaa.txt"
outputDir = "build"
println delegate.class.methods.find { it.name == "setInputFile" }
}
> gradle copyFile
...
public void sample.FooPluginExtension_Decorated.setInputFile(java.lang.Object)
...
この代入演算子は、自動生成された setInpputFile(Object)
メソッド呼び出しのシンタックスシュガーとなっている。
この Property
型はカスタムタスクのプロパティでも利用でき、セッターの自動生成もサポートされている。
package sample
import org.gradle.api.provider.Property
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputDirectory
class CustomCopyTask extends DefaultTask {
@InputFile
Property<Object> inputFile = project.objects.property(Object)
@OutputDirectory
Property<Object> outputDir = project.objects.property(Object)
@TaskAction
def copy() {
project.copy {
from this.inputFile
into this.outputDir
}
}
}
task foo(type: sample.CustomCopyTask) {
inputFile = "fromDir/aaa.txt"
outputDir = "build"
}
プロパティをコレクションで宣言したい場合は、 ListProperty や SetProperty といったサブタイプが用意されている。
いずれも ObjectFactory
にファクトリメソッドが用意されているので、そこからオブジェクトを生成できる。
ライフサイクルタスク
Gradle にはライフサイクルタスクというものが用意されている。
ライフサイクルタスクは、それ自身は処理を行わない。
代わりに、何らかの概念を表現し、他のタスクに依存することで役割を果たす。
たとえば、 Base プラグイン には check というタスクが定義されている。
この check
タスク自体は何もしないが、「プロジェクトの検証処理を行う」という概念を表現している。
あるプラグインが検証処理を行うタスクを追加する場合、 check
の依存関係にその検証タスクを追加しておく。
すべてのプラグインがこのように実装されていれば、 check
を実行するだけですべての検証処理が動くようになる。
例えば、 Java プラグイン は、 test
タスクを check
の依存関係に追加している。
これにより、 check
を実行すれば test
も実行されることになる。
参考
- Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築 | 綿引 琢磨, 須江 信洋, 林 政利, 今井 勝信 |本 | 通販 | Amazon
- Build Lifecycle
- Authoring Tasks
- Lazy Configuration