Gradle のタスク定義のあれこれ

Gradle のタスクについて、基礎から応用までいろいろメモ。

# 前提知識
- [Groovyを知らない人のためのbuild.gradle読み書き入門 - Qiita](https://qiita.com/opengl-8080/items/a0bb31fb20cb6505188b) に書いていること
- Groovy の基本知識
    - 各種シンタックスシュガーとか基本文法とか

# 環境
**Gradle**

`5.0`

**Java**

`openjdk 11.0.1`

**OS**

`Windows 10`


# タスクの実体
Gradle でタスクを定義するには、次のように記述する。

```gradle:build.gradle
task foo
```

この `foo` タスクの定義は、 [Project](https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html) の [task(String)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:task(java.lang.String)) メソッドを呼び出している。

`task()` メソッドは、生成されたタスクを表すオブジェクトを返すので、次のようにして `foo` タスクのオブジェクトを取得できる。

```gradle:build.gradle
def foo = task foo
println(foo)
println(foo.class)
```

```bash:実行結果
> gradle foo

task ':foo'
class org.gradle.api.DefaultTask_Decorated
```

Gradle のタスクの実体は [Task](https://docs.gradle.org/current/javadoc/org/gradle/api/Task.html) のオブジェクトで、単純に `task()` で生成した場合は [DefaultTask](https://docs.gradle.org/current/javadoc/org/gradle/api/DefaultTask.html) のオブジェクトが生成される。

# Action
タスクは、内部に [Action](https://docs.gradle.org/current/javadoc/org/gradle/api/Action.html) のリストを保持している。
この `Action` は、そのタスクで実行する具体的な処理を持っている。
そしてタスクを起動すると、リストの先頭から順番に `Action` が実行されていく。

`Action` は、 `Task` が持つ `doFirst()`, `doLast()` メソッドでリストの先頭と末尾のいずれかに追加できる。

```gradle:build.gradle
def foo = task foo

foo.doFirst {
    println "two"
}
foo.doFirst {
    println "one"
}
foo.doLast {
    println "three"
}
foo.doLast {
    println "four"
}
```

```bash:実行結果
> gradle foo

one
two
three
four
```

`doFirst()` はリストの先頭に `Action` を追加し、 `doLast()` は末尾に `Action` を追加する。

上記例では `doFirst()` を二回実行している。
この場合、あとで実行した `"one"` を出力する `Action` のほうがリストの先頭に挿入されることになるので、 `"one" -> "two"` の順番で出力されている。

# 設定ブロックで定義する
```gradle:build.gradle
task foo {
    println(delegate)
    println(delegate.class)

    doFirst {
        println("FOO!!")
    }
}
```

```bash:実行結果
> gradle foo

> Configure project :
task ':foo'
class org.gradle.api.DefaultTask_Decorated

> Task :foo
FOO!!
```

`task()` メソッドでタスクを定義するときに、[第二引数にクロージャを渡すことができる](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:task(java.lang.String,%20groovy.lang.Closure))。
このクロージャの `delegate` は、作成されたタスクオブジェクトとなっている。
したがって、クロージャ内では作成したタスクのメソッドやプロパティに暗黙的にアクセスできるようになっている。


# ビルドライフサイクル
Gradle の実行は、大きく３つのフェーズに分かれている。

|フェーズ                      |内容|
|-----------------------------|----|
|初期化フェーズ<br>(Initialization)|プロジェクトがシングルかマルチかなどを判定し、 `Project` のオブジェクトを生成する。|
|設定フェーズ<br>(Configuration)   |ビルドスクリプトを実行し、 `project` オブジェクトを構築していく。|
|実行フェーズ<br>(Execution)       |コマンドラインで指定されたタスクを実際に実行する。               |

具体的に `build.gradle` の中でいうと、タスクに設定している `Action` の中が実行フェーズで実行され、それ以外は設定フェーズで実行される。

```gradle:build.gradle
println("設定フェーズ１")

task foo {
    println("設定フェーズ２")
    doFirst {
        println("実行フェーズ１")
    }
    
    println("設定フェーズ３")
    doLast {
        println("実行フェーズ２")
    }
}

task bar {
    println("設定フェーズ４")
}

println("設定フェーズ５")
```

```bash:実行結果
> gradle foo

> Configure project :
設定フェーズ１
設定フェーズ２
設定フェーズ３
設定フェーズ４
設定フェーズ５

> Task :foo
実行フェーズ１
実行フェーズ２
```

設定フェーズで実行される部分は、指定しているタスクにかかわらず常に実行される。
したがって、設定フェーズで実行される部分に間違ってタスクの処理を書いてしまうと、タスクを指定していないのに処理が実行されるという状態になってしまうので注意しなければならない。

# タスクの参照
```gradle:build.gradle
task foo {
    doFirst {
        println foo
        println project.foo
        println tasks.foo
        println tasks.getByName("foo")
        println tasks["foo"] // getAt() メソッドのシンタックスシュガー
    }
}
```

```text:実行結果
task ':foo'
task ':foo'
task ':foo'
task ':foo'
task ':foo'
```

プロジェクトにタスクを定義すると、そのプロジェクト内ではタスク名だけでタスクを参照できるようになる。
これは、 `Project` オブジェクトのプロパティに、作成されたタスクオブジェクトが追加されることで実現している。

生成されたタスクのオブジェクトは `Project` が持つ [TaskContainer](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskContainer.html) に登録されている。
`TaskContainer` は `Project` の [tasks](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:tasks) プロパティから参照でき、 `TaskContainer` からはプロパティアクセスや `getByName()`, `getAt()` メソッドを使ってタスクを参照できる。

まれに、タスク名と同じ名前の拡張オブジェクトを追加するプラグインが存在する。
例えば、 [Eclipse Plugin](https://docs.gradle.org/current/userguide/eclipse_plugin.html) は `eclipse` という名前で[タスク](https://docs.gradle.org/current/userguide/eclipse_plugin.html#eclipsetasks)と[拡張オブジェクト](https://docs.gradle.org/current/userguide/eclipse_plugin.html#sec:eclipse_configuration)の２つを定義する。
この場合、 `project.eclipse` で参照したオブジェクトは拡張オブジェクトの方になる。
タスクを参照したい場合は、 `tasks.eclipse` のように `TaskContainer` を経由する必要がある。

なお、 `getByName()` のような文字列で指定するメソッドは、 `:someSubProject:fooTask` のようにして他のプロジェクトのタスクを参照することもできる。

# タスクの依存関係を定義する
```gradle:build.gradle
task foo {
    doFirst {
        println("foo")
    }
}

task bar {
    doFirst {
        println("bar")
    }
}

foo.dependsOn bar
```

```bash:実行結果
> 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 は、コマンドラインで指定されたタスクを実行する前に、依存するタスクを調査する。
そして、依存されている側から順番にタスクを実行していく。

このとき、たとえ複数のタスクから依存されているタスクが存在しても、全てのタスクは必ず一度だけしか実行されないように制御されている。

```gradle:build.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
```

```bash:実行結果
> gradle hoge
bar
foo
hoge
```

タスク `hoge` は、 `foo`, `bar` の両方に依存している。
`foo` も `bar` に依存しているので、単純にタスクの依存関係を図にすると次のようになる。

```text:依存関係を図で表現
hoge -> foo -> bar
 |              ^
 +--------------+
```

単純に依存するタスクを全て実行していくと、 `bar` が二回実行されてしまう。
しかし、 Gradle は前述したように複数のタスクに依存されていても一度だけしか実行されないように制御している。

## dependsOn を書ける場所と注意点
`dependsOn` は `Task` のメソッドなので、次のように設定ブロックの中で書くこともできる。

```gradle:build.gradle
task foo {
    dependsOn "bar"
    doFirst {
        println("foo")
    }
}

task bar {
    doFirst {
        println("bar")
    }
}
```

ここでは、 `bar` タスクの指定を文字列にしている。
もしこれを、文字列ではなくプロパティ参照にすると、次のようなエラーになる。

```gradle:build.gradle
task foo {
    dependsOn bar // ★プロパティ参照に置き換え
    doFirst {
        println("foo")
    }
}

task bar {
    doFirst {
        println("bar")
    }
}
```

```bash:実行結果
> 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() メソッドの引数で指定する
```gradle:build.gradle
task foo(dependsOn: "bar") {
    doFirst {
        println("foo")
    }
}

task bar {
    doFirst {
        println("bar")
    }
}
```

`Map` を受け取る [task()](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:task(java.util.Map,%20java.lang.String,%20groovy.lang.Closure)) メソッドを使えば、引数の `Map` で依存するタスクを指定することもできる。

# 実行順序を強制する
```gradle:build.gradle
apply plugin: "java"

clean.doFirst { println "clean" }
build.doFirst { println "build" }

task cleanBuild(dependsOn: [clean, build])
```

```bash:実行結果
> gradle cleanBuild
build
clean
```

`cleanBuild` タスクの依存タスクに `[clean, build]` と指定している。
しかし、 `cleanBuild` タスクを実行すると `build` -> `clean` の順番でタスクが実行されている。

このように、単純に `dependsOn` に指定しただけでは、タスクの実行順序は定まらない（名前順？）。

`clean` -> `build` の順で実行されるように制御するにはどうすればいいか？
まず単純に思いつくのは、 `dependsOn` で `build` が `clean` に依存するように定義する方法。

```gradle:build.gradle
apply plugin: "java"

clean.doFirst { println "clean" }
build.doFirst { println "build" }
build.dependsOn clean

task cleanBuild(dependsOn: [clean, build])
```

```bash:実行結果
> gradle cleanBuild
clean
build
```

これで、 `clean` -> `build` の順序でタスクが実行されるようになった。

しかし、この方法だと `build` だけを実行することができなくなる（常に `clean` されてしまう）。

```bash:buildだけ指定して実行
> gradle build
clean
build
```

できれば、 `build` 単独で実行したときは、 `clean` は実行しないようにしたい。

このような、同時に指定された場合は順序を限定したいが、単独でも実行できるようにしておきたい場合は、 [mustRunAfter(Object...)](https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:mustRunAfter(java.lang.Object[])) を使用する。

```gradle:build.gradle
apply plugin: "java"

clean.doFirst { println "clean" }
build.doFirst { println "build" }
build.mustRunAfter clean

task cleanBuild(dependsOn: [clean, build])
```

```bash:実行結果
> gradle cleanBuild
clean
build

> gradle build
build
```

`taskA.mustRunAfter(taskB)` とすることで、 `taskA` と `taskB` が同時に実行されたときの順序を `taskB` -> `taskA` となるように強制できる。

# 後処理のタスクを指定する
```gradle:build.gradle
task foo {
    doFirst { println "foo" }
    finalizedBy "finalizer"
}

task bar {
    doFirst { 
        println "bar"
        throw new Exception("test")
    }
    finalizedBy "finalizer"
}

task finalizer {
    doFirst { println "finalizer" }
}
```

```bash:実行結果
> gradle foo
foo
finalizer

> gradle bar
bar
finalizer

...

Execution failed for task ':bar'.
> java.lang.Exception: test

...

BUILD FAILED in 3s
```

[finalizedBy()](https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:finalizedBy(java.lang.Object[])) で設定したタスクは、そのタスクが正常終了したか異常終了したかにかかわらず、必ず実行される。

リソースの開放のような、必ず実行しないといけない処理がある場合に指定する。

# 実行する条件を指定する
```gradle:build.gradle
task foo {
    doFirst { println "foo" }
    onlyIf { project.hasProperty("hoge") }
}
```

```bash:実行結果
> gradle foo
【何も出力されない】

> gradle foo -Phoge
foo
```

[onlyIf()](https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:onlyIf(groovy.lang.Closure)) を使うと、そのタスクのアクションを実行する条件を指定できる。

引数で渡すクロージャが `true` を返したときにだけ、そのタスクのアクションが実行される。
クロージャが `false` を返した場合は、そのタスクのアクションはスキップされる。

ただし、スキップされるのはあくまでそのタスクのみで、依存するタスクや `finalizedBy` で指定したタスクは実行される。

```gradle:build.gradle
task foo {
    doFirst { println "foo" }
    onlyIf { project.hasProperty("hoge") }
    dependsOn "bar"
    finalizedBy "finalizer"
}

task bar {
    doFirst { println "bar" }
}

task finalizer {
    doFirst { println "finalizer" }
}
```

```bash:実行結果
> gradle foo
bar
finalizer
```

`foo` の実行はスキップされているが、依存する `bar` タスクやファイナライザに指定している `finalizer` タスクは実行されている。

# ファイルの選択
Gradle にはファイルやディレクトリを選択するための柔軟な API が `Project` クラスに用意されている。
タスクの設定やアクションを記述するときは、この API をうまく利用することでファイルの操作が簡潔に書けるようになる。

## [file(Object)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:file(java.lang.Object))

```gradle:build.gradle
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)
    }
}
```

```text:実行結果
> gradle foo
F:\etc\...\foo.txt
F:\etc\...\foo.txt
F:\etc\...\foo.txt
```

※パスは省略して記載しているだけで、実際はフルパスが出力されている。

`file()` メソッドは、引数で受け取った値を解析して `File` オブジェクトを返す。

文字列のパスから `File` や `Path` など、様々な入力に対応している（詳しくは API ドキュメントを参照）。

Gradle で任意の入力を良しなに `File` オブジェクトに変換したい場合は、この `file()` メソッドを使うとだいたいうまくいく。

## [files(Object...)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:files(java.lang.Object[]))

```gradle:build.gradle
task foo {
    doFirst {
        FileCollection files = files("foo.txt", "bar.txt")
        files.each { println it }
    }
}
```

```text:実行結果
> gradle foo
F:\etc\...\foo.txt
F:\etc\...\bar.txt
```

`files()` メソッドは、複数の `File` をまとめたコレクションである [FileCollection](https://docs.gradle.org/current/javadoc/org/gradle/api/file/FileCollection.html) を生成する。
引数は可変長引数になっており、 `file()` で指定できる値は同様に指定できる。
それ以外にも、 `Iterable` や `Collection` などを渡すこともできる。

とにかく、こちらもたいていの値を良しなに解析して `FileCollection` にしてくれる。

`FileCollection` には次のようなメソッドが用意されている。

**[getAsPath()](https://docs.gradle.org/current/javadoc/org/gradle/api/file/FileCollection.html#getAsPath--)**

```gradle:build.gradle
        FileCollection files = files("foo.txt", "bar.txt")
        println "asPath = ${files.asPath}"
```

```text:実行結果
asPath = F:\etc\...\foo.txt;F:\etc\...\bar.txt
```

各パスをプラットフォームごとの区切り文字で連結した文字列を取得する。
区切り文字は、例えば Linux なら `:` で、 Windows なら `;` になる。

クラスパスやモジュールパスのような、ファイルを複数指定するオプションなどで利用できる。

**[filter(Closure)](https://docs.gradle.org/current/javadoc/org/gradle/api/file/FileCollection.html#filter-groovy.lang.Closure-)**

```gradle:build.gradle
        FileCollection files = files("foo.txt", "bar.txt", "fizz.txt", "buzz.txt")
        FileCollection filtered = files.filter { it.name.startsWith("b") }
        filtered.each { println it }
```

```text:実行結果
F:\etc\...\bar.txt
F:\etc\...\buzz.txt
```

`filter()` に渡したクロージャが `true` を返した `File` のみに要素が絞られた `FileCollection` を返す。


## [fileTree(Object)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:fileTree(java.lang.Object))

```gradle:build.gradle
task foo {
    doFirst {
        FileTree tree = fileTree("fromDir")
        tree.each { println it }
    }
}
```

```text:実行結果
> 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](https://docs.gradle.org/current/javadoc/org/gradle/api/file/FileTree.html) を取得できる。
`FileTree` は `FileCollection` を継承しているので、 `FileCollection` と同様の操作ができる。

## [fileTree(Object, Closure)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:fileTree(java.lang.Object,%20groovy.lang.Closure))

```gradle:build.gradle
task foo {
    doFirst {
        FileTree tree = fileTree("fromDir") {
            include "**/*.xml"
        }
        tree.each { println it }
    }
}
```

```text:実行結果
> gradle foo
F:\etc\...\fromDir\hoge.xml
F:\etc\...\fromDir\sub\foo.xml
```

`fileTree()` は、第二引数にクロージャを渡すことができる。

クロージャの `delegate` は、第一引数で指定したディレクトリの [ConfigurableFileTree](https://docs.gradle.org/current/javadoc/org/gradle/api/file/ConfigurableFileTree.html) オブジェクトになっている。
`ConfigurableFileTree` は、名前の通り設定可能な `FileTree` で、 `include()` や `exclude()` といったファイルの絞り込みができるようになっている。

絞り込みのパターンは Ant 形式[^2]で指定できる。

[^2]: Ant形式については「ant パターン 形式」とかで検索すればいろいろ情報が出てくるのでそちらを参照

## [fileTree(Map)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:fileTree(java.util.Map))

```gradle:build.gradle
task foo {
    doFirst {
        FileTree tree = fileTree(dir: "fromDir", include: "**/*.xml")
        tree.each { println it }
    }
}
```

```text:実行結果
> gradle foo
F:\etc\...\fromDir\hoge.xml
F:\etc\...\fromDir\sub\foo.xml
```

引数に `Map` を受け取る `fileTree()` では、 `ConfigurableFileTree` のプロパティに設定する値を `Map` で指定できる。


## ファイル関係のクラス図
あらためて、ファイル操作まわりのクラスの関係を確認する。

![gradle.png](https://qiita-image-store.s3.amazonaws.com/0/28302/0ceb8703-456c-9ccc-9c55-da5f8157f9a9.png)

`FileCollection` は `File` のコレクション。
`FileTree` は、特定のディレクトリ以下の `File` を再帰的に保持したコレクション。

それぞれを継承した、 `ConfigurableFileCollection` と `ConfigurableFileTree` があり、対象の `File` を絞り込むような設定ができるようになっている。

# タスクの入出力
```gradle:build.gradle
task foo {
    doFirst {
        println "foo"
        file("foo.txt").text = project.hoge
    }
}
```

タスク `foo` は、 `project` の `hoge` プロパティに設定された値を `foo.txt` に出力するタスクとなっている。
このタスクを何度か実行してみる。

```bash:実行結果
> 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 では、タスクの結果が変わらないのであれば、そのタスクの実行をスキップする仕組みが用意されている。
この仕組みを利用するためには、タスクの入出力を定義する必要がある。

```gradle:build.gradle
task foo {
    def outputFile = file("foo.txt")

    inputs.property "hoge", project.hoge
    outputs.file outputFile

    doFirst {
        println "foo"
        outputFile.text = hoge
    }
}
```

```bash:実行結果
> 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` タスクは実行された。

```gradle:build.gradle
task foo {
    ...
    inputs.property "hoge", project.hoge
    outputs.file outputFile
    ...
}
```

タスクの入出力を定義するには、まず `Task` に定義されている次の２つのプロパティを使って入出力を定義するためのオブジェクトにアクセスする。

- [inputs](https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:inputs)
- [outputs](https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task:outputs)

それぞれのプロパティは、 [TaskInputs](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskInputs.html) と [TaskOutputs](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskOutputs.html) を返す。
これらのクラスに用意されているメソッドを使って、具体的な入出力を定義していく。

Gradle は、タスクが実行されたときに入出力の内容を記録している。
そして、次に同じタスクが実行されたときに、入出力の内容が変化していないかをチェックする[^1]。
もし入出力が変化していない場合、 Gradle はそのタスクの実行をスキップする。
入出力に指定した値が１つでも変化している場合は、タスクは通常どおり実行される。

[^1]: 出力ファイルが削除されていたりしたら再実行が必要なので、出力の状態もチェックする必要がある

入出力は、両方が定義されていなければならない。
片方だけ（入力だけ、出力だけ）の指定では、このスキップの仕組みは動かない。

## 入力の定義
入力には、大きく次の３つを利用できる。

- プロパティ
    - [property(String, Object)](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskInputs.html#property-java.lang.String-java.lang.Object-)
    - [properties(Map)](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskInputs.html#properties-java.util.Map-)
- ファイル
    - [file(Object)](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskInputs.html#file-java.lang.Object-)
    - [files(Object...)](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskInputs.html#files-java.lang.Object...-)
- ディレクトリ
    - [dir(Object)](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskInputs.html#dir-java.lang.Object-)

プロパティは、前述の例で示したように、任意のキーと値を指定できる。
ただし、値はシリアライズ可能で、かつ `equals()` で比較できなければならない。

ファイルを指定した場合は、ファイルの内容が変更されたかどうかがチェックされる。

ディレクトリを指定した場合は、そのディレクトリ以下の状態がチェックされる。
ディレクトリの中にファイルやディレクトリが作られたり、ファイルの内容が変更されると、入力が変化したと判断される。
この判定は、サブディレクトリ以下も再帰的に対象になる。

```gradle:build.gradle
task foo {
    ...
    inputs.file file("input.txt") // 入力ファイルの指定
    inputs.dir file("inputDir")   // 入力ディレクトリの指定
    ...
}
```

## 出力の定義
出力は、次の２つを利用できる。

- ファイル
    - [file(Object)](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskOutputs.html#file-java.lang.Object-)
    - [files(Object...)](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskOutputs.html#files-java.lang.Object...-)
- ディレクトリ
    - [dir(Object)](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskOutputs.html#dir-java.lang.Object-)
    - [dir(Object...)](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskOutputs.html#dirs-java.lang.Object...-)

```gradle:build.gradle
task foo {
    ...
    outputs.file file("output.txt") // 出力ファイルの指定
    outputs.dir file("outputDir")   // 出力ディレクトリの指定
    ...
}
```

# タスクルール
```gradle:build.gradle
tasks.addRule("Pattern: echo<MESSAGE>") { taskName ->
    if (taskName.startsWith("echo")) {
        task(taskName) {
            doFirst { println(taskName - "echo") }
        }
    }
}
```

```bash:実行結果
> gradle echoHoge
Hoge

> gradle tasks
...

Rules
-----
Pattern: echo<MESSAGE>
```

`TaskContainer` の [addRule()](https://docs.gradle.org/current/javadoc/org/gradle/api/NamedDomainObjectCollection.html#addRule-java.lang.String-groovy.lang.Closure-) メソッドを使うと、タスクルールというものを定義できる。

`addRule()` は、第一引数にタスクルールの説明を渡し、第二引数にクロージャを渡す。
クロージャには、参照しようとしたタスクの名前が渡ってくる（ここでは、コマンドラインで指定した `"echoHoge"` という文字列が渡ってくる）。

このクロージャの中で受け取った名前でタスクを定義すれば、その名前のタスクが事前に定義されていなくてもエラーにはならず、動的に定義されたタスクが採用される。
つまり、あらかじめ静的にタスク名を定義できないようなタスクでも、タスク名にパターン（ルール）を設けることで、実行時に動的にタスクを定義できる。

このタスクルールで定義されるルールは、 `dependsOn` などでも参照できる。

```gradle:build.gradle
tasks.addRule("Pattern: echo<MESSAGE>") { taskName ->
    if (taskName.startsWith("echo")) {
        task(taskName) {
            doFirst { println(taskName - "echo") }
        }
    }
}

task foo(dependsOn: echoBar) {
    doFirst { println "foo" }
}
```

```bash:実行結果
> gradle foo
Bar
foo
```

ここでは、 `foo` タスクの `dependsOn` で `echoBar` と指定している。
ここでもタスクルールが適用され、 `echoBar` タスクが動的に生成されている。

## 標準で用意されている clean ルール
```gradle:build.gradle
apply plugin: "base"
```

```bash:実行結果
> gradle tasks
...

Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
```

`base` プラグインを読み込むと、 `clean` ルールが適用されるようになる。
（このプラグインは、 Java プラグインなどを適用すれば一緒に読み込まれるので、明示的に読み込むことは基本的にない）

このルールは、任意のタスクの出力ファイルを削除するタスクを生成する。

```gradle:build.gradle
apply plugin: "base"

task foo {
    def outputFile = file("foo.txt")
    outputs.file outputFile

    doFirst {
        outputFile.text = "foo!!"
    }
}
```

ここでは `foo` というタスクを定義している。
このタスクは、 `foo.txt` というファイルを出力ファイルとして定義している。

```bash:実行結果
> 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()` で生成するタスクは、そのプロジェクトでしか利用できない。
また、同じ処理をパラメータを変えて複数の場所で再利用する、といったこともできない。

複数のプロジェクトでパラメータだけを変えて同じタスクを再利用したいような場合は、カスタムのタスクを作成する。

```gradle:build.gradle
class FooTask extends DefaultTask {
    @TaskAction
    def foo() {
        println("FOO!!")
    }
}

task foo(type: FooTask)
```

```bash:実行結果
> gradle foo
FOO!!
```

カスタムタスクは、 `DefaultTask` を継承して作成する。
任意のメソッドを定義して、 [@TaskAction](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskAction.html) でアノテートすると、そのメソッドがタスクのアクションとして実行される。

作成したカスタムタスクを利用するには、 `Map` を受け取る [task()](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:task(java.util.Map,%20java.lang.String)) を使用する。
この引数の `Map` で、 `type` に作成したいタスクの型を指定する。
すると、指定した型でタスクのオブジェクトが生成されるようになる。

ここでは説明を簡単にするために同じビルドスクリプト内でカスタムタスクを定義している。
しかし、実際は複数のプロジェクトから参照できる外部の場所（後述する `buildSrc` や、外部の jar ファイルなど）に定義することになる。

## カスタムタスクの設定
```gradle:build.gradle
class FooTask extends DefaultTask {

    String message

    @TaskAction
    def foo() {
        println("FOO!! message=${message}")
    }
}

task foo(type: FooTask) {
    message = "Hello World"
}
```

```bash:実行結果
> gradle foo
FOO!! message=Hello World
```

`type` で型を指定して生成されたタスクは、その型のオブジェクトになっている。
つまり、カスタムタスクに設定用のプロパティやメソッドを定義しておけば、生成されたタスクオブジェクトにアクセスできる任意の場所でタスクオブジェクトの設定を指定できるようになる。

ここでは、設定ブロックの中で `message` プロパティに値を設定している。

## ファイルの設定値は Object で定義しておく
```gradle:build.gradle
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()` がサポートする様々な形式でファイルを指定できるようになるので、設定の柔軟性が高まる。

## 入出力の指定
```gradle:build.gradle
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"
}
```

```bash:実行結果
> 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 メソッドを注釈することで、タスクの入出力を定義できる。

- 入力
    - [@Input](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/Input.html)
    - [@InputFile](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/InputFile.html)
    - [@InputFiles](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/InputFiles.html)
    - [@InputDirectory](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/InputDirectory.html)
- 出力
    - [@OutputFile](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/OutputFile.html)
    - [@OutputFiles](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/OutputFiles.html)
    - [@OutputDirectory](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/OutputDirectory.html)
    - [@OutputDirectories](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/OutputDirectories.html)


# 標準で用意されているタスク
よく利用しそうな処理については、標準でタスククラスが用意されている。
例えば、次のようなタスクがある。

- [Copy](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html)
- [Delete](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Delete.html)
- [Sync](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Sync.html)
- [Exec](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html)
- [Zip](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html)

## Copy
```gradle:build.gradle
task foo(type: Copy) {
    from "fromDir"
    into "toDir"
}
```

```bash:実行結果
> tree /f fromDir
...
│  hoge.txt
│
└─sub
        foo.txt

> gradle foo
...

> tree /f toDir
│  hoge.txt
│
└─sub
        foo.txt
```

[Copy](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html) タスクは、ファイルやディレクトリのコピーを実行する。

コピーの方法の設定には、 `Copy` が実装している [CopySpec](https://docs.gradle.org/current/javadoc/org/gradle/api/file/CopySpec.html) が提供している API を使用する。
`CopySpec` にはコピー対象の絞り込みやコピー先を指定するための様々なメソッドが用意されており、かなり柔軟な設定ができるようになっている。

**[from(Object...)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:from(java.lang.Object[]))**

```gradle:build.gradle
task foo(type: Copy) {
    from "fooDir", "barFile"
    from file("fizzDir"), file("buzzFile")
    ...
}
```

コピー元のファイルやディレクトリを指定する。
引数で複数指定することも、 `from()` 自体を複数回呼ぶこともできる。

引数で渡した値は最終的に `Project` の [files(Object...)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:files(java.lang.Object[])) メソッドに渡される。
したがって、文字列のパスや `File` オブジェクトなど、様々な値を渡すことができる。

**[from(Object, Closure)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:from(java.lang.Object,%20groovy.lang.Closure))**

```gradle:build.gradle
task foo(type: Copy) {
    from("fromDir") {
        include "**/*.txt"
    }
    ...
}
```

`from()` の第二引数にクロージャを渡すことができる。
このクロージャの `delegate` は `CopySpec` になっており、 `from()` で指定したディレクトリ以下だけを対象にさらにコピーの条件を絞ることができるようになっている。
ここでは、 `include()` でコピーするファイルを `*.txt` だけに絞っている。

**[into(Object)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:into(java.lang.Object))**

```gradle:build.gradle
task foo(type: Copy) {
    into "toDir"
    ...
}
```

コピー先のディレクトリを指定する。


**[include(String...)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:include(java.lang.String[]))**

```gradle:build.gradle
task foo(type: Copy) {
    include "*.txt", "**/*.xml"
    ...
}
```

Ant 形式のパターン指定でコピー対象に含める条件を指定できる。

**[exclude(String...)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:exclude(java.lang.String[]))**

```gradle:build.gradle
task foo(type: Copy) {
    exclude "**/*.class", "**/*.bk"
    ...
}
```

こちらは、同じく Ant 形式のパターンでコピーから除外する条件を指定できる。

**[rename(Closure)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:rename(groovy.lang.Closure))**

```gradle:build.gradle
task foo(type: Copy) {
    from "fromDir"
    into "toDir"
    rename { name ->
        name.toUpperCase()
    }
}
```

`rename()` を使用すると、コピーするときにファイルの名前を変更できる。
クロージャを受け取る `rename()` の場合は、クロージャの引数にコピー前のファイル名（`String`）が渡される。
そして、クロージャが返した値がコピー後のファイル名として利用される。

上記実装の場合、元ファイル名を全て大文字にしたファイル名がコピー先のファイル名に利用される。

**[rename(String, String)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:rename(java.lang.String,%20java.lang.String))**

```gradle:build.gradle
task foo(type: Copy) {
    from "fromDir"
    into "toDir"
    rename(/(.*)\.([a-z]+)/, '$1_copy.$2')
}
```

引数に `String` を２つ受け取る `rename()` は、第一引数に正規表現を、第二引数にリネーム後の式を渡す。

第一引数の正規表現でグループとして定義した部分（`()` で括った部分）は、第二引数の式では `$n` で参照できる（`n` は 1 からの連番）。

上の例の場合、１つ目のグループが `(.*)` で、拡張子より前のファイルのベース名を指している。
２つ目のグループは `([a-z]+)` の部分で、拡張子の部分を指している。

そして、 `$1_copy.$2` とリネーム後の式で指定することで、ファイルのベース名の末尾に `_copy` をつけた名前にリネームしてコピーしている。

実際に動かすと、次のようになる。

```bash:実行結果
> 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)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:copy(groovy.lang.Closure)) というメソッドが用意されている。

引数のクロージャの `delegate` は `CopySpec` インターフェースを実装しており、 `Copy` タスクと同じ要領でコピーの対象を指定できるようになっている。

```gradle:build.gradle
task foo {
    doFirst {
        copy {
            from "fromDir"
            into "toDir"
            include "**/*.txt"
        }
    }
}
```

単一のタスクの中でいくつかのコピー処理を実行したい場合は、この `Project` の `copy()` メソッドを使う方法もある。

## Delete
```gradle:build.gradle
task foo(type: Delete) {
    delete "targetDir/aaa.txt", "targetDir/hoge"
}
```

```text:実行結果
> 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](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Delete.html) タスクを使うと、ファイルやフォルダの削除ができる。

`Delete` クラスは [DeleteSpec](https://docs.gradle.org/current/javadoc/org/gradle/api/file/DeleteSpec.html) インターフェースを実装している。
このインターフェースで定義されている [delete(Object...)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Delete.html#org.gradle.api.tasks.Delete:delete(java.lang.Object[])) メソッドで削除対象を指定する。
引数で指定した値は、 `Project#files(Object...)` メソッドに渡されるので、文字列や `File` など様々な型で指定できる。

`Delete` 自体には、「特定のディレクトリの、特定のパターンのファイル」を指定できるような API は用意されていない。
しかし、前述のように `delete()` に渡す値は `Project#files()` に渡されるので、 `FileCollection` を渡すこともできる。
つまり、次のような感じで指定することもできる。

```gradle:build.gradle
task foo(type: Delete) {
    delete fileTree(dir: "targetDir", include: "hoge/**/*.txt")
}
```

```text:実行結果
> 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)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:delete(org.gradle.api.Action)) というメソッドが `Project` に用意されている。

クロージャの `delegate` は `DeleteSpec` を実装しているので、 `Delete` タスクと同じ要領で削除対象を指定できる。

```gradle:build.gradle
task foo {
    doFirst {
        project.delete { delete "targetDir" }
    }
}
```

ここで注意なのが、 `delete()` メソッドを呼ぶときは必ず `project.delete()` というふうに `project` を前につけないといけない点。

```gradle:build.gradle
task foo {
    doFirst {
        project.delete {
            println "project.delete.delegate=${delegate.class}"
        }

        delete {
            println "delete.delegate=${delegate.class}"
        }
    }
}
```

```text:実行結果
> 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
```gradle:build.gradle
task foo(type: Sync) {
    from "fromDir"
    into "toDir"
    include "**/*.txt"
}
```

```text:実行結果
> 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](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Sync.html) タスクは、 `into` で指定したディレクトリを `from` で指定したディレクトリと同じ状態に更新する。
`Sync` は `CopySpec` を実装しているので、 `Copy` タスクと同じ要領でコピーの方法を制御できる。

タスク実行前に `toDir` に存在した `hoge.txt` が削除されている。
`Sync` による同期は、**デフォルトではコピー先のファイルやフォルダを全て一旦削除してから行われる**。

同期先にだけ存在するファイルやフォルダで削除してほしくないモノがある場合は、 [preserve](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Sync.html#org.gradle.api.tasks.Sync:preserve(org.gradle.api.Action)) を指定する。

```gradle:build.gradle
task foo(type: Sync) {
    from "fromDir"
    into "toDir"
    include "**/*.txt"
    preserve {
        include "hoge.txt"
    }
}
```

```text:実行結果
> 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)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:sync(org.gradle.api.Action)) メソッドも `Project` に用意されている。

ただし、このクロージャの `delegate` が実装しているのは `CopySpec` インターフェースになる。
つまり、 `preserve` は指定できない（リファレンスでは `preserve` が指定可能と書かれているが、実際に書くとエラーになる）。

```gradle:build.gradle
task foo {
    doFirst {
        project.sync {
            from "fromDir"
            into "toDir"
            include "**/*.txt"
        }
    }
}
```

`sync()` も、 `sync(Action)` しかないので、 `delete()` 同様 `project` の修飾をつけないとエラーになる。


## Exec
```gradle:build.gradle
task foo(type: Exec) {
    workingDir "workDir"
    environment MESSAGE: "Hello World!!"
    commandLine "cmd", "/c", "echo", "%MESSAGE%", ">", "hello.txt"
}
```

```text:実行結果
> gradle foo
...
BUILD SUCCESSFUL in 5s

> type workDir\hello.txt
Hello World!!
```

[Exec](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html) タスクを使うと、任意のコマンドを実行できる。
`Exec` は [ExecSpec](https://docs.gradle.org/current/javadoc/org/gradle/process/ExecSpec.html) インターフェースを実装しており、そこに設定用のメソッドが定義されている。

[commandLine(Object...)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html#org.gradle.api.tasks.Exec:commandLine(java.lang.Object[])) で、実行したいコマンドを指定する。
Linux の場合は直接実行したいコマンドから始めればいいが、 Windows の場合は `cmd /c <実際に実行したいコマンド>` と続ける必要がある。

[workingDir(Object)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html#org.gradle.api.tasks.Exec:workingDir(java.lang.Object)) で実行時のワーキングディレクトリを指定できる。

[environment(Map)](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html#org.gradle.api.tasks.Exec:environment(java.util.Map)) で、コマンドを実行しているサブプロセス内での環境変数を設定できる。

### Project の exec() メソッド
```gradle:build.gradle
task foo {
    doFirst {
        exec {
            commandLine "cmd", "/c", "echo", "Hello World!!"
        }
    }
}
```

`Project` の [exec(Closure)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:exec(groovy.lang.Closure)) メソッドを使えば、 `Exec` と同じ要領でコマンドを実行できる。

クロージャの `delegate` は `ExecSpec` となっている。

## Zip
```gradle:build.gradle
task foo(type: Zip) {
    baseName = "foo"
    from "fromDir"
    destinationDir = file("toDir")
    include "**/*.txt"
}
```

```text:実行結果
> 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](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html) タスクを使うと、任意のディレクトリを zip で圧縮できる。

`Zip` は `CopySpec` インターフェースを実装しているので、 `Copy` タスクと同じように対象を絞り込むことができる。

ただし、出力先は [destinationDir](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle.api.tasks.bundling.Zip:destinationDir) プロパティで指定する。
このプロパティは `Object` ではなく `File` なので注意。
なお、 `base` プラグインを適用している場合、 `destinationDir` はデフォルトで `build/distributions` になる。
`base` プラグインが適用されていない場合（基本ありえないが。。。）、 `destinationDir` の指定は必須になる。

zip ファイルの名前は、いくつかのプロパティに設定された値が連結されてつくられる。

```gradle:build.gradle
task foo(type: Zip) {
    baseName = "baseName"
    appendix = "appendix"
    version = "version"
    classifier = "classifier"
    extension = "extension"

    from "fromDir"
    destinationDir = file("toDir")
    include "**/*.txt"
}
```

```text:実行結果
> gradle foo
...
BUILD SUCCESSFUL in 5s

> dir /b toDir
baseName-appendix-version-classifier.extension
```

ファイル名全体は、次のようにして各プロパティをハイフンでつなげて作られる。
（設定されていない分のプロパティは無視される(`extension` が未設定の場合は `"zip"` になる)）

[baseName](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle.api.tasks.bundling.Zip:baseName)-[appendix](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle.api.tasks.bundling.Zip:appendix)-[version](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle.api.tasks.bundling.Zip:version)-[classifier](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle.api.tasks.bundling.Zip:classifier).[extension](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle.api.tasks.bundling.Zip:extension)

ファイル名全体そのものを一気に指定したい場合は、 [archiveName](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle.api.tasks.bundling.Zip:archiveName) プロパティを指定する。

なお、 `Zip` タスクによって生成される zip ファイルのパスは [archivePath](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html#org.gradle.api.tasks.bundling.Zip:archivePath) プロパティで確認できる。

### Project の zipTree() メソッド
残念ながら？ `Project` に `zip()` というメソッドは存在しない。

代わりではないが、逆に zip の内容を抽出するための [zipTree(Object)](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:zipTree(java.lang.Object)) メソッドが用意されている。
引数で対象の zip ファイルのパスを指定すると、その zip の内容の情報を保持した `FileTree` が返される。

```gradle:build.gradle
task foo {
    doFirst {
        def zip = zipTree("toDir/foo.zip")
        zip.each { println it }
    }
}
```

```text:実行結果
> 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 の解凍を再現できる。

```gradle:build.gradle
task foo {
    doFirst {
        copy {
            from zipTree("toDir/foo.zip")
            into "build/unzip"
        }
    }
}
```

これで、 zip の内容が `build/unzip` ディレクトリの下に解凍される。

# buildSrc
Gradle には、ビルドスクリプトで使用するクラスを配置するための専用のプロジェクトが用意されている。

```text:フォルダ構成
|-build.gradle
`-buildSrc/
  `-src/main/groovy/
    `-sample/
      `-HogeTask.groovy
```

ルートの `buildSrc` ディレクトリが、専用のプロジェクトになる。
このプロジェクトの `src/main/groovy` ディレクトリの下に、自作のタスクやプラグインのコードを配置できる。

```groovy:HogeTask.groovy
package sample

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

class HogeTask extends DefaultTask {

    @TaskAction
    def hoge() {
        println("Hello Hoge.")
    }
}
```

```gradle:build.gradle
task foo(type: sample.HogeTask)
```

```text:実行結果
> gradle foo
Hello Hoge.
```

`buildSrc` にソースコードが存在すると、タスクを実行するときに自動的にコンパイルが行われ、ビルドスクリプトで利用できるようになる。

# プラグイン
カスタムタスクを使うと、タスクのロジックをあらかじめ定義しておき、複数の箇所で設定値だけを切り替えて使い回すことができるようになる。
しかし、カスタムタスクはあくまで単一のタスクであり、タスクの生成や細かい設定はすべて利用側で指定しなければならない。

複数のタスクの生成や、タスク間の依存関係などの定義など、より広範囲な処理を共通化して使い回しできるようにするためには、**プラグイン**という仕組みを利用する。

```text:フォルダ構成
|-build.gradle
`-buildSrc/
  `-src/main/groovy/
    `-sample/
      `-FooPlugin.groovy
```

```groovy: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!!") }
        }
    }
}
```

```gradle:build.gradle
apply plugin: sample.FooPlugin
```

```text:実行結果
> gradle hello
FOO!!
```

プラグインは、 [Plugin](https://docs.gradle.org/current/javadoc/org/gradle/api/Plugin.html) インターフェースを実装して作成する（型引数は `Project` にしておく）。

`apply(T)` メソッドを実装し、その中にプラグインの処理を記述していく。
記述する内容は、普通に `build.gradle` で記述していた内容と同じような感じで問題ない。
ただし、 `build.gradle` の場合は暗黙的に `Project` への委譲が行われるようになっていたので `task()` などを直接呼べていたが、ここでは暗黙的な委譲は行われないので、明示的に `Project` のメソッドを呼び出す形で記述する必要がある。

作成したプラグインは、他のプラグインと同じように `apply` で読み込むことができる。

## プラグインの設定値
プラグインに設定値を渡したい場合は、拡張オブジェクトの仕組みを利用する。

```groovy:FooPlugin.groovy
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](https://docs.gradle.org/current/javadoc/org/gradle/api/plugins/ExtensionContainer.html) の `create()` メソッドを使用する。
`ExtensionContainer` は、 `Project` の [extensions](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:extensions) プロパティから取得できる。

`create()` メソッドは、第一引数に拡張オブジェクトの名前を、第二引数に拡張オブジェクトの型を渡す。
ここでは、名前に `"foo"` を指定し、型に `FooPluginExtension` を指定している。

`create()` の戻り値は第二引数で指定したクラスのオブジェクトになっており、このオブジェクトを介してビルドスクリプトで指定された設定値にアクセスできる。

生成された拡張オブジェクトは `Project` にプロパティとして追加されており、 `create()` のときに指定した名前で参照できるようになっている。

```gradle:build.gradle
apply plugin: sample.FooPlugin

foo.message = "Hello Foo!!"
```

```text:実行結果
> gradle hello
Hello Foo!!
```

拡張オブジェクトは設定ブロックが利用できるようになっているので、次のように記述することもできる。

```gradle:build.gradle
apply plugin: sample.FooPlugin

foo {
    message = "Hello Foo!!"
}
```

設定値が複数存在する場合は、設定ブロックを利用して記述を簡潔にすることができる。

### 拡張オブジェクトのコンストラクタ引数
拡張オブジェクトのコンストラクタで引数を受け取るようにしたい場合は、次のように記述する。

```groovy:FooPlugin.groovy
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()` の第三引数は可変長引数になっており、ここで指定した値はそのまま拡張オブジェクトのコンストラクタに渡されるようになっている。

## 拡張オブジェクトで設定した値をタスクの設定値に利用する
```groovy:FooPlugin.groovy
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` を設定している。

```gradle:build.gradle
apply plugin: sample.FooPlugin

foo {
    inputFile = "fromDir/aaa.txt"
    outputDir = "build"
}
```

これを実行すると、次のようにビルドは失敗する。

```text:実行結果
> 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'.
...
```

これだけだと、ちょっと何を言っているかわからないが、次のようなデバッグ用の出力を追加してみると何が起こっているかわかってくる。

```groovy:FooPlugin.groovy
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
}
```

```groovy:build.gradle
println "FooPlugin 適用前"
apply plugin: sample.FooPlugin
println "FooPlugin 適用後"

println "foo 拡張オブジェクトに設定値を指定"
foo {
    inputFile = "fromDir/aaa.txt"
    outputDir = "build"
}
```

```text:実行結果
> 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` を利用すると回避できる。

```groovy:FooPlugin.groovy
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)
    }
}
```

```gradle:build.gradle
apply plugin: sample.FooPlugin

foo {
    inputFile = "fromDir/aaa.txt"
    outputDir = "build"
}
```

```text:実行結果
> gradle copyFile
...
BUILD SUCCESSFUL in 6s
```

`inputFile`, `outputDir` プロパティの型に [Property](https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Property.html) という型を使用している。

`Property` のインスタンスは [ObjectFactory](https://docs.gradle.org/current/javadoc/org/gradle/api/model/ObjectFactory.html) の `property(Class)` メソッドで生成できる。
`ObjectFactory` は、 `Project` の `getObjects()` メソッドから取得できる。

`Project` の `file()` などは、この `Property` 型をサポートしているのでそのまま渡せているが、中身の値が必要な場合は `get()` メソッドを使えば取得できる。
また、 `set()` メソッドで値を変更することもできる。

ところで、上の例では `Property` 型であるはずのプロパティに対して、代入演算子で文字列（`String` 型）を設定している。
普通であれば、型が違うためにこのような記述はエラーになる。

これは、 Gradle が `Property` 型のプロパティに対してセッターメソッドを自動的に生成してくれているために実現できている。

```gradle:build.gradle
apply plugin: sample.FooPlugin

foo {
    inputFile = "fromDir/aaa.txt"
    outputDir = "build"

    println delegate.class.methods.find { it.name == "setInputFile" }
}
```

```text:自動生成されているPropertyのセッターメソッド
> gradle copyFile
...
public void sample.FooPluginExtension_Decorated.setInputFile(java.lang.Object)
...
```

この代入演算子は、自動生成された `setInpputFile(Object)` メソッド呼び出しのシンタックスシュガーとなっている。

この `Property` 型はカスタムタスクのプロパティでも利用でき、セッターの自動生成もサポートされている。

```groovy:CustomCopyTask.groovy
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
        }
    }
}
```

```gradle:build.gradle
task foo(type: sample.CustomCopyTask) {
    inputFile = "fromDir/aaa.txt"
    outputDir = "build"
}
```

プロパティをコレクションで宣言したい場合は、 [ListProperty](https://docs.gradle.org/current/javadoc/org/gradle/api/provider/ListProperty.html) や [SetProperty](https://docs.gradle.org/current/javadoc/org/gradle/api/provider/SetProperty.html) といったサブタイプが用意されている。
いずれも `ObjectFactory` にファクトリメソッドが用意されているので、そこからオブジェクトを生成できる。

## ライフサイクルタスク
Gradle には**ライフサイクルタスク**というものが用意されている。

ライフサイクルタスクは、それ自身は処理を行わない。
代わりに、何らかの概念を表現し、他のタスクに依存することで役割を果たす。

たとえば、 [Base プラグイン](https://docs.gradle.org/current/userguide/base_plugin.html) には [check](https://docs.gradle.org/current/userguide/base_plugin.html#sec:base_tasks) というタスクが定義されている。
この `check` タスク自体は何もしないが、「プロジェクトの検証処理を行う」という概念を表現している。

あるプラグインが検証処理を行うタスクを追加する場合、 `check` の依存関係にその検証タスクを追加しておく。
すべてのプラグインがこのように実装されていれば、 `check` を実行するだけですべての検証処理が動くようになる。

例えば、 [Java プラグイン](https://docs.gradle.org/current/userguide/java_plugin.html#lifecycle_tasks) は、 `test` タスクを `check` の依存関係に追加している。
これにより、 `check` を実行すれば `test` も実行されることになる。

# 参考
- [Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築 | 綿引 琢磨, 須江 信洋, 林 政利, 今井 勝信 |本 | 通販 | Amazon](https://www.amazon.co.jp/Gradle%E5%BE%B9%E5%BA%95%E5%85%A5%E9%96%80-%E6%AC%A1%E4%B8%96%E4%BB%A3%E3%83%93%E3%83%AB%E3%83%89%E3%83%84%E3%83%BC%E3%83%AB%E3%81%AB%E3%82%88%E3%82%8B%E8%87%AA%E5%8B%95%E5%8C%96%E5%9F%BA%E7%9B%A4%E3%81%AE%E6%A7%8B%E7%AF%89-%E7%B6%BF%E5%BC%95-%E7%90%A2%E7%A3%A8/dp/4798136433/ref=sr_1_1?ie=UTF8&qid=1543925366&sr=8-1&keywords=gradle%E5%BE%B9%E5%BA%95%E5%85%A5%E9%96%80)
- [Build Lifecycle](https://docs.gradle.org/current/userguide/build_lifecycle.html)
- [Authoring Tasks](https://docs.gradle.org/current/userguide/more_about_tasks.html)
- [Lazy Configuration](https://docs.gradle.org/current/userguide/lazy_configuration.html)