grx :「Gradleのタスク定義のあれこれ」のサンプルコードを実行するためのシェルスクリプト


@opengl-8080 さんが5年前にQiitaに投稿したGraleに関する記事３本を読んだ。
- [Groovyを知らない人のためのbuild.gradle読み書き入門, opengl-8080, QIITA](https://qiita.com/opengl-8080/items/a0bb31fb20cb6505188b)
- [Gradleを完全に理解した人が、何も分からなくなるための第一歩, opengl-8080, QIITA](https://qiita.com/opengl-8080/items/c482998fa15ce738e2ba)
- [Gradleのタスク定義のあれこれ, opengl-8080, QIITA](https://qiita.com/opengl-8080/items/0a192b62ee87d8ac7578)

わたしはGradleを何年も使っているけれど、とりあえず動いたで立ち止まっていた。実はbuild.gradleがよく分かっていなかった。今回、@opengl-8080　さんの記事を読んで目から鱗が三枚ぐらい落ちた。

## 解決したい問題

[「Gradleのタスクの定義のあれこれ」](https://qiita.com/opengl-8080/items/0a192b62ee87d8ac7578) （これを以下で「あれこれ」と略記する）を読んでいて困ったことがありました。「あれこれ」にはGradleビルドスクリプトのサンプルが（なんとまあ）62個掲載されていた。わたしはサンプルコードを見ながらエディタでコードを書きGradleで実行してどのように動作するかを確かめた。「あれこれ」はそれらサンプルに `build.gradle` という同一のラベルを付けて掲載していた。しかし中身の違う62個のビルドスクリプトを同じ `build.gradle` という名でファイル保存することはできない。ひとつひとつに固有のファイル名を付けて保存するしかない。だからわたしはビルドスクリプトをこんなふうに作った。

ファイル名: `25_fileTree.gradle`
```
task foo {
    doFirst {
        FileTree tree = fileTree("src")
        tree.each { println it }
    }
}
```

この調子でビルドスクリプトを作っていったら、最終的に以下のファイルができた。

```
01_task_foo.gradle
02_doFist_doLast.gradle
03_configure_task_with_closure.gradle  
04_build_lifecycle.gradle  
04_task_dependsOn_other.gradle  
05_reference_to_task.gradle  
06_task_dependsOn_other.gradle  
07_task_runs_only_once.gradle  
08_dependsOn_inside_configuration_block.gradle  
09_dependsOn_as_task_argument.gradle  
10_sequence_control_not_good.gradle  
11_sequence_control_by_dependsOn.gradle  
12_sequence_control_mustRunAfter.gradle  
13_finalizedBy.gradle  
14_onlyIf.gradle  
15_onlyIf_with_finalizedBy.gradle  
21_file.gradle  
22_files.gradle  
23_files_asPath.gradle  
24_files_filter.gradle  
25_fileTree.gradle  
26_fileTree_include_as_closure.gradle  
27_fileTree_include_as_Map.gradle  
31_task_without_inputs_outputs.gradle  
32_task_with_inputs_outputs.gradle  
33_task_inputs_declaration.gradle  
34_task_outputs_declaration.gradle  
41_tasks_addRule.gradle  
42_clean_rule.gradle  
51_custom_task_within_buildscript.gradle  
52_custom_task_input.gradle  
53_custom_task_input_as_Object.gradle  
54_annotations_for_Input_Output.gradle  
61_Copy_task.gradle  
62_Copy_from_Object.gradle  
63_Copy_from_Object_Closure.gradle  
64_Copy_into_Object.gradle  
65_Copy_include.gradle  
66_Copy_exclude.gradle  
67_Copy_rename_closure.gradle  
68_Copy_rename_String_String.gradle  
69_copy_method.gradle  
71_Delete_task.gradle  
72_Delete_fileTree.gradle  
73_delete_method_of_Project.gradle  
74_Sync_task.gradle  
75_Sync_preserve.gradle  
76_sync_method_of_Project.gradle  
77_Exec_task.gradle  
78_exec_method_of_Project.gradle  
79_Zip_task.gradle  
80_zipTree.gradle  
81_unzip.gradle  
85_buildSrc.gradle  
86_Plugin.gradle  
87_Plugin_with_Extension.gradle  
88_Plugin_with_Extension_configured_by_closure.gradle  
89_Plugin_with_Extension_inputFile=null_outputDir=null.gradle  
90_Plugin_with_Extension_using_Property.gradle  
91_CustomCopyTask.gradle  
```

さて、これらビルドスクリプトをgradleで実行したいが、コマンドラインでどのように入力するか?

ひとつのやり方は `--build-file` オプションでファイル名を指定すること。たとえばコマンドラインで次のようにキー入力すればいい。

```
$ ./gradlew -b 25_fileTree.gradle foo
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :foo
/Users/.../src/main/resources/hoge/hoge.xml
/Users/.../src/main/resources/main.xml
/Users/.../src/main/resources/fuga/fuga.xml
/Users/.../src/main/java/hoge/Hoge.java
/Users/.../src/main/java/fuga/Fuga.java
/Users/.../src/main/java/Main.java
```

これでちゃんと動きました。でも、繰り返しgradleを起動するたびに長いファイル名をキー入力するのが面倒だった。なんとかしたい。

## 解決方法

シェルスクリプト `grx` を作りました。

ファイル名: `grx`
```
#!/bin/sh

#f='01_task_foo.gradle'
#f='02_doFist_doLast.gradle'
#f='03_configure_task_with_closure.gradle'
#f='04_build_lifecycle.gradle'
#f='05_reference_to_task.gradle'
#f='06_task_dependsOn_other.gradle'
#f='07_task_runs_only_once.gradle'
#f='08_dependsOn_inside_configuration_block.gradle'
#f='09_dependsOn_as_task_argument.gradle'
#f='10_sequence_control_not_good.gradle'
#f='11_sequence_control_by_dependsOn.gradle'
#f='12_sequence_control_mustRunAfter.gradle'
#f='13_finalizedBy.gradle'
#f='14_onlyIf.gradle'
#f='15_onlyIf_with_finalizedBy.gradle'
#f='21_file.gradle'
#f='22_files.gradle'
#f='23_files_asPath.gradle'
#f='24_files_filter.gradle'

f='25_fileTree.gradle'

#f='26_fileTree_include_as_closure.gradle'
#f='27_fileTree_include_as_Map.gradle'
#f='31_task_without_inputs_outputs.gradle'
#f='32_task_with_inputs_outputs.gradle'
#f='33_task_inputs_declaration.gradle'
#f='34_task_outputs_declaration.gradle'
#f='41_tasks_addRule.gradle'
#f='42_clean_rule.gradle'
#f='51_custom_task_within_buildscript.gradle'
#f='52_custom_task_input.gradle'
#f='53_custom_task_input_as_Object.gradle'
#f='54_annotations_for_Input_Output.gradle'
#f='61_Copy_task.gradle'
#f='62_Copy_from_Object.gradle'
#f='63_Copy_from_Object_Closure.gradle'
#f='64_Copy_into_Object.gradle'
#f='65_Copy_include.gradle'
#f='66_Copy_exclude.gradle'
#f='67_Copy_rename_closure.gradle'
#f='68_Copy_rename_String_String.gradle'
#f='69_copy_method.gradle'
#f='71_Delete_task.gradle'
#f='72_Delete_fileTree.gradle'
#f='73_delete_method_of_Project.gradle'
#f='74_Sync_task.gradle'
#f='75_Sync_preserve.gradle'
#f='76_sync_method_of_Project.gradle'
#f='77_Exec_task.gradle'
#f='78_exec_method_of_Project.gradle'
#f='79_Zip_task.gradle'
#f='80_zipTree.gradle'
#f='81_unzip.gradle'
#f='85_buildSrc.gradle'
#f='86_Plugin.gradle'
#f='87_Plugin_with_Extension.gradle'
#f='88_Plugin_with_Extension_configured_by_closure.gradle'
#f='89_Plugin_with_Extension_inputFile=null_outputDir=null.gradle'
#f='90_Plugin_with_Extension_using_Property.gradle'
#f='91_CustomCopyTask.gradle'

cp $f build.gradle
./gradlew -q $1 $2 $3 $4 $5 $6 $7 $8 $9
```

`grx` が何をしているかは一目瞭然です。変数`f`が示すファイルの中身を `build.gradle` に上書きしてから `gradlew` コマンドを起動する。それだけ。コマンドラインでこんなふうに `grx` を利用します。

```
$ grx foo
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :foo
/Users/.../src/main/resources/hoge/hoge.xml
...
```

`grx` がどのビルドファイルを実行するか？それは `grx` のコードを都度修正して切り替えます。シェル変数 `f` にファイル名を代入する行を取り替える。`grx` のコードにビルドファイルの正しいファイル名をいちど列挙してしまえば、あとは行の先頭のコメント記号 `#` を付けたり外したりすればいい。

## わたしの意見

`grx` を作って何が嬉しかったか。Gradleのビルド・スクリプトのファイル名を `build.gradle` ではない任意の名前にしても構わないという自由が得られたこと。

というのも、**Gradleのビルド・ファイルの名前は `build.gradle` しかあり得ないという思い込みがわたしにはあった**からだ。この思い込みのせいでビルド・ファイルを２つ以上作ることをためらった。1個の　`build.gradle` ファイルの中に `foo` タスクを作った。次に `foo2` タスクを作った。次に `foo3` タスクを作り、 `foo4` タスクを作った。さらに `foo5` タスクを作った。タスク名が重複しないよう気配りしながら。そうこうするうちに　`build.gradle` ファイルは膨らんでぐちゃぐちゃになり、とても読めたものでなくなった。こんなことしなきゃいけないGradleってどうなのよとムカついた。このやり方でGradleを学習するのはわたしにとって苦痛だった。

ふと、わたしは `grx` を思いついた。grxがあれば小分けにしたビルド・スクリプトをいくらでも書くことができる。小分けされた個々のスクリプトが同じタスク名（たとえば`foo`）を繰り返し使っていても問題ない。grxが小分けされたスクリプトをひとつ拾ってbuild.gradleを生成するから実行時にタスク名の重複は起きない。また、ファイル名が `89_Plugin_with_Extension_inputFile=null_outputDir=null.gradle` のように長くても問題ないという点もナイスだ。ファイル名をシェル・スクリプトの中に一度書いておけばよくて、コマンドラインで長いファイル名を繰り返しタイプする手間を無くすることができる。`grx`のおかげでGradleビルド・スクリプトをたくさん書くことが苦でなくなった。むしろ楽しくなった。

もっとも、Javaアプリケーションやライブラリを開発することを目的とするプロジェクトにおいてGradleビルド・スクリプトを沢山たくさん書く必要などない。build.gradleをひとつ書いておしまいだ。ところがGradleそのものを学ぼうと志してチュートリアルを読むことを想像してごらんなさい。たとえば

- [Working with Files](https://docs.gradle.org/current/userguide/working_with_files.html#working_with_files)

とか。この記事にはbuild.gradleのexampleが48本も掲載されている。チュートリアルを読むのに `grx` のようなシェル芸がきっと役に立つだろう。