Dev Study
← 解説「Ant: build.xml を1行ずつ読む」に戻る

サンプルコードで身につける: Ant: build.xml を1行ずつ読む

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1build.xml の全文(73行): 前のレッスンの5段を5つの target に

前のレッスンで手でやった5段を、そのまま にした です。塗ってあるのは、本文と5本目の表で説明している要素の行です。読む順は、 の ="" から、jar の ="test"、test の depends="compile,resources"、compile と resources の depends="init"、init と辿ります。各 target の中の 、copy、、jar、 の行と、冒頭の 、path の行も塗ってあります。属性の ${…} は property の値に置き換えて読みます。

build.xml(l2-ant/app、Ant 1.10.18 で実行確認)設定ファイル
<project name="app" default="jar" basedir=".">
<description>The five hand-made steps of l1-manual, written as Ant targets</description>
 
<property name="src.dir" location="src"/>
<property name="res.dir" location="resources"/>
<property name="test.dir" location="test"/>
<property name="lib.dir" location="lib"/>
<property name="classes.dir" location="build/classes"/>
<property name="test.classes.dir" location="build/test-classes"/>
<property name="dist.dir" location="dist"/>
 
<path id="compile.classpath">
<fileset dir="${lib.dir}" includes="*.jar"/>
</path>
<path id="test.classpath">
<path refid="compile.classpath"/>
<fileset dir="lib-test" includes="*.jar"/>
<pathelement location="${classes.dir}"/>
</path>
 
<target name="clean" description="delete build/ and dist/">
<delete dir="build"/>
<delete dir="${dist.dir}"/>
</target>
 
<target name="init">
<mkdir dir="${classes.dir}"/>
<mkdir dir="${test.classes.dir}"/>
<mkdir dir="${dist.dir}"/>
<mkdir dir="build/test-reports"/>
</target>
 
<target name="compile" depends="init" description="(a) javac src/ -> build/classes/">
<javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="compile.classpath"
includeantruntime="false" debug="true"/>
</target>
 
<target name="resources" depends="init" description="(b) copy resources/ -> build/classes/">
<copy todir="${classes.dir}">
<fileset dir="${res.dir}"/>
</copy>
</target>
 
<target name="test" depends="compile,resources" description="(c) compile and run the JUnit 5 tests">
<javac srcdir="${test.dir}" destdir="${test.classes.dir}" classpathref="test.classpath"
includeantruntime="false" debug="true"/>
<junitlauncher printsummary="true" haltOnFailure="true">
<classpath refid="test.classpath"/>
<classpath><pathelement location="${test.classes.dir}"/></classpath>
<testclasses outputdir="build/test-reports">
<fileset dir="${test.classes.dir}"/>
<listener type="legacy-plain" sendSysOut="true"/>
</testclasses>
</junitlauncher>
</target>
 
<target name="jar" depends="test" description="(d) build/classes/ -> dist/app.jar">
<jar destfile="${dist.dir}/app.jar" basedir="${classes.dir}">
<manifest>
<attribute name="Main-Class" value="com.example.app.Main"/>
</manifest>
</jar>
</target>
 
<target name="run" depends="jar" description="(e) run with the dependency on the classpath">
<java classname="com.example.app.Main" fork="true" failonerror="true">
<classpath>
<pathelement location="${dist.dir}/app.jar"/>
<path refid="compile.classpath"/>
</classpath>
</java>
</target>
</project>

2ant -p と、ant run の1回目・2回目

は のある target を並べ、最後に default を出します。このファイルには description のある target があるので、無い init は出ません。 run の1回目は init から順に6つの target が走り、2回目は compile、resources、jar の見出しだけで何もしません。test だけは毎回走ります。junitlauncher の には済んでいるかの判定が無いからです。

$ ant -p / $ ant run(1回目と2回目)コマンド出力
$ ant -p # description のある target が1つでもあるので、無い init は出ない
Buildfile: /bt/l2-ant/app/build.xml
The five hand-made steps of l1-manual, written as Ant targets
Main targets:
 
