この文書は『JavaWorld』2003年7月号(IDGジャパン)に掲載された「初学者必修! Antの基本」の原稿です(掲載記事はこれに若干の修正が入っています)。この文書の著作財産権は(株)IDGジャパンに帰属し、複製や二次利用を行う場合には(株)IDGジャパンの許可が必要です。この文書は『JavaWorld』編集部の了承を得て公開しています。

はじめに

Antは、WebサーバでおなじみのApache Software Foundationの中のひとつのプロジェクトが開発している、JavaによるJavaのためのビルドツールである。Apache Antプロジェクトのドキュメントでは

Apache Ant is a Java-based build tool. In theory, it is kind of like make, without make's wrinkles.
Antは、Javaベースのビルドツールです。理論的には、makeの欠点がないmakeの一種です。

とAntを紹介している(日本語はJa-Jakartaプロジェクトによる翻訳)。これだけでAntがどういうツールであるかイメージできる人もいれば、そうでない人もいるだろうが、確実に言えるのはおよそJavaのコードを書く者にとってAntが間違いなく有用であることだ。おそらくすでにAntを使って、その便利さを享受している人も多いだろう。そういう人は本稿など読み飛ばして、Part2以降の内容を参照してさらにAntを使い込んでいただきたい。本稿ではごく簡単な例を使って、Antというツールが何をしてくれるのかを紹介する。不幸にもこれまでAntを知らなかった、あるいはその名前を知っていても使う機会に恵まれなかった方が、本稿をご覧になってAntに興味を持って、実際にAntを試してみる気になってくれれば幸いだ。ひとたびAntに触れれば二度と手放せなくなること請け合いである。

Antに至るまで

javacにできること

はじめて書いたJavaのコードはやはりリスト1のようなものだっただろうか。

リスト1: HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world");
    }
}

このコードをコンパイルするには

$ javac HelloWorld.java

を叩けばいいことは、ここで改めて記述するまでもない話だが、このようにプログラムを構成するファイルがひとつであれば、コードの作成や修正に伴なうコンパイルの作業でjavacを直接入力することもさほど無茶な話ではない。しかしながら、ただひとつのファイルからプログラムが構成されることはまれである。ソースコードが複数のファイルに分かれてくれば、javacを実行しなければならない回数も増えてくる。

javacにはささやかながらその回数を抑制する機能がある。たとえばリスト2のようなソースコードがあったとしよう。

リスト2: X.java, Y.java
public class X {
    private Y y;

    private void f() {
        y.g();
    }
}

public class Y {
    public void g() {}
}

新規にこのふたつのファイルを作成し、以下のコマンドを実行する。

$ javac X.java

するとX.javaだけでなく、Y.javaもコンパイルが行われることがわかる。これはjavacが単純に指示されたソースファイルをコンパイルするだけでなく

  • コンパイルするソースファイルが使用するクラスやインターフェイスを定義しているクラスファイルやソースファイルを検索する。
  • ソースファイルが見つかったとき、対応するクラスファイルと比較して、ソースファイルが新規に作られたか、またはソースファイルの方が新しいとき、そのソースファイルもコンパイルする。

という振る舞いをするからである。javacのこのような振る舞いを理解していれば、いったんX.class、Y.classを作成したあと、Y.javaだけを修正したときでも

$ javac X.java

によってY.javaのコンパイルが実行されることが推測できるだろう。ソースファイルの数が多くなく、ソースファイル同士の依存関係が単純であれば、特定のソースファイルのコンパイルを実行することで、javacは変更のあったソースファイルに対するコンパイルをおおむね実行してくれる。このようにどのソースファイルを修正したかに関わらず、単一のコマンドの実行によって必要なコンパイルが実行されれば、開発作業は非常に楽になる。

javacのこうした機能はそれなりに有用ではあるが、やはり限界がある。リスト3のようにソースファイルの依存関係がもう一段深くなったとしよう。

リスト3: X.java, Y.java, Z.java
public class X {
    private Y y;

    private void f() {
        y.g();
    }
}

public class Y {
    private Z z;

    public void g() {
        z.h();
    }
}

public class Z {
    public void h() {}
}

3つのソースファイルを新規に作成してから

$ javac X.java

を実行すると、
X.class、Y.class、Z.classが作成されるのはリスト2の場合と同様である。しかしこのあとZ.javaのみを修正したときに

$ javac X.java

