Gradleは主にJVMをターゲットとするJava/Kotlinなどのプロジェクトに使えるビルドツール/ビルドシステムのひとつ。単体のビルドツールとしても使えるほか、AndroidStudioなどでは内部のビルドシステムとしてGradleが採用されている。

Gradle自体はJavaランタイム上で動作するのだが、最大の特徴としてJavaされ動けば例えGradleの本体が無い環境でも動作するという点がある。これにはwrapperと呼ばれる小さなExcutable jarをプロジェクトファイルとしてgitなどで共有させておいて、そのjarが自動的に~/.gralde以下にGradle本体をダウンロードすることで最終的にはGradle本体があたかも最初からあったかのように実行される、という仕組みが働いている。

Gradleは本体を各自ダウンロードして使うことよりもむしろwrapper経由で使われることが想定されているようなのだがこれがどうにも微妙な点がいろいろある。

そんなGradleに対する意見を書き並べた。

  • 別バージョンのwrapperを起動するたびに~/.gralde/wrapper/distが膨張する
  • 不必要に大きなディストリビューション
  • gradle-wrapper.propertiesにかかれたdistを問答無用でダウンロードする
  • daemonの実質ゾンビ化によって削除が妨害される

~/.gradle/wrapper/distが膨張

gradleはあるプロジェクトをビルドするためのgradleのバージョンは同一でなければならないと(ユーザの意図に反して)勝手に決めつけている節がある。

どういうことかというとgradle-wrapper.propertiesにかかれたgradleバージョンが6.1だったとして、~/.gradle/wrapper/dist以下により新しい6.4.1しかなかった場合、gradleはバージョン6.1の数百メガバイトあるディストリビューションを即座にダウンロードして実行する。

いくつかのサンプルプロジェクトをクローンしてきて、それらのgradle-wrapper.propertiesにあるバージョンが全部異なっていたらどうなるだろうか?数百メガバイト×いくつかとなるとギガバイト単位のものが突然./gradle/wrapper/dist以下に出現することになる。gradleはバージョンの更新が比較的頻繁に行われていて、それはそれでOSSとして活発で良いことではあるのだが逆にこういうことは日常的に発生する。

実際のところ、バージョン違っても同じツールなんだから普通コケなくて当然だろうという感覚からも経験上もgradleのバージョン違いによってビルドがコケることはほぼ無い。だからユーザが望む動作としては

  1. とりあえずwarningでも出しつついまローカルにあるGradle本体で実行する
  2. もしビルドがコケたらwarningが原因かもしれない等とユーザに通知する

だろう。とくにローカルにあるバージョンの方がgradle-wrapper.propertiesのバージョンより大きい場合はこれで問題ない。後述のwrapper無しでtask wrapperできない問題も無くなる。

不必要にサイズが大きなディストリビューション

ビルドツールのバージョンを完全にマッチさせること単体ではさして問題にならないことはわかってる。ビルドツールのバージョン違いによる潜在的なエラーの可能性をつぶし、その結果として開発効率を向上させようとする意図はわかるしnpm-scriptとか他でも行われているような動作だ。ユーザ意図との乖離を引き起こしている点はその勝手にダウンロードしているもののサイズと内容だ。

各バージョンのディストリビューションが数百メガバイトになってしまっている原因として、

  • そもそも本体サイズが大きいのに一切分割されていない
  • allとbinに全く同じものが含まれている
  • allにはソースコードやドキュメントが含まれる

という点がある。本体サイズが大きくなるのはある程度仕方ないし、潜在的なエラーでビルドがコケる可能性をつぶすために分割せずに予め全部載せにするのはわかる。だとしても完全に内容が同じものなら結果は同じなんだから個々の構成ファイルの差分を適切にバージョン管理して必要なものを必要な時にダウンロードするという動作にすれば不要なものを重複に重複してローカルに存在させなくて済むし、他のツールを見てもそれは可能だろう。

よりわからないのは同じバージョンにallとbinという2種類のzipがあって、それぞれにその大きな本体が含まれていること。全くおなじ内容のファイルがストレージに2つ存在するのはパソコンの健康上よろしくない。all=bin+etcと分割してbinとetcをそれぞれダウンロードすべき時にダウンロードしてほしいというのが普通のユーザの願いだろう(そうしない理由があるなら教えて欲しいのだけれども)。gradle-wrapper.propertiesのバージョンが同じでもall指定のものとbin指定のものがあれば内容被りの本体部分が重複してダウンロードされローカルに展開される。

さらに、このetcに当たる部分、つまりallにあってbinに無い部分にはgradleのソースコードおよびドキュメント(javadoc)が含まれている。IntelliJ系のIDEはこれらを用いてgradleファイルのLintingや補間が行えるようでIDE側からallをダウンロードすることを進めてくるが、IDEを使わずに直接ローカルにあるソースコードやjavadocを活用するユーザがどれほどいるだろうか?

IDEを経由することが前提なのならばそれらをダウンロードするか否かを決めるのはIDE側であってすなわち個々の開発者がどのIDEを使っているかは個々の開発者によっているからプロジェクト全体に共有されるgradle-wrapper.propertiesのdistributionURLで一元的にallと決めつけるのは違うでしょ(日本語())。しかもbinにしたとしても次allに変更すると重複する。健康に悪い。