clean delete build/ and dist/
compile (a) javac src/ -> build/classes/
jar (d) build/classes/ -> dist/app.jar
resources (b) copy resources/ -> build/classes/
run (e) run with the dependency on the classpath
test (c) compile and run the JUnit 5 tests
Default target: jar
 
$ ant run # 1回目。depends を辿って init から順に
Buildfile: /bt/l2-ant/app/build.xml
 
init:
[mkdir] Created dir: /bt/l2-ant/app/build/classes
[mkdir] Created dir: /bt/l2-ant/app/build/test-classes
[mkdir] Created dir: /bt/l2-ant/app/dist
[mkdir] Created dir: /bt/l2-ant/app/build/test-reports
 
compile:
[javac] Compiling 2 source files to /bt/l2-ant/app/build/classes
 
resources:
[copy] Copying 1 file to /bt/l2-ant/app/build/classes
 
test:
[javac] Compiling 1 source file to /bt/l2-ant/app/build/test-classes
[junitlauncher] Running com.example.greet.GreeterTest
[junitlauncher] Tests run: 1, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: 0.035 sec
 
jar:
[jar] Building jar: /bt/l2-ant/app/dist/app.jar
 
run:
[java] Hello, World!
 
BUILD SUCCESSFUL
 
$ ant run # 2回目。何も変えていない
Buildfile: /bt/l2-ant/app/build.xml
 
init:
 
compile:
 
resources:
 
test:
[junitlauncher] Running com.example.greet.GreeterTest
[junitlauncher] Tests run: 1, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: 0.028 sec
 
jar:
 
run:
[java] Hello, World!
 
BUILD SUCCESSFUL

3property の値を見る: -v では出ず、-d で出る。-D は先に決まる

property の名前と値の対応は には出ません(展開済みのパスが javac の引数などに現れるだけです)。 にすると Setting project property: の行で名前と展開済みの絶対パスが見えます。コマンドラインの -Ddist.dir=… は build.xml より先に設定されるので、build.xml 側の <property> は Override ignored と無視され、dist/ は作られませんでした。

$ ant -d / $ ant -Ddist.dir=/tmp/elsewhere jar(29行版の build.xml)コマンド出力
# 29行版の build.xml(レッスン「ant を実行すると何が起きるか」のもの)で確認
# <property name="src.dir" location="src"/>
# <property name="build.dir" location="build/classes"/>
# <property name="dist.dir" location="dist"/>
 
