サンプルコードで身につける: 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 の値に置き換えて読みます。
<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 # description のある target が1つでもあるので、無い init は出ないBuildfile: /bt/l2-ant/app/build.xmlThe five hand-made steps of l1-manual, written as Ant targetsMain targets:clean delete build/ and dist/compile (a) javac src/ -> build/classes/jar (d) build/classes/ -> dist/app.jarresources (b) copy resources/ -> build/classes/run (e) run with the dependency on the classpathtest (c) compile and run the JUnit 5 testsDefault target: jar$ ant run # 1回目。depends を辿って init から順にBuildfile: /bt/l2-ant/app/build.xmlinit:[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-reportscompile:[javac] Compiling 2 source files to /bt/l2-ant/app/build/classesresources:[copy] Copying 1 file to /bt/l2-ant/app/build/classestest:[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 secjar:[jar] Building jar: /bt/l2-ant/app/dist/app.jarrun:[java] Hello, World!BUILD SUCCESSFUL$ ant run # 2回目。何も変えていないBuildfile: /bt/l2-ant/app/build.xmlinit:compile:resources:test:[junitlauncher] Running com.example.greet.GreeterTest[junitlauncher] Tests run: 1, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: 0.028 secjar: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/ は作られませんでした。
# 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.xml6:parsing buildfile /bt/l2-ant/plain/build.xml with URI = file:/bt/l2-ant/plain/build.xml9: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/classes19: [javac] Compiling 2 source files to /bt/l2-ant/plain/build/classes23: [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 -40Setting ro project property: ant.file -> /bt/l2-ant/plain/build.xml...Setting ro project property: ant.project.name -> helloSetting ro project property: ant.project.default-target -> jar...Setting project property: src.dir -> /bt/l2-ant/plain/srcSetting project property: build.dir -> /bt/l2-ant/plain/build/classesSetting 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/elsewherecompile:[javac] Compiling 2 source files to /bt/l2-ant/plain/build/classesjar:[jar] Building jar: /tmp/elsewhere/app.jarBUILD SUCCESSFULTotal time: 0 secondsls: 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/elsewhere2Override 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 でなぞった模型です。
# 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 runsB:[echo] B runsC:[echo] C runsD:[echo] D runsBUILD 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, EBuild sequence for target(s) `E' is [A, C, B, E]$ ant B C # target を2つ並べると A が2回A:[echo] A runsB:[echo] B runsA:[echo] A runsC:[echo] C runsBUILD SUCCESSFUL$ ant -p # description が無いので全部 Other targetsMain targets:Other targets:ABCDEDefault target: D
5要素・属性の早見表: 職場の build.xml で出会うもの
73行版に出てくる要素と属性を、 1.10.18 のマニュアルの原文、73行版の該当する行、Node 側で近いもの(たとえ)の順に並べました。project から java まで、1本目の build.xml で塗ってある行と対応しています。マニュアルの逐語を取っていない java の task だけは、実測で分かることにとどめています。
要素・属性 | 一言で(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 の namedefault | 引数なしで 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 calculationsare done.手元では: basedir="."description | project や target の説明。ant -p に出る手元では: <description>The five hand-made steps ...</description>原文(target): Targets without such a description are deemedinternal and will not be listed実測: description のある target が1つでもあれば、無い target(init)は出ない。1つも無いと全部が Other targets に並ぶproperty | 名前と値の対。一度決めたら変えられない(変数ではない)原文: Properties are immutable: whoever sets a property firstfreezes it for the rest of the build手元では: <property name="src.dir" location="src"/>value は文字列のまま、location は絶対パスに直して保存原文(location): Sets the property to the absolute filename ofthe 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 onwhich 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 directoriesare created, when necessary.手元では: <mkdir dir="${classes.dir}"/>delete | ファイル、またはフォルダとその中身を消す原文: Deletes a single file, ... a specified directory and allits files and subdirectories手元では: <delete dir="build"/>copy + fileset | fileset で選んだファイルを todir へ。既定では送り側が新しいときだけコピー原文: By default, files are only copied if the source file isnewer than the destination file手元では: <copy todir="${classes.dir}"><fileset dir="${res.dir}"/>近いもの: ビルド時のアセットのコピーjavac | srcdir の .java を destdir へ。ソースはパッケージどおりの階層に置く原文: srcdir = Location of the java files. / destdir = Locationto 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 falsedebug="true": javac に -g が付く(ant -v で確認)近いもの: tscpath / fileset / pathelement / refid | クラスパスの型の値原文: If you want to use the same path-like structure for severaltasks, you can define them with a <path> element at the samelevel as <target>s, and reference them via their id attributefileset: フォルダ木からパターンで選んだファイルの集まり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 thereference 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 した近いもの: jestjava | Java のクラスを実行する task手元では: <java classname="com.example.app.Main" fork="true" ...>実測: run target で [java] Hello, World! が出た(この task のマニュアルの逐語は取っていない)近いもの: node dist/main.js