問答無用のダウンロード

いやわかってる。ストレージの大容量化が進むこの時代に数百メガバイトくらいの蓄積なんて微々たるものだろうという考えのもとでのことだと。だとしても、数百メガバイトの蓄積を良しとするか否かはGradleが決めるのではなくユーザが決めることだ。もしかしたらユーザがたった数ギガしかないポータブルストレージ上で作業しているかもしれないだろう?

いいや、わかってる。Gradleのほとんどのおいしい機能は利用しているうちに勝手に数百メガバイトのキャッシュは勝手につくるんだからそんな弱小ストレージユーザなんて考慮の範疇に入れなくてよいと。だとしても中身と機能が似通ったzipをほいほいダウンロードすることはユーザの精神衛生上の不健康をもたらす可能性があるのでやはり決めるのはユーザだよ。

しかも、zipでダウンロードして展開することは構わないがダウンロードしたzipが放置されているのも心象が悪い。どうかんがえてもキャッシュだろう?キャッシュはキャッシュとしてしかるべき場所に置くのが礼儀だと思います。

  • これからローカルに無いバージョンx.xをダウンロードするよ?
  • gradle-wrapper.propertiesのdistributionURLを今ありものに書き換えてダウンロードしないこともできるよ
  • どうする?

これだけユーザに問い合わせるくらいIDEならダイアログ1つポチるだけで済むんだから問答無用のダウンロードをまずはやめていただきたい。そうでなくとも設定ファイルくらいあるんだろうからこの問い合わせをするか否かくらいはIDEでもwrapperのjarでも設定できるようにしてくれないかな。IDE側の問題なのかもしらんが小さい実行ファイルがユーザの意図しない大きなファイルを勝手にインターネットから取ってくると考えるとかなりevilだよ。

daemonの実質ゾンビ化によって削除が妨害される

そうだよ。そういう風に思うユーザはgradlewが実行される前に必ずgradle-wrapper.propertiesのdistributionURLのバージョンを確認し、間違えてダウンロードされてしまったらそのつど削除すればいいんだよ。そもそもAndroidStudioやIntelliJなどはプロジェクトを開いたら即gradlewが実行されてもしローカルに無いバージョンがdistributionURLにかかれていたら確認も無くすぐにダウンロードしてくるので、こういうことを考えているユーザにとって間違えてダウンロードされることは頻発する。

定期的に蓄積した./gradle/wrapper/distを掃除すればいいが、それを忘れるから不要なディストリビューションがダウンロードされるたびに削除しておきたいというのがありえるだろう?

そういう時に邪魔をするのがgradle daemonだ。gradleはwrapper経由で起動するなどの性質上一回一回のビルドにおけるオーバーヘッドが大きいというのがあり、その辺りのビルド周りを高速化させるためにデーモンというサーバを立てる。IDEなどで適切にプロセス管理がされていれば単なる高速化の仕組みで別に邪魔にならないが、こいつが./gradle/wrapper/distの特定のバージョンを削除する際に邪魔をする。もうそのバージョンは使っていないにもかかわらずちょっと前に間違ってダウンロードしてデーモンが起動されたディストリビューションが削除できない。

待ってれば勝手に終了して削除出来るようになるんだがこちらは健康被害を受けているのでそんなのは待てない。だいたい、ビルドの円滑化のためにもろもろのユーザへの問い合わせを削っているツールのために、ユーザ自ら初回の実行前にwrapperのバージョンを調べないと手間を喰うというのはどういうことなんだよと。

wrapperは使わないという選択肢

最終手段だ。もうwrapperは使わない。

特定のバージョンのgradle本体をローカルにインストールしておいて、全てのプロジェクトからgradlewとgradlew.bat、gradleフォルダを削除してしまえばずっと全てのプロジェクトでそのバージョンのgradleが使える。IntelliJであればどのgradleを使うかをSettingsから指定できる。新しいバージョンを使いたければそのgradleを更新してやればいい。

しかし願わくば、「ローカルにインストール」はしたくなくて(わがまま)、ただ一つのディストリビューションをこれまでと同じ~/.gradle/wrapper/dist以下に保持したい。なぜならば、macOSとかLinuxならbrewとかrpmとかパッケージ管理があるので問題ないだろうがWindowsだとどこに置くのか/置いたのか管理するのが面倒だから。ちなみにインストーラにしてくれればいいっていう話じゃない。ピンキリだが、Windowsのいらんものいっぱいバンドルしてて勝手なことばっかするインストーラも中にはあってevil。

そういうことがあって選択肢にありつつも全部の環境におけるベストとはいえるのかいなという状態だよ。

まとめ

サンプルプロジェクトを頻繁に動かしているそこのユーザ諸君、./gradle/wrapper/dist以下の容量を一度確認してみよう。そして願わくばgradle-wrapper.propertiesのバージョン指定にlatestとかanyとか5+(5以上)とかの指定方法を導入していただきたい。