AntのGroovyタスクを使うときの俺流ベストプラクティス

G* Advent Calendar 2013の12/16担当、@nobusue です。

12/7に続き二度目の登場になりますが、引き続き実務に役立つシリーズでいきたいと思います。といっても、「ビルドはGradleで決まりだよね!」というイマドキの現場ではなく、いまだにAntでがんばっている(ちょっと残念な)現場の方向けです。

最近はMavenやGradleやsbtなんかの新興勢力に押され気味のAnt御大ですが、その安定感からいまだに「Ant以外認めない」という現場も多いと聞きます。(あくまで伝聞ですよ。。。)実際、要件的にはAntで十分というケースも多いでしょう。しかし、やはりAntで無理やりがんばるのはあまり得策ではないケースというのもままあります。例えば、

  1. テンプレートエンジンを利用して設定ファイルを動的に生成したい(単純なプロパティの置換ではすまない、もしくは日本語を含む文字列を置換したい場合など)
  2. 環境に応じて動的にビルドのロジックを切り替えたい(ファイルの有無や、プラットフォームの差異によって挙動を変えたい)
  3. ビルドの中でJavaのクラスを呼びたいが、わざわざカスタムAntタスクを作るのはめんどう

というような場合です。

わたしが今入っている現場では、まさに上記(1)のケースにヒットしてしまいました。(プロパティファイルにUnicodeエスケープした日本語を書いておくと、AntのCopyタスクのfilterでエスケープが解除されてしまい、そのままエスケープなしの状態でプロパティファイルが作られてしまう、、、というややこしい問題です。もう一回native2asciiすればいいんですけど、ダサダサですよね。)

というわけで、今回の内容は「いろいろ大人の事情でGradleに移行できないけど、せめてカスタムタスクぐらいはGroovyで書きたい」という人向けです。たいした内容ではありませんが、意外にまとまった情報がなかったのでまとめておきたいと思います。

AntのGroovyタスクとは?

GroovyのディストリビューションにはAntのカスタムタスクとしてGroovyタスクが内包されています。ですので、Antのクラスパスにgroovy-all-x.y.z.jarを追加し、ビルドスクリプトに以下の定義を追加すればGroovyタスクが利用できるようになります。だまって $ANT_HOME/lib 以下にgroovy-all.jarをぶっこんどくのがおススメです。

<taskdef name="groovy"
 classname="org.codehaus.groovy.ant.Groovy"
 classpath="groovy-all-x.y.z.jar" />

あとは、以下のようにビルドスクリプト内でGroovyが使えるようになります。

<groovy><![CDATA[
  ant.echo level:'info', message:'Hello groovy task!'
]]></groovy>

詳しくは、 The groovy Ant Task とか Antスクリプト内でGroovyを利用する あたりをご参照ください。

モジュール化してカスタムタスクっぽく使う

Groovyタスクは強力ですが、Ant以外受け付けない方に見つかるとお叱りを受ける可能性もあります。ですので、以下のようにしてGroovyコードの部分を隠蔽してしまいましょう。
呼び出し元:

<project name="main">
  <import file="./buildUtil.xml"/>
  <target name="build">
    <antcall target="complexTask"/>
  </target>
</project>

呼び出し先(Groovyタスク):

<project name="util">
  <taskdef name="groovy"
   classname="org.codehaus.groovy.ant.Groovy"
   classpath="groovy-all-x.y.z.jar" />
  <target name="complexTask">
    <groovy><![CDATA[
      ant.echo level:'info', message:'Groovy makes easier!'
    ]]></groovy>
  </target>
</project>

GroovyにはAntBuilderという便利な仕組みが内包されており、Groovyタスクの中からAntタスクを呼び出すこともできますので、Antで用意されているファイル操作なんかはそのまま便利に利用しましょう。

パラメータを渡す

前出のようにモジュール化した場合、当然ながら「呼び出し元タスクからパラメータを渡したい」という要望が出てきます。これ、不思議とサンプルコードが見当たらないのですが、やり方は簡単で以下のようにするだけです。
呼び出し元:

<project name="main">
  <import file="./buildUtil.xml"/>
  <target name="build">
    <antcall target="complexTask">
      <param name="sourcePath" value="./source"/>
      <param name="distPath" value="./dist"/>
    </antcall>
  </target>
</project>

呼び出し先(Groovyタスク):

<project name="util">
  <taskdef name="groovy"
   classname="org.codehaus.groovy.ant.Groovy"
   classpath="groovy-all-x.y.z.jar" />
  <target name="complexTask">
    <groovy><![CDATA[
      def sourcePath = properties['sourcePath']
      def distPath = properties['distPath']
      ant.echo level:'debug', message:"sourcePath: ${sourcePath}"
      ant.echo level:'debug', message:"distPath: ${distPath}"
    ]]></groovy>
  </target>
</project>

要するに、呼び出し側でで渡してやれば、Groovyタスク側ではproperties(マップ)で受け取ることができるということです。分かってしまえばどうということはありませんが、自力で調べると意外に試行錯誤が面倒でしたので、備忘の意味でここで記録に残しておきます。

ログ出力

汎用性の高いタスクができると、幅広く使われるようになります。そうすると、printデバッグではいずれ限界がきますので、早い段階でメッセージのログレベルをきちんと分けておきましょう。
実は既に小出しにしていましたが、

ant.echo level:'debug', message:"sourcePath: ${sourcePath}"

のように、AntBuilder経由でant.echoをログレベル付きで実行すればよいです。(Taskクラスのloggerを取得しようとしたのですが、どうもうまくいかないのでこの方法にしました。)
ログレベルを分けておくと、開発時はdebugを出力して、運用時はwarning以上のみ出力というような使い分けが可能です。ログレベルはAntのコマンドラインオプションで指定できますので、こちらなどを参考にいろいろためしてみてください。

まとめ

Antに疲れたらGroovyタスクで息抜きしましょう。

次は @nagai_masato さんです!