いまさら初めて Ant のビルドファイルを書いたので、逆引きの備忘録を残す

# はじめに
これまで、CI/CD のプロジェクト導入を通じて、各種のビルドプロセスの自動化に関わってきましたが、今になって初めて Ant のビルドファイルを書く機会がありました。

現在、Java のビルドツールでは主に Maven もしくは Gradle が使われています[^1]。そのため、Web上の記事も Maven/Gradle に比べ Ant は比較的少ないようで、欲しい情報にたどり着くのに苦労しました。例えば、Qiita で Maven/Gradle/Ant のタグがついた記事はそれぞれ 932/1723/89 記事でした。桁が違いますね。

ちなみに、Google Trend で過去10年の人気度推移を比較すると以下のようなグラフになりました。
<img width="`00%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229637/3acf8179-9764-fe21-2c7b-610738ec31e5.png">

一応 [公式ドキュメント](https://ant.apache.org/manual/) はありますが、やや検索性に乏しく「こういうことがしたいけど、ドキュメントのどこを調べればよいのか分からない…」という状態に陥る事が多かったです。そこで本記事では、個人的に備忘として残しておきたいトピックを逆引きでまとめます。

[^1]: https://snyk.io/jp/reports/jvm-ecosystem-report-2021/

# ant コマンド実行時に特定のターゲットの実行を除外したい
Gradle における `-x` オプションのような動作をさせたいケースです。Gradle では `gradle build -x test` と実行した場合、build タスクが test タスクに依存していても、test を除外して build を実行することができます。

残念ながら、Ant ではこのようなオプションは用意されておらず、Target に `unless` 属性をつけて制御できるようにする必要があります。
次の例は、init ターゲットに依存する build ターゲットを持つビルドファイルです。

```xml:例
    <target name="build" depends="init">
        <echo message="build" />
    </target>
    <target name="init" unless="skip.init">
        <echo message="init" />
    </target>
```

そして、コマンド実行時には `-D` オプションでプロパティを指定することで、特定のターゲットの実行を除外します。
以下の例では、unless で指定した `skip.init` プロパティを設定することで init ターゲットを除外しています。（設定する値は何でも構いません。）
```terminal: コマンド実行時
$ ant -Dskip.init=true build
```



# Target 要素以外でも if, unless 条件を使って制御したい
Ant 1.9.1 より前のバージョンでは、Target 要素にのみ `if`, `unless` 属性があり、プロパティの設定有無に応じて Target の実行が制御できました。Ant 1.9.1 以降では、全てのタスク及びネストされた要素についても `if`, `unless` 属性を使った条件分岐ができます。

`xmlns:if="ant:if"` と `xmlns:unless="ant:unless"` で `if`, `unless` 名前空間を宣言します。これにより、Target 以外でも if/unless の制御ができるようになります。

`if`, `unless` 名前空間では以下の3条件を使うことができます。
`set` の場合のみ 値 ではなく **プロパティ名** を設定することに注意してください。

|条件|意味|使い方の例|
|---|---|---|
|true|値が true なら true |`if:true="${someproperty}"`|
|brank|値が null または 空 の場合は true|`if:brank="${someproperty}"`|
|set|指定したプロパティが設定されていたら|`if:set="someproperty"`|

```xml: 例
<project name="sample" xmlns:if="ant:if" xmlns:unless="ant:unless">
    <target name="build">
        <echo message="prop1" if:true="${prop1}" /> 
        <echo message="prop2" if:set="prop2" />
    </target>
</project>
```

# ビルドされるOSに応じて条件分岐したい
`Condition` タスクで `os` 条件の `family` 属性を使うことで、Ant が実行された OS を判別することができます。Linux や Windows など OS の種類に応じて異なる処理をさせたい場合に活用できます。

`family` 属性がサポートする値は [ドキュメント](https://ant.apache.org/manual/Tasks/conditions.html) を参照してください。

```xml:例
    <condition property="isWindows">
        <os family="windows" />
    </condition>
    
    <condition property="isLinux">
        <os family="unix" />
    </condition>
```

# 改行文字を使いたい
`\n` を使いたくなるところですが・・・`${line.separator}` を使います。
```xml:例
    <target name="build">
        <echo message="aaa${line.separator}bbb" />
    </target>
```

# FileSet や DirSet の内容を echo で確認したい
`${toString:id名}` の形式で出力することができます。
```xml:例
    <target name="build">
        <fileset id="fileset" dir="." />
        <echo message="${toString:fileset}" />
    </target>
```

内部的には、呼び出された Javaクラスのインスタンスメソッド `toString()` が呼ばれるようです。したがって、FileSet や DirSet 以外でも `toString()` に意味ある出力が実装されているクラスであれば、同様の記法でインスタンスの情報を得ることができるでしょう。

# FileSet や DirSet の結果を1つの文字列として取得したい
`<pathconvert>` タスクを使います。複数のファイルやディレクトリのパスを、`pathsep` 属性で指定したデリミタで結合し、プロパティに出力します。

以下の例では、カレントディレクトリ配下のファイルのパスをカンマ区切りで `paths` プロパティに出力しています。
```xml:例
    <target name="build">
        <pathconvert property="paths" pathsep=",">
            <fileset dir="." />
        </pathconvert>
        <echo message="${paths}" />
    </target>
```
`<pathconvert>` を使うケースの多くでは、 [`<mapper>` 要素](https://ant.apache.org/manual/Types/mapper.html) をネストして、パスの文字列を何かしら変換して使うことが多いと思われます。例えば、特定の階層の文字列をトリムしたり、別の文字列に置換したりなどです。

# Ant で用意されている処理以外の任意の処理を実装したい
`<Script>` タスク を使います。本記事では、例として JavaScript で実装する場合を示します。Java8 から Java14 までは、Javaに組み込みの Nashorn JavaScript エンジンを利用できるため、特別な宣言なく JavaScript で実装できます。

以下の例では、プロパティの取得と設定だけ実施していますが、複雑な文字列処理や、FileSet や DirSet では実現できない特殊なファイル検索なども可能になります。 あくまで、痒いところに手を届かせるためのインタフェースとしてのみ使い、濫用することは避けましょう。
```xml:例
    <target name="build">
        <property name="prop1" value="piyo" />
        <script language="javascript">
            <![CDATA[
                var a = project.getProperty("prop1")
                project.setProperty("prop2", a + a)
            ]]>
        </script>
        <echo message="${prop2}" />
    </target>
```

# タイムスタンプを取得する
`<tstamp>` タスクを使います。 ネストした `<format>` 要素で、タイムスタンプの値を受け取るプロパティ名とタイムスタンプの書式を決めます。`pattern` 属性には Java の [SimpleDateFormat](https://docs.oracle.com/javase/jp/14/docs/api/java.base/java/text/SimpleDateFormat.html) のパターンを使うことができます。詳細は [ドキュメント](https://ant.apache.org/manual/Tasks/tstamp.html) を参照してください。
```xml:例
	<tstamp>
		<format property="datetime" pattern="yyyy-MM-dd-HHmmss"/>
	</tstamp>
```

# JUnit タスクで 失敗/エラー 時に Ant の処理を中断する
`haltonfailure` 属性や `haltonerror` 属性を使います。これらはデフォルトでは無効であり、`yes`/`true`/`on` に設定しない限り、テストで失敗/エラーが発生してもビルドプロセスが正常終了してしまいます。
```xml:例
    <junit printsummary="yes" fork="yes" haltonfailure="yes">
        <test name="my.test.TestCase"/>
    </junit>
```

`<junit>` タスクの中ではなく、複数の処理を一通り実行した後で ビルドプロセスを停止したい場合には `failureproperty` 属性を使うと良いでしょう。`failureproperty` 属性には、テストで 失敗 または エラー が発生した時にセットされるプロパティ名を指定します。これと `<fail>` タスクを組み合わせることで、任意のタイミングでビルドプロセスを停止することができます。

```xml: 例
    <junit failureproperty="junit.failure">
        <!-- テストの設定を記載 -->
    </junit>
    <fail message="TEST FAILED" if="junit.failure"/>
```
# まとめ
意外と、備忘として残したかった項目が少なかった......
すでにだいぶ忘れかけている気がするので、気づいたら足しておきます。