$ ant -v | grep -n -i "property\|build.dir\|/bt/l2-ant/plain/build\|Build sequence"
3:Buildfile: /bt/l2-ant/plain/build.xml
6:parsing buildfile /bt/l2-ant/plain/build.xml with URI = file:/bt/l2-ant/plain/build.xml
9:Build sequence for target(s) `jar' is [init, compile, jar]
10:Complete build sequence is [init, compile, jar, clean, ]
13: [mkdir] Created dir: /bt/l2-ant/plain/build/classes
19: [javac] Compiling 2 source files to /bt/l2-ant/plain/build/classes
23: [javac] '/bt/l2-ant/plain/build/classes'
25: [javac] '/bt/l2-ant/plain/build/classes'
47: [jar] Location: /bt/l2-ant/plain/build.xml:23:
# 展開済みのパスは出るが、「名前 -> 値」の設定行は -v には1行も無い
 
$ ant -d clean 2>&1 | grep -i "Setting .*property\|Override ignored\|build.dir\|dist.dir\|src.dir" | head -40
Setting ro project property: ant.file -> /bt/l2-ant/plain/build.xml
...
Setting ro project property: ant.project.name -> hello
Setting ro project property: ant.project.default-target -> jar
...
Setting project property: src.dir -> /bt/l2-ant/plain/src
Setting project property: build.dir -> /bt/l2-ant/plain/build/classes
Setting project property: dist.dir -> /bt/l2-ant/plain/dist
...
 
$ ant -Ddist.dir=/tmp/elsewhere jar && ls -l /tmp/elsewhere dist 2>&1 # コマンドラインで dist.dir を上書き
init:
[mkdir] Created dir: /bt/l2-ant/plain/build/classes
[mkdir] Created dir: /tmp/elsewhere
 
compile:
[javac] Compiling 2 source files to /bt/l2-ant/plain/build/classes
 
jar:
[jar] Building jar: /tmp/elsewhere/app.jar
 
BUILD SUCCESSFUL
Total time: 0 seconds
ls: cannot access 'dist': No such file or directory
/tmp/elsewhere:
total 4
-rw-r--r-- 1 root root 1680 Oct 3 00:21 app.jar
 
$ ant -d -Ddist.dir=/tmp/elsewhere2 init 2>&1 | grep -i "dist.dir"
Setting ro project property: dist.dir -> /tmp/elsewhere2
Override ignored for property "dist.dir"

4depends の順序: 1つの呼び出しでは1回、2つ並べると2回

D が B と C に、B と C が A に依存する形で、ant D なら A は1回です。マニュアルの例と同じ depends="C,B,A" を持つ E は、B と C が A にだけ依存するこの形では、左から右に、ただし依存が先になるよう並べ直されて A, C, B, E になりました(マニュアルの例は C→B→A の鎖なので、そちらは A, B, C, D です。build.xml のコメントはその depends を借りたという意味です)。ant B C と2つ並べると、呼び出しごとに記録が切れて A が2回走ります。下のコードは、この2つの規則と property の凍結を素の Java でなぞった模型です。

diamond/build.xml と $ ant / $ ant -v D / $ ant E / $ ant B Cコマンド出力
# build.xml(diamond)
<project name="diamond" default="D">
<!-- D depends on B and C; both depend on A. How many times does A run? -->
<target name="A"><echo>A runs</echo></target>
<target name="B" depends="A"><echo>B runs</echo></target>
<target name="C" depends="A"><echo>C runs</echo></target>
<target name="D" depends="B,C"><echo>D runs</echo></target>
<!-- the manual's own example: depends="C,B,A" -->
<target name="E" depends="C,B,A"><echo>E runs</echo></target>
</project>
 
$ ant # default="D"。A は1回
A:
[echo] A runs
 
B:
[echo] B runs
 
C:
[echo] C runs
 
D:
[echo] D runs
 
BUILD SUCCESSFUL
 
$ ant -v D | grep "Build sequence"
Build sequence for target(s) `D' is [A, B, C, D]
 