を実行してもZ.javaはコンパイルされない。javacが確実に参照してくれるのは直接の依存関係だけであり、X.javaの中にはZというクラスは現れないので、javacはX.javaしかコンパイルを実行しない。この程度の依存関係になっただけで、もうjavacの振る舞いをあてにすることはできない。

いっそのこと毎回すべてのソースファイルをコンパイルするというのもひとつの考え方だろう。

$ javac *.java

を実行すれば、カレントディレクトリにあるすべてのソースファイルがコンパイルされる。これはこれで確実な方法であり、コマンドを入力する手間もかからない。ソースファイルの数が多くなければ十分に現実的な手段であろう。そうはいってもさわってもいないソースコードが毎回コンパイルされるのはやはり非効率。ソース修正・コンパイル・テストの繰り返しがつづくときには、コンパイルにかかる時間はできるだけ短くしたいものである。とくにソースファイルの数が多くなれば、全量コンパイルにかかる時間が馬鹿にならない。修正したソースファイルだけを確実にコンパイルする手段が欲しくなる。

makeの問題点

このような要求は必ずしもJavaに固有のものではなく、分割コンパイルがサポートされる言語には必ずついてまわるものである。したがってその要求に応えるツールも早くから存在する。その代表がmakeである。makeはソースファイルから生成物を生成する規則をファイルに記述しておき、それをもとにコンパイルなどの処理を自動化するツールだ。たとえばリスト3のソースファイルと同じディレクトリにリスト4のようなMakefileを作成したとする(インデントは水平タブで行う)。

リスト4: Makefile
build : X.class Y.class Z.class

.SUFFIXES: .class .java

.java.class:
        javac $*.java

ここで

$ make

を実行すると、目的とする生成物であるX.class、Y.class、Z.classそれぞれについて、ソースファイルとの比較が行われ、生成物が存在しないかソースファイルより古い場合にのみコンパイルが行われる。makeのこの動作は多くの人にとって理想的だ。

ただしmakeの理想的な動作を実現するための設定に問題がないわけではない。リスト4の1行目を見ておわかりの通り、Makefileには生成するクラスファイルのリストが必要だ。つまりソースファイルの追加・削除があるごとにMakefileを修正しなければならない。これはmakeがCのソースファイルを扱うことを考えているためだろう。Cのソースファイルと最終的な生成物のあいだの依存関係はJavaに比べるとはるかに複雑である。個々のファイルごとに生成規則を細かく記述できなければならない。これに対してJavaでは基本的にソースファイルとクラスファイルの一対一の依存関係しかない。makeの仕様はJavaを扱うには少々大げさで、そのため無駄に感じる設定を利用者に求めることになる。

そしてmakeには冒頭で紹介したAntのドキュメントが真っ先に指摘する欠点がある。固有のプラットフォームへの依存である。そもそも黙っていてmakeが実行できるのはUnix系のOSだけだ。加えてMakefileの中にコマンドを直接記述するので、仮にmakeが動く異なるOSにMakefileとソースファイルを持ちこんでも、同一の動作は保証されない。javacくらいならプラットフォームごとの差異は大きくないので問題ないかもしれない。しかしビルドは単にコンパイルだけでなく、生成したファイルをアーカイバで固めてライブラリを作成したり、適切なディレクトリに生成物をコピーしたり、不要になったファイルを削除することなども含まれる。残念ながらこうした処理において多くのプラットフォームで共通に動作するコマンドは存在しない。たとえJavaでプラットフォーム非依存のプログラムを書いても、ビルドにmakeを使うならプラットフォームに依存したMakefileを用意しないといけないのである。

このようにAntが登場するまでのJavaのビルド作業は何がしかの問題を抱えていた。次節以降でそれらの問題に対してAntがどのように応えたかを、実際にAntを使って簡単なJavaのコンパイルを実行するまでの手順を追いながら見ていくことにする。

Antのセットアップ

Antに必要なもの

Ant自身はJavaで記述されている。これはmakeが持っていたひとつ問題に対するAntの解答である。Javaで記述されているということは、JDKが動作する環境であればプラットフォームに関係なく同一のバイナリでほぼ同一の動作を期待できる。

そういうわけでJavaによるJavaのためのビルドツールであるAntの実行にはJDKの1.1以上が必要だ。さらに言うと一部の機能を利用するには1.2以上が望ましく、将来のバージョンでは1.2以上が必要になるとアナウンスされている。できればJDK1.2以上が動作する環境で使いたい。

