Gradle Watch Plugin を作ってみた

今年も残りわずかとなりましたが、今年最初となるエントリーは 『G* Advent Calendar 2013』の12日目です。

皆さん Gradle 使ってますか?

自分はと言うと…
残念ながら、今年も業務への本格導入はできなかったので、 来年こそは導入するぞという意気込みも込めて、Gradle プラグインを作ってみました。

今回作成した Gradle プラグインですが、簡単に言ってしまうと grunt-contrib-watch のようなプラグインです。というかパクリです。
grunt-contrib-watch は ファイルが変更されたタイミングで任意のタスクを実行する GruntJavaScript 製ビルドツール)用のプラグインです。

最近、G* つながり?で Grunt にも少し手を出し始めているのですが、(まだ、Gradle さえ本格導入できていないのに) その中でも grunt-contrib-watch プラグインがとっても便利。
早速、Gradle でも同じプラグインがないか探してみたのですが、残念ながら見つけることができませんでした。
Java プログラマIDE 前提だし、こういったプラグインの需要はないのかなとも 思いましたが、そんなことは気にせず、ないなら作っちゃえばいいんじゃねと言うことで、さくっと作ってみました。

プラグインの使い方の前に、まずはいろいろと言い訳…
時間がなくてかなり適当な作りになっています。(今後、ちゃんとしていく予定)
また、Java7 の NIO.2 を使っているので Java7 以上でないと動きません。
もちろん、良い子の皆さんは Java6 とか使ってないですよね。
もしかして、良い子はもうすでに Java8 使ってるのかな?
あと、Mac OS X 使ってるみなさん、(私もその一人ですが)残念ながら、WatchService のファイル更新の通知が遅いので、動作がモサッとしていてちょっとイラっときます。

と言うことで、Gradle Watch Plugin の使い方ですが とっても簡単です。
まず、このプラグインを利用したいプロジェクトの build.gradle に

buildscript {
    repositories { maven { url 'http://bluepapa32.github.io/maven/' } }
    dependencies { classpath 'com.bluepapa32.gradle.plugins:gradle-watch-plugin:0.1.0' }
}

apply plugin: 'watch'
を追加して、Gradle Watch Plugin を適用します。あとは
watch {

    java {
        files files('src/main/java')
        tasks 'compileJava'
    }

    resources {
        files fileTree(dir: 'src/main/resources',       // FileTree で指定
                       include: '**/*.properties')
        tasks 'processResources'
    }

    hoge {
        files files('foo/bar', 'foo/boo/hoo.txt')       // 複数のディレクトリを監視
        tasks 'hogehoge', 'hugohugo'                    // 複数のタスクを実行
    }
}
watch ブロック内に 任意の名前を付けたブロック(watch ブロック内でユニークであれば何でも可)を定義して、そのブロック内に
名前 説明
files 監視したいディレクトリ または ファイル FileCollection
tasks ファイルが変更された際に実行するタスク String[]
を定義して、
gradle watch
と実行するだけで、常駐プロセスがファイルを監視して、変更があったタイミングで指定したタスクを実行してくれるようになります。
上の例の場合、src/main/java 配下のファイルが更新されると compileJava タスクが実行され、src/main/resources 配下の プロパティファイルが更新されると processResources タスクが実行されます。

もし、興味があれば、ぜひ使ってみてください。
ちなみにソースは GitHub に公開しています。

せっかくなので、今回プラグインを作成するにあたり、いくつか新しい機能を使ってみたので簡単に紹介したいと思います。
その前に、Gradle プラグインの基本的な作り方ですが、

がかなり参考になるので、プラグインを作成する場合は一読することをおススメします。

今回使ってみた新しい機能(もう新機能ではないかもしれませんが...)は

の2つです。

extension オブジェクト

まず、extension オブジェクトですが、ざっくり言うと独自でDSLを拡張できる仕組みです。
例えば、上記の watch ブロックは この機能を使って追加したものです。

更に 今回の場合、watch ブロック内部で自由にブロックを追加できるのですが、これ は NamedDomainObjectContainer というクラスを使って実現しています。

以下がそれを実現しているコードです。たったこれだけです。とっても簡単ですね。

class WatchPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.watch = project.container(WatchTarget) { name ->
            project.extensions.create(name, WatchTarget, name)
        }
        ...
    }
}

NamedDomainObjectContainer は Project クラスの container メソッドで生成することができます。
今回の場合 watch が NamedDomainObjectContainer に対応しています。
container メソッドの引数に指定している型が NamedDomainObjectContainer内の要素の型、 その後ろのクロージャが実際に NamedDomainObjectContainer 内の要素を生成する部分になります。
今回の場合、watch ブロック内の java, resources, hoge を順番に name 変数で受け取り、 クロージャ内で WatchTarget を生成しています。
もちろん生成された WatchTarget には files や tasks で定義した値が勝手に設定されます。
たったこれだけで自由にDSLを拡張できるなんて、便利な世の中になりましたね。

Gradle tooling API

もう一つ利用した機能が Gradle tooling API ですが

ProjectConnection connection = GradleConnector.newConnector()
    .forProjectDirectory(new File("someProjectFolder"))
    .connect();

try {
    connection.newBuild().forTasks("taskA", "taskB").run();
} finally {
    connection.close();
}
のように書くだけで、Gradle以外の(例えば IDE みたいな)アプリケーションから直接 Gradle のタスクが実行できるようになるという便利な機能です。
今回は Gradle プラグインから実行しているのでアレですが…

と言うことで…
今年の冬休みは みなさんも Gradle プラグインを作ってみてはいかがでしょうか?

明日は あの きょんくん が再び登場です。
それでは 少し早いですが 良いクリスマスを!!