$ ant -v E | grep "Build sequence" # E depends="C,B,A"。B と C は A にだけ依存するので A, C, B, E
Build sequence for target(s) `E' is [A, C, B, E]
 
$ ant B C # target を2つ並べると A が2回
A:
[echo] A runs
 
B:
[echo] B runs
 
A:
[echo] A runs
 
C:
[echo] C runs
 
BUILD SUCCESSFUL
 
$ ant -p # description が無いので全部 Other targets
Main targets:
 
Other targets:
 
A
B
C
D
E
Default target: D
Java

5要素・属性の早見表: 職場の build.xml で出会うもの

73行版に出てくる要素と属性を、 1.10.18 のマニュアルの原文、73行版の該当する行、Node 側で近いもの(たとえ)の順に並べました。project から java まで、1本目の build.xml で塗ってある行と対応しています。マニュアルの逐語を取っていない java の task だけは、実測で分かることにとどめています。

build.xml の要素・属性の早見表(Ant 1.10.18)表
要素・属性 | 一言で(Ant 1.10.18 マニュアルの範囲)
手元では: 73行版 build.xml の該当する行
近いもの: Node/NestJS で近いもの(あれば。たとえ)
 
project | ビルドファイル1つに1つ。name、default、basedir を持つ
原文: Each buildfile contains one project and at least one
(default) target.
手元では: <project name="app" default="jar" basedir=".">
近いもの: package.json の name
 
default | 引数なしで ant と打ったときに動く target
原文: the default target to use when no target is supplied.
手元では: default="jar"
近いもの: npm start に近いが、名前を自分で決める
 
basedir | すべてのパス計算の基準。省略すると build.xml のあるフォルダ
原文: the base directory from which all path calculations
are done.
手元では: basedir="."
 
description | project や target の説明。ant -p に出る
手元では: <description>The five hand-made steps ...</description>
原文(target): Targets without such a description are deemed
internal and will not be listed
実測: description のある target が1つでもあれば、無い target
(init)は出ない。1つも無いと全部が Other targets に並ぶ
 
property | 名前と値の対。一度決めたら変えられない(変数ではない)
原文: Properties are immutable: whoever sets a property first
freezes it for the rest of the build
手元では: <property name="src.dir" location="src"/>
value は文字列のまま、location は絶対パスに直して保存
原文(location): Sets the property to the absolute filename of
the given file.
近いもの: 無い(環境変数に似るが、上書きできない)
 
${name} | property の値への置き換え
原文: Apache Ant tries to expand ${key} to value at run time.
手元では: destdir="${classes.dir}"
 
target | 実行したい task の集まり。depends で先に行う target を書く
原文: A target is a set of tasks you want to be executed.
原文(depends): a comma-separated list of names of targets on
which this target depends.
手元では: <target name="test" depends="compile,resources" ...>
近いもの: npm scripts(ただし依存の木は無い。pre/post だけ)
 
task | 実行できるコードの一片。Ant の中の Java クラス
原文: A task is a piece of code that can be executed.
手元では: <mkdir> <delete> <copy> <javac> <jar> <java> など
 
mkdir | フォルダを作る。親も作る。既にあれば何もしない
原文: Creates a directory. Also non-existent parent directories
are created, when necessary.
手元では: <mkdir dir="${classes.dir}"/>
 
delete | ファイル、またはフォルダとその中身を消す
原文: Deletes a single file, ... a specified directory and all
its files and subdirectories
手元では: <delete dir="build"/>
 
copy + fileset | fileset で選んだファイルを todir へ。
既定では送り側が新しいときだけコピー
原文: By default, files are only copied if the source file is
newer than the destination file
手元では: <copy todir="${classes.dir}"><fileset dir="${res.dir}"/>
近いもの: ビルド時のアセットのコピー
 
javac | srcdir の .java を destdir へ。ソースはパッケージどおりの
階層に置く
原文: srcdir = Location of the java files. / destdir = Location
to store the class files.
手元では: <javac srcdir="${src.dir}" destdir="${classes.dir}"
classpathref="compile.classpath"
includeantruntime="false" debug="true"/>
includeantruntime: Ant 自身の実行時ライブラリをクラスパスに含めるか。
書かないと warning: 'includeantruntime' was not set ...
原文: It is usually best to set this to false
debug="true": javac に -g が付く(ant -v で確認)
近いもの: tsc
 
path / fileset / pathelement / refid | クラスパスの型の値
原文: If you want to use the same path-like structure for several
tasks, you can define them with a <path> element at the same
level as <target>s, and reference them via their id attribute
fileset: フォルダ木からパターンで選んだファイルの集まり
pathelement: location で1つの場所を指す
refid: 定義済みの path を名前で参照
手元では: <path id="compile.classpath">
<fileset dir="${lib.dir}" includes="*.jar"/>
</path>
<path refid="compile.classpath"/>
<pathelement location="${classes.dir}"/>
 
jar + manifest | basedir の中身を destfile の JAR にまとめる
原文: Jars a set of files. / The basedir attribute is the
reference directory from where to jar.
手元では: <jar destfile="${dist.dir}/app.jar" basedir="${classes.dir}">
<manifest><attribute name="Main-Class" .../></manifest>
近いもの: npm pack(たとえ)
 
junitlauncher | JUnit 5 以降のテストを実行する task(Ant 1.10 系)
手元では: <junitlauncher printsummary="true" haltOnFailure="true">
実測: 2回目の ant run でも走った。outputdir のフォルダは自分で
作らないので init で mkdir した
近いもの: jest
 
java | Java のクラスを実行する task
手元では: <java classname="com.example.app.Main" fork="true" ...>
実測: run target で [java] Hello, World! が出た
(この task のマニュアルの逐語は取っていない)
近いもの: node dist/main.js

図やコードに出てくる用語(タップで説明)