バイナリ配布ファイルの入手と展開

インストールもどのプラットフォームでも同様の簡単な手順ですむ。Apache Antプロジェクトのバイナリダウンロードページ(http://ant.apache.org/bindownload.cgi)から配布用のアーカイブファイルをダウンロードして、任意のディレクトリに展開すればいい。アーカイブファイルの形式にはzip、tar.gz、tar.bz2があるので、使用する環境に合わせて都合のいい形式をダウンロードする。とくにzip形式ならばjarで展開できるから、どの形式を選んだらいいかわからないときはzipをダウンロードすればいいだろう。本稿を執筆している時点でのAntの最新のバージョンは1.5.2であり、zip形式のバイナリアーカイブファイルはapache-ant-1.5.2-bin.zipとなっている。このファイルを作業用の任意のディレクトリに保存して

$ jar xf apache-ant-1.5.2-bin.zip

を実行すると、apache-ant-1.5.2というサブディレクトリが作成され、その配下にAntの配布セットが展開される。展開後は作成されたapache-ant-1.5.2をディレクトリごとまとめて都合のいい場所(たとえばUnix系のOSの/usr/localのような)に移動してもらってかまわない。もちろんtar.gzやtar.bz2が利用できる環境ならそうした形式を使えばいい。また1.5.2より前のバージョンにおいても、アーカイブファイル名称のバージョン部分が変わるくらいで、基本的なインストールの手順は変わらない。

とはいえインストール手順におけるプラットフォームやバージョンの影響はまったくの皆無ではない。筆者はUnix系OSやWindows系OSでしかJavaを使ったことがなく、そこで使われる階層型ファイルシステムと異なる環境でどのようにアーカイブファイルが展開されるのかわかっていない。あるいは上述の手順どおりというわけにはいかないかもしれない。またWindows系OSについても、いわゆる9x系列ではアーカイブファイルが展開されたときにできるサブディレクトリをそのままの名前で使うと、付属のAnt起動用バッチが正常に動作しない問題が知られている。これは9x系列のコマンドシェルの一部のコマンドが8.3形式でないディレクトリ名を正しくハンドリングできないことに起因する。9x系列のWindowsでAntを利用する場合は、Antを格納するディレクトリ名を8.3形式(たとえばantのような)に変更する必要がある。さらに古いAntのアーカイブファイルは全体を収めるサブディレクトリを作らず、指定したディレクトリの直下に内容が展開されていたので、やはり上述の手順とは少し違う作業をしないといけない。

Antの趣旨を考えて、できるだけ多くの環境に配慮したいのではあるが、残念ながら筆者の能力の欠如がそれを許さない。わかる範囲でプラットフォームやバージョンの差異による影響にも言及するが、知らず知らずUnix系のOSと最新バージョン(1.5.2)のAntを前提にした記述をしてしまうかもしれないことをご容赦いただきたい。

環境変数の設定と動作確認

アーカイブファイルの展開が終わったら表1の環境変数を設定する。

表1: 環境変数の設定
JAVA_HOMEJDKのインストールディレクトリ
ANT_HOMEAntのインストールディレクトリ
PATHANT_HOMEの下のbinを追加

この環境変数の設定は付属のAnt起動用シェル(あるいはバッチ)に必要なものであって、Antそのものの実行に必須というわけではない。環境変数を設定しないでJVMから直接Antを起動する方法も後述する。JVMから起動する方法ならば多くのプラットフォームで実行可能なはずだ。

具体的な環境変数の設定方法は、Unix系のOSでbashなどのBourneシェル系のコマンドシェルを利用している場合は、.profile(あるいはそれに類するファイル)に次のような記述を追記することになるだろう。

ANT_HOME=/usr/local/apache-ant-1.5.2
JAVA_HOME=/usr/java/jdk1.3.1_01
PATH=${PATH}:${ANT_HOME}/bin
export ANT_HOME JAVA_HOME PATH

またcsh系のコマンドシェルを利用している場合は、.cshrc等に次のような内容を追記すればいい。

setenv ANT_HOME /usr/local/apache-ant-1.5.2
setenv JAVA_HOME /usr/java/jdk1.3.1_01
set path=( $path ${ANT_HOME}/bin )

9x系列のWindows系OSではautoexec.batに次のような内容を追記する(前述の通りANT_HOMEには8.3形式のディレクトリ名を設定するべきである)。

set ANT_HOME=C:\ant
set JAVA_HOME=C:\j2sdk1.3.1_01
set path=%path%;%ANT_HOME%\bin

ただし9x系列はデフォルトの環境変数の領域が非常に小さく、上記の定義が正しく反映されないことがある。あらかじめconfig.sysに

shell=c:\command.com c:\ /p /e:32768

の一行を追加して環境変数の領域を拡張しておいた方がいい。

NT系列の場合、「コントロールパネル」の「システム」を開けば、「環境」あるいは「詳細」タブで対話的に環境変数を設定できる。

こうして追加した環境変数を有効にしたら、Antの動作確認のために次のコマンドを実行する。

$ ant -version

Antのバージョンが表示されればセットアップは完了である。

Unix系、またはWindows系以外のOSで利用する場合でも、JVMから直接Antを起動することができる。起動用のシェル(またはバッチ)がやっていることは大雑把に

  • JDKのtools.jarとclasses.zipをクラスパスに追加する(もしあれば)。
  • Antのインストールディレクトリの下のlibにあるjarファイルをクラスパスに追加する。
  • プロパティant.homeにAntのインストールディレクトリを設定する。
  • org.apache.tools.ant.Mainを実行する(パラメータもそのまま渡す)。

といった程度である。これを直接JVMコマンドから実行すればいい。Antのバージョンを表示する例をあげる。

java -classpath /usr/java/jdk1.3.1_01/lib/tools.jar:/usr/local/apache-ant-1.5.2/lib/ant.jar:/usr/local/apache-ant-1.5.2/optional.jar:/usr/local/apache-ant-1.5.2/xercesImpl.jar:/usr/local/apache-ant-1.5.2/xml-apis.jar -Dant.home=/usr/local/apache-ant-1.5.2 org.apache.tools.ant.Main -version

これもUnix系OSを前提にした例で申し訳ないが、同等のことを行えばほかの環境でもAntを利用できるはずだ。

Antでコンパイル

まずは動かしてみる

セットアップができたところで、Antを使ってリスト3のコードのコンパイルを実行してみよう。まずはJavaのコードと同じ場所に、リスト5に示す内容をbuild.xmlという名前で保存する。

リスト5: build.xml
<project name="SampleProject" default="compile" basedir=".">
    <target name="compile">
        <javac srcdir="." />
    </target>
</project>

とりあえずAntの基本動作を確認するために、もしあればX.class、Y.class、Z.classは削除しておく。そうしておいてbuild.xmlを保存したディレクトリで以下のコマンドを実行する。

$ ant

すると次のようなメッセージが表示される。

Buildfile: build.xml

compile:
    [javac] Compiling 3 source files to /home/higashi/temp

BUILD SUCCESSFUL
Total time: 10 seconds

そしてメッセージにある通り、カレントディレクトリの3つのJavaのソースファイルがコンパイルされたことを確認できると思う。さらにソースファイルのうちのひとつだけ(たとえばZ.java)を適当に変更してさきほどと同じようにAntを実行してみる。

$ ant
Buildfile: build.xml

compile:
    [javac] Compiling 1 source file to /home/higashi/temp

BUILD SUCCESSFUL
Total time: 10 seconds

メッセージはひとつのソースファイルだけをコンパイルしたと言い、実際にファイルを見ればそのようになっているはずだ。これは前に見たmakeの振る舞いと同じである。

ビルドファイルの最小限の構成要素

makeがMakefileをもとに振る舞いを決めるのと同様に、Antはビルドファイルを見て動作を決定する。とくに指定がなければカレントディレクトリのbuild.xmlを利用する。ビルドファイルはproject要素をルートエレメントとするXML文書である。project要素には表2にあげる三つの属性を指定することができる。

表2: project要素の属性
nameプロジェクト名称任意
defaultデフォルトターゲット必須
basedirベースディレクトリ任意

nameには任意のプロジェクト名称を指定する。basedirにはAntを実行する際の基準ディレクトリを指定する。後述するタスク等でパスを指定するとき基準ディレクトリからの相対パス表現を利用することができる。リスト5ではbuild.xmlと同じディレクトリを基準ディレクトリとしている。

project要素の中にはひとつ以上のtarget要素を含めなければならない。Antでは個々のコマンドに対応するひとつひとつの動作をタスクで指定する。ひとつ以上のタスクをまとめたものがターゲットである(実際にはタスクを持たないターゲットも存在するが、それはまた後述する)。リスト5ではjavacを実行するタスクであるjavac要素だけからなるターゲットがcompileという名前で定義されている。project要素ではデフォルトで動作するターゲットをdefault属性で指定しなければならず、ここでは唯一のターゲットであるcompileがデフォルトになっている。

antコマンドの形式は以下の通りである。

ant [オプションの並び] [ターゲットの並び]

本来ならばcompileというターゲットを実行するには、

$ ant compile

と入力する必要があるが、project要素のdefault属性に記述されたターゲットのみを実行する場合は、ターゲットの並びを省略することができる。

リスト6: タスク一覧
Ant, AntCall, AntStructure, Apply/ExecOn, Available, Basename, BuildNumber, BUnzip2, BZip2, Checksum, Chmod, Concat, Condition, Copy, Copydir, Copyfile, Cvs, CvsChangeLog, CVSPass, CvsTagDiff, Delete, Deltree, Dependset, Dirname, Ear, Echo, Exec, Fail, Filter, FixCRLF, GenKey, Get, GUnzip, GZip, Input, Jar, Java, Javac, Javadoc/Javadoc2, LoadFile, LoadProperties, Mail, Manifest, Mkdir, Move, Parallel, Patch, PathConvert, Property, Record, Rename, Replace, Rmic, Sequential, SignJar, Sleep, Sql, Style, Tar, Taskdef, Tempfile, Touch, TStamp, Typedef, Unjar, Untar, Unwar, Unzip, Uptodate, Waitfor, War, XmlProperty, Xslt, Zip

ここではひとつひとつのタスクについての説明は行わないが、ざっと眺めればおおよそどのようなことができるか推測できるだろう。exec要素のような一部の例外を除いて、タスクの記述にコマンドが直接登場することはなく、パラメータ類もすべて属性や子要素を用いて指定する。こうすることでビルドファイルの記述についても特定のプラットフォームへの依存を小さくしているわけだ。

ビルドファイルを指定するオプション

リスト5の中の唯一のタスクであるjavac要素は、srcdir属性のディレクトリ以下にあるJavaのソースファイルを再帰的に検索し、対応するクラスファイルが存在しないか古い場合にのみコンパイルを実行する。destdir属性を指定することでクラスファイルを任意のディレクトリに出力することもできる。実際の開発では

sample
 |- build.xml
 |
 |- src
 |   +- *.java
 |
 +- classes
     +- *.class

のようにソースファイルとクラスファイルを分けておく方が一般的だろう。上の構成であれば、java要素の記述は以下のようになる。

<javac srcdir="src" destdir="classes" />

ここでsrcをカレントディレクトリとしてソースファイルを修正し、そのままAntを実行すると

$ ant
Buildfile: build.xml does not exist!
Build failed

というエラーが発生することは簡単に想像できるだろう。これを回避するには

$ ant -f ../build.xml

のように明示的にビルドファイルを指定するやり方もあるが、

$ ant -find build.xml

でも回避可能だ。findオプションが指定されると、Antはカレントディレクトリからルートディレクトリに向かってビルドファイルが見つかるまで再帰的に検索を繰り返す。パッケージを適用してディレクトリの階層が深くなった場合などには便利なオプションだ。こうしたオプションは便利だが、毎回コマンドラインから入力するのが面倒だというときには、環境変数ANT_ARGSを利用することができる。ここにAntに与えるオプションを記述すると、Ant起動シェル(またはバッチ)がAntに環境変数の内容を渡してくれる。設定例をあげる(Bourneシェル系の場合)。

$ ANT_ARGS=-find build.xml
$ export ANT_ARGS

残念ながらこの設定は、環境変数や付属の起動シェル(またはバッチ)を利用するため、Unix系、Windows系のOSでしか意味を持たない。

コンパイルの次に

ターゲットの依存関係

ビルド作業はひとりコンパイルのみに限定されるものではない。コンパイルに成功したらクラスファイルを配布用にjarでまとめる、という要求も当然出てくるだろう。先のソースファイルとクラスファイルのディレクトリを分けた構成において、クラスファイルをまとめたjarファイルをbuild.xmlと同じディレクトリに作成するときの記述をリスト7にあげる。

リスト7: build.xml
<project name="SampleProject" default="compile" basedir=".">
    <target name="compile">
        <javac srcdir="src" destdir="classes" />
    </target>

    <target name="dist" depends="compile">
        <jar destfile="sample.jar" basedir="classes" />
    </target>
</project>

新たに登場したjar要素はもちろんjarを実行するタスクを表す。destfile属性に作成するjarファイルを指定し、basedir属性にアーカイブするファイルを格納しているディレクトリを指定することはすぐに理解できると思う。リスト7ではJarタスク単独でdistというターゲットに定義している。distはデフォルトターゲットではないから、これを実行するにはターゲット名を明示してやる必要がある。

$ ant -find build.xml dist
Buildfile: /home/higashi/temp/build.xml

compile:

dist:
      [jar] Building jar: /home/higashi/temp/sample.jar

BUILD SUCCESSFUL
Total time: 6 seconds

こうして実行すると、期待通りの場所にsample.jarが作成されたことが確認できるだろう。さらにつづけて同じコマンドを実行しても、今度はsample.jarは更新されない。JarタスクもまたJavacタスクと同様にjarファイルが最新の場合は何もしない。

ところですでに気づいているとは思うが、distを実行する前にcompileも動いているらしいことが伺える。これはtarget要素に記述したdepends属性によるものである。depends属性にはターゲット間の依存関係を記述する。リスト7ではdistがcompileに依存することが記述されているので、Antはdistを実行する前にcompileも実行する。つまりjarファイルを作る前に必ずjarでまとめるクラスファイルを最新化するわけである(実際にX.java、Y.java、Z.javaのいずれかを修正して、distをもう一度実行してみてほしい)。もしdepends属性を記述しなかった場合、distとcompileはまったく別々に動くので、配布用のjarファイルを作るときクラスファイルが最新であるかどうかを人が判断しないといけないが、depends属性を使えばそうしたわずらわしさから解放される。

タスクを持たないターゲット

ここでリスト8に示すようにソースファイルW.javaを追加し、X.javaを修正したとする。

リスト8: W.java, X.java
public interface W {
    public static final String CONSTANT_STRING = "ABC";
}

public class X {
    private Y y;

    private void f() {
        y.g();
    }

    public static void main(String[] args) {
        System.out.println(W.CONSTANT_STRING);
    }
}

Antを実行すれば当然ふたつのソースファイルがコンパイルされ、X.classを実行すると以下の出力が得られる。

$ java X
ABC

そのあとでW.javaをリスト9のように変更したとする。

リスト9: W.java
public interface W {
    public static final String CONSTANT_STRING = "123";  // 文字列変更
}

再びAntを実行するとW.javaはコンパイルされるが、X.classの実行結果に変化がないことはご承知のことと思う。final宣言された変数はコンパイル時に即値に置きかえられるため、修正していないX.javaに対してももう一度コンパイルを実行しないと修正内容が反映されない。しかしただ単純にAntでJavacタスクを実行しても、修正していないソースファイルをコンパイルは実行されない。確実にコンパイルを実行する手っ取り早い方法はクラスファイルを消してしまうことだ。そこで以下のようなターゲットをbuild.xmlに追加する。

<target name="clean">
    <delete dir="classes" />
    <mkdir dir="classes" />
</target>

こうしておいて

$ ant -find build.xml clean
Buildfile: /home/higashi/temp/build.xml

clean:
   [delete] Deleting directory /home/higashi/temp/classes
    [mkdir] Created dir: /home/higashi/temp/classes

BUILD SUCCESSFUL
Total time: 5 seconds

のようにAntを実行すればclasses以下が空になる。そのあとでコンパイルを実行すると全ソースファイルがコンパイルされる。cleanのようなターゲットは通常compileとセットで実行することが多いだろう。そこで以下のターゲットを追加する。

<target name="rebuild" depends="clean,compile" />

このターゲットはタスクを含まず、依存関係のみ記述する。depends属性に複数のターゲットを記述することでそれらをセットで動かすことができる。実行例をあげる。

$ ant -find build.xml rebuild
Buildfile: /home/higashi/temp/build.xml

clean:
   [delete] Deleting directory /home/higashi/temp/classes
    [mkdir] Created dir: /home/higashi/temp/classes

compile:
    [javac] Compiling 4 source files to /home/higashi/temp/classes

rebuild:

BUILD SUCCESSFUL
Total time: 9 seconds

ビルドファイルを読みやすく

プロパティの利用

まだまだ小さなビルドファイルだが、それでもたとえばclassesというディレクトリが繰り返し記述されていることが気になってくる人もいるだろう。Antのビルドファイルではproperty要素を使ってmakeのマクロに相当する記述ができる。ここまでのビルドファイルをproperty要素を使って書き換えたものをリスト10に示す。

リスト10: build.xml
<project name="SampleProject" default="compile" basedir=".">
    <property name="src" location="src" />
    <property name="build" location="classes" />
    <property name="dist" value="sample.jar" />

    <target name="compile">
        <javac srcdir="${src}" destdir="${build}" />
    </target>

    <target name="dist" depends="compile">
        <jar destfile="${dist}" basedir="${build}" />
    </target>

    <target name="clean">
        <delete dir="${build}" />
        <mkdir dir="${build}" />
    </target>

    <target name="rebuild" depends="clean,compile" />
</project>

プロパティの設定の方法にはいくつかあるが、リスト10では名称と値を指定するようにしている。名称はname属性、値はvalue属性で指定する。location属性で指定した値はファイルシステムの絶対パスに置き換えられる。設定したプロパティは${名称}という形式で参照する。ユーザが設定したもののほかに、システムプロパティも同じ形式で参照可能だ。たとえば${user.home}は実行ユーザのホームディレクトリとなる。さらにAntは表3にあげるプロパティを自動的に設定する。

表3: Antが設定するプロパティ
basedirproject要素のbasedir属性の値
ant.fileビルドファイルの絶対パス
ant.versionAntのバージョン
ant.project.nameproject要素のname属性の値
ant.java.versionAntが検出したJVMのバージョン

パスの指定方法

話を単純にするため属性の指定があまり必要ない例を取り上げてきたが、実際にはそれだけですまないビルド作業もあるだろう。中でもファイルやディレクトリのパス名を列挙しなければならないケースは少なくない。たとえばJavacタスクにおいて、単純に特定ディレクトリ以下のソースをすべてコンパイルすればいいとは限らないし、クラスパスの指定がとても長くなることもあるだろう。javac要素ではクラスパスをclasspath属性に記述する。ここには環境変数CLASSPATHと同様の記述をする。例をあげる。

<javac srcdir="src" destdir="classes"
       classpath="lib/foo.jar:lib/bar.jar:/home/baz/classes" />

こうした記述は長くなりがちで柔軟性にも乏しい。Javacを含めて多くのタスクではclasspath属性に限らず、ファイルやディレクトリを指定する属性を子要素として記述できるようになっている。上記の例は次のようにも記述できる。

<javac srcdir="src" destdir="classes">
    <classpath>
        <pathelement location="lib/foo.jar" />
        <pathelement location="lib/bar.jar" />
        <pathelement path="/home/baz/classes" />
    </classpath>
</javac>

pathelement要素によって個々のパスを指定する。location属性はproperty要素のときと同様に指定された値を絶対パスに置き換える。path属性では与えられた値をそのままパス名として利用する。この方法だと記述量が増える代わりに可読性が向上し保守もやりやすい。

もしクラスパスに加える対象がlib以下のすべてのjarファイルであるならば、さらに次のような書き方もできる。

<javac srcdir="src" destdir="classes">
    <classpath>
        <fileset dir="lib" includes="*.jar" />
        <pathelement path="/home/baz/classes" />
    </classpath>
</javac>

fileset要素を使うとdir属性以下のファイルをパターンを使って指定できる。includes属性で対象のパターンを指定することも、逆にexcludes属性で対象から除外するパターンを指定することも可能だ。パターン指定に利用できるメタキャラクタを表4にあげる。

表4: Antのパターンのメタキャラクタ
*ゼロ個以上の文字
?ひとつの文字
**すべての階層のディレクトリ・ファイル名

このうち*と?はUnix・Windows系OSで使われるものとよく似ているのですぐに理解できると思う。**についてもたとえば、/test/**が/test以下のすべての階層のディレクトリとファイルにマッチする、ことが分かればイメージがつかめるのではないだろうか。要は深くなったディレクトリ構造を簡略に表現する記法である。

参照を使う

クラスパスはJavaプログラムのコンパイルにも実行にも必要となるため、複数のタスクで同じ記述を繰り返さなければならない場合も出てくる。そうしたときに便利なのが参照だ。例をあげる。

<project ... >

    ...

    <path id="my.classpath">
        <fileset dir="lib" includes="*.jar" />
        <pathelement path="/home/baz/classes" />
    </path>

    ...

    <target ... >
        <javac ... >
            <classpath refid="my.classpath" />
        </javac>
    </target>

    ...

    <target ... >
        <java ... >
            <classpath refid="my.classpath" />
        </java>
    </target>

    ...

</project>

path要素のid属性に任意の識別名を設定し、子要素に具体的なパス名を定義する。パス名の定義には前述のpathelement要素やfileset要素を使うことができる。こうしておけばタスク内のclasspath要素など、パス名を設定する要素においてrefid属性を用いて定義済みのパスを指定できる。

ここから先は

これまで書いてきたことは、Antのほんの上っ面をざっと概観したに過ぎない。言及していないタスクは山ほどあるし、言及したタスクについてももっといろいろな使い方ができる(詳細はPart2以降に記述されるだろう)。だがそれらをすべて紹介することは筆者の手に余る話だ。それでも、いままで使っていなかったがAntは何か役に立ちそうだ、という感想を持ってくれる人がいれば本稿の目的は達せられたと思う。

もしちょっとでもAntを使ってみる気になったら、Ant付属のドキュメント、すなわちAntのインストールディレクトリの下のdocs/index.htmlをWebブラウザを開いてほしい。ここにAntを使うときの基礎となる情報がそろっている。とくに「Manual」→「Ant Tasks」→「Core Tasks」はAntを使う上で拠り所となるリファレンスなので繰り返し見ることになるだろう。それを読むために最低限必要なことは本稿で触れたつもりである。

さらにJa-JakartaプロジェクトによるAntドキュメントの翻訳が http://www.jajakarta.org/ において進められている。 Antの最新バージョンの1.5についても一通りの翻訳が完了した様子で、英文はちょっという方は、ボランティアで翻訳をしてくれた有志の方々に感謝しつつ、こちらを参照されるといい。

AntはもともとApache Software Foundationの中の、Java関連の環境・ツールを開発するJakartプロジェクトの中のひとつのサブプロジェクトである、サーブレット・JSPコンテナTomcatをビルドするツールとして作られた。そういうわけでコンパイルなどのビルド作業を支援することがAntの主要な目的であることは今も変わらないが、Antが有用であることがわかってくるともっと多様な目的に使われるようになった。もっとも単純なところでは、ソースを編集しているところからAntが呼べると便利だとは誰もが考えるわけで、たとえばEclipseとの組み合わせで統合開発環境にAntを組み込む話がこのあとに出てくるはずである。ほかにもユニットテストツールのJUnitや、ソースコード管理ツールであるCVSとの組み合わせでもAntが活躍する。Antはmakeと比較されるような単なるビルドツールの枠を超えて、Javaによる開発の基盤となるツールに進化しつつある。

こうした動きを受けて、Apache Software Foundation内のAntの地位も変遷している。前述の通りTomcatに付属するツールからはじまり、やがてTomcatから独立してJakartaプロジェクト内のサブプロジェクトとなる。さらにはJakartaからも独立して、Apache Software Foundation内のひとつのプロジェクトにまで昇格した。そしてAnt自身、次期メジャーバージョンとしてAnt2の設計作業が進められている。Ant2では従来バージョンの機能拡張の中で混乱した一部規則を整理するとともに、フロントエンド・オブジェクトモデル・タスク実行部分を明確に分離することで、ほかのツールとの連携が容易になることを目標としている。

このようにAntは自身の機能を拡張し、またほかのツールとの連携を拡大することで、その適用の範囲を大きく広げてきたし、今後もその動きがつづいていくことが予想される。Javaでの開発においてAntの存在が前提となる日が遠からず訪れるかもしれない。 Antのすべてをいきなり理解することは難しいにせよ、本稿が言及した程度の使い方だけでもAntは十分に役に立つし、ここで大まかなAntの振る舞いを捉えておけば、Part2以降で説明するより進んだ利用方法の理解の助けになるだろう。Antは使う者のレベルに応じてそれなりの便利さを提供してくれるので、これからAntを使おうという人も変に構えることなく、必要の都度、ドキュメントを見ながらビルドファイルを書いていく、といった使い方をしていけばいいと思う。何よりもまず使ってみることが重要である。