Ant Conditions & Control Flow
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Apache Ant 1.10.18. Every example was run on JDK 26 on 2026-10-16 with that release and the
JARs in its lib directory, nothing else on the class path. The examples use the tidepool scenario of this section
(data/stations.csv and the station directories SF, BOS and HNL) in two throwaway build files that are not part of
the scenario itself. The network conditions were run against a local web server on localhost. The absolute scratch path
in console output was shortened to /work/tidepool.
Ant has no if statement. Decisions are made in three places: a condition computes a boolean and the condition,
waitfor and fail tasks act on it, targets are switched on and off by properties (if and unless), and a few
container tasks (parallel, sequential, retry) arrange when and how often nested tasks run. This page explains each
and ends with the question that every Ant user eventually asks: how do I loop?
Properties are covered on Properties, and the target-level
if and unless attributes on
Targets & Extension Points. Utility tasks
such as sleep, tstamp and input are on Utility Tasks,
and get and the other remote tasks on
Network & Remote Tasks.
The condition task
condition is the generalisation of available and uptodate: it sets a property when a nested condition holds. Exactly
one condition must be nested, and and, or, not and xor let you combine several.
| Attribute | Meaning | Default |
|---|---|---|
|
The property to set. Required. |
|
|
The value to set when the condition is true. |
|
|
The value to set when the condition is false (Ant 1.6.3). Without it the property is left unset when the condition is false. |
unset |
Leaving the property unset when the condition is false is what makes the if and unless attributes of targets work,
and it is also the source of the classic bug: an unset property is not false, it is absent, and ${name} stays
literally as ${name} in any attribute that refers to it. Use else="false" when you want an explicit value.
The same set of conditions is accepted by waitfor and by the nested <condition> of fail, and custom conditions
(see Custom Types, Conditions & Selectors)
can be used anywhere a core condition can.
A helper that prints any condition
The examples evaluate many conditions, so a macro runs one nested condition and prints its result. Because it sets the
property with else="false", every outcome is visible. See
Macros & Presets for macrodef.
<?xml version="1.0" encoding="UTF-8"?>
<project name="tidepool-conditions" default="logic" basedir=".">
<!-- evaluate any nested condition and print the result -->
<macrodef name="check">
<attribute name="name"/>
<element name="test" implicit="true"/>
<sequential>
<condition property="r.@{name}" value="true" else="false">
<test/>
</condition>
<echo message="@{name}: ${r.@{name}}"/>
</sequential>
</macrodef>
<property name="stations" value="data/stations.csv"/>
<property name="empty.prop" value=""/>
<property name="flag" value="yes"/>
Source: Apache Ant manual: Condition
Core conditions
The manual lists the nested elements below under "Core Conditions". Attributes not shown in the table are on the manual page.
| Condition | What it tests | Since |
|---|---|---|
|
Boolean combinators. |
|
|
The |
|
|
The |
|
|
Operating system by |
|
|
|
|
|
The property named by |
|
|
The |
|
|
The URL answers with a status below |
|
|
Something listens on |
|
|
Two files are identical byte for byte, or with line endings ignored if |
|
|
|
|
|
|
|
|
A reference |
1.6 |
|
A JAR has a signature (any, or the one named |
1.7 |
|
A |
1.6.3 |
|
A task or type |
1.7 |
|
A script decides. Needs a script engine. |
1.7 |
|
The XML parser supports a SAX or JAXP |
1.7 |
|
A |
1.7 |
|
The |
1.6.3 |
|
An executable’s return |
1.7 |
|
The size of a resource collection compares with |
1.7 |
|
The content of the nested resources is identical; |
1.7 |
|
A |
1.7.1 |
|
A class has a |
1.7 |
|
|
1.7 |
|
The |
1.7 |
|
|
1.7.0 |
|
The modification time of a nested resource compares with |
1.8.0 |
|
A nested resource exists. |
1.8.0 |
|
The JVM running Ant is |
1.10.2 |
|
The process can create symbolic links. |
1.10.16 |
Source: Apache Ant manual: javaversion
Logic and strings
<target name="logic">
<check name="and"><and><isset property="flag"/><equals arg1="${flag}" arg2="yes"/></and></check>
<check name="or"><or><isset property="nope"/><isset property="flag"/></or></check>
<check name="not"><not><isset property="nope"/></not></check>
<check name="xor-two-true"><xor><isset property="flag"/><isset property="stations"/></xor></check>
<check name="xor-one-true"><xor><isset property="flag"/><isset property="nope"/></xor></check>
<check name="equals-case"><equals arg1="M2" arg2="m2"/></check>
<check name="equals-ignore-case"><equals arg1="M2" arg2="m2" casesensitive="false"/></check>
<check name="equals-trim"><equals arg1=" M2 " arg2="M2" trim="true"/></check>
<check name="contains"><contains string="SF,San Francisco" substring="francisco" casesensitive="false"/></check>
<check name="istrue-yes"><istrue value="${flag}"/></check>
<check name="istrue-unset"><istrue value="${nope}"/></check>
<check name="isfalse-unset"><isfalse value="${nope}"/></check>
<check name="isfalse-off"><isfalse value="off"/></check>
<check name="matches"><matches string="M2:0.55:120" pattern="^[A-Z][A-Z0-9]*:\d+\.\d+:\d+$$"/></check>
<check name="matches-multiline"><matches string="a${line.separator}b" pattern="^b$$" multiline="true"/></check>
<check name="length-string"><length string=" M2 " trim="true" length="2"/></check>
<check name="length-file"><length file="${stations}" when="greater" length="0"/></check>
</target>
$ ant -q logic
[echo] and: true
[echo] or: true
[echo] not: true
[echo] xor-two-true: false
[echo] xor-one-true: true
[echo] equals-case: false
[echo] equals-ignore-case: true
[echo] equals-trim: true
[echo] contains: true
[echo] istrue-yes: true
[echo] istrue-unset: false
[echo] isfalse-unset: true
[echo] isfalse-off: true
[echo] matches: true
[echo] matches-multiline: true
[echo] length-string: true
[echo] length-file: true
BUILD SUCCESSFUL
Total time: 0 seconds
Three results are worth reading twice. xor-two-true is false because two children are true; xor is not "exactly one". The
istrue-unset and isfalse-unset pair shows how an unset property is handled: ${nope} stays the literal string
${nope}, which is not one of true, yes and on, so istrue is false and isfalse is true. And equals-trim needs
trim="true" to ignore the spaces around M2. In the matches patterns the end-of-line anchor is written $$ because
a single dollar sign starts a property reference in a build file.
Files and resources
<target name="files">
<check name="resourceexists"><resourceexists><file file="${stations}"/></resourceexists></check>
<check name="filesmatch-same"><filesmatch file1="${stations}" file2="data/stations-copy.csv"/></check>
<check name="filesmatch-different"><filesmatch file1="${stations}" file2="data/copy.csv"/></check>
<check name="filesmatch-line-endings"><filesmatch file1="data/crlf.csv" file2="data/lf.csv"/></check>
<check name="filesmatch-textfile"><filesmatch file1="data/crlf.csv" file2="data/lf.csv" textfile="true"/></check>
<check name="resourcecontains"><resourcecontains resource="${stations}" substring="Honolulu"/></check>
<check name="resourcecount"><resourcecount count="3"><dirset dir="stations" includes="*"/></resourcecount></check>
<check name="isfileselected"><isfileselected file="${stations}"><size value="100" when="more"/></isfileselected></check>
<check name="islastmodified"><islastmodified datetime="01/01/2020 12:00 AM" mode="after"><file file="${stations}"/></islastmodified></check>
<checksum file="${stations}" algorithm="SHA-256" property="stations.sha256"/>
<check name="checksum"><checksum file="${stations}" algorithm="SHA-256" property="${stations.sha256}"/></check>
<check name="checksum-wrong"><checksum file="${stations}" algorithm="SHA-256" property="0000"/></check>
<check name="canCreateSymlink"><canCreateSymlink/></check>
<check name="hasfreespace"><hasfreespace partition="/" needed="1M"/></check>
<check name="isreference-no"><isreference refid="no.such.ref"/></check>
<path id="cp"><pathelement location="data"/></path>
<check name="isreference-path"><isreference refid="cp" type="path"/></check>
<check name="isreference-wrong-type"><isreference refid="cp" type="fileset"/></check>
<check name="issigned"><issigned file="${ant.library.dir}/ant.jar"/></check>
</target>
$ ant -q files
[echo] resourceexists: true
[echo] filesmatch-same: true
[echo] filesmatch-different: false
[echo] filesmatch-line-endings: false
[echo] filesmatch-textfile: true
[echo] resourcecontains: true
[echo] resourcecount: true
[echo] isfileselected: true
[echo] islastmodified: true
[echo] checksum: true
[echo] checksum-wrong: false
[echo] canCreateSymlink: true
[echo] hasfreespace: true
[echo] isreference-no: false
[echo] isreference-path: true
[echo] isreference-wrong-type: false
[echo] issigned: false
BUILD SUCCESSFUL
Total time: 0 seconds
filesmatch-line-endings is false and filesmatch-textfile is true for the same two files, which differ only in CRLF versus
LF line endings: set textfile="true" when a file may have been checked out with another platform’s line endings. The
resourcecount condition counts the three station directories only because a dirset was used; a fileset of the same
directory would have counted zero, since a fileset selects files. isreference-wrong-type is false because the reference
exists but is a path, not a fileset, and issigned is false for the unsigned ant.jar.
Environment, versions and types
<target name="environment">
<check name="os-family-unix"><os family="unix"/></check>
<check name="os-family-mac"><os family="mac"/></check>
<check name="os-family-windows"><os family="windows"/></check>
<check name="os-family-mac-and-not-unix"><and><os family="mac"/><not><os family="unix"/></not></and></check>
<check name="javaversion-atleast-21"><javaversion atleast="21"/></check>
<check name="javaversion-exactly-26"><javaversion exactly="26"/></check>
<check name="javaversion-atmost-17"><javaversion atmost="17"/></check>
<check name="antversion-atleast-1.10.2"><antversion atleast="1.10.2"/></check>
<check name="antversion-exactly-1.10.18"><antversion exactly="1.10.18"/></check>
<check name="typefound-junitlauncher"><typefound name="junitlauncher"/></check>
<check name="typefound-checkstations"><typefound name="checkstations"/></check>
<check name="hasmethod"><hasmethod classname="java.util.ArrayList" method="trimToSize"/></check>
<check name="hasmethod-missing"><hasmethod classname="java.util.ArrayList" method="shrinkToFit"/></check>
<check name="parsersupports"><parsersupports feature="http://xml.org/sax/features/namespaces"/></check>
<check name="available-class"><available classname="java.util.HexFormat"/></check>
<check name="available-file"><available file="${stations}" type="file"/></check>
<check name="available-dir"><available file="data" type="dir"/></check>
<check name="available-resource"><available resource="org/apache/tools/ant/taskdefs/defaults.properties"/></check>
<exec executable="false" resultproperty="rc" failonerror="false"/>
<check name="isfailure"><isfailure code="${rc}"/></check>
</target>
$ ant -q environment
[echo] os-family-unix: true
[echo] os-family-mac: true
[echo] os-family-windows: false
[echo] os-family-mac-and-not-unix: false
[echo] javaversion-atleast-21: true
[echo] javaversion-exactly-26: true
[echo] javaversion-atmost-17: false
[echo] antversion-atleast-1.10.2: true
[echo] antversion-exactly-1.10.18: true
[echo] typefound-junitlauncher: true
[echo] typefound-checkstations: false
[echo] hasmethod: true
[echo] hasmethod-missing: false
[echo] parsersupports: true
[echo] available-class: true
[echo] available-file: true
[echo] available-dir: true
[echo] available-resource: true
[echo] isfailure: true
BUILD SUCCESSFUL
Total time: 0 seconds
The isfailure check uses the Unix false command, which always exits with a non-zero code, so it needs a Unix-like
system. os-family-mac and os-family-unix are both true on the macOS machine used for this page, so
os-family-mac-and-not-unix is false: the manual’s isMacOsButNotMacOsX example (family mac and not family unix)
matches only the classic Mac OS, because macOS is in the unix family as well. typefound-junitlauncher is true because ant-junitlauncher.jar is in lib, while typefound-checkstations is false
because the <checkstations> task of the tidepool scenario has not been defined here (see
Writing Custom Tasks). The javaversion result
for exactly="26" is true because the examples ran on JDK 26. Use javaversion and antversion in a guard target to
fail early with a readable message instead of with a class-format error halfway through the build.
Source: Apache Ant manual: Core Conditions, os, javaversion and typefound
Network conditions against localhost
The conditions below were run against a Python http.server started on port 8765 in the directory that holds
data/stations.csv; nothing on the Internet was contacted.
<target name="network">
<check name="http-ok"><http url="http://localhost:${port}/data/stations.csv"/></check>
<check name="http-404"><http url="http://localhost:${port}/missing.csv"/></check>
<check name="http-404-errorsBeginAt"><http url="http://localhost:${port}/missing.csv" errorsBeginAt="500"/></check>
<check name="http-head"><http url="http://localhost:${port}/data/stations.csv" requestMethod="HEAD"/></check>
<check name="socket-open"><socket server="localhost" port="${port}"/></check>
<check name="socket-closed"><socket server="localhost" port="9"/></check>
<check name="isreachable-localhost"><isreachable host="localhost" timeout="2"/></check>
</target>
$ ant -q -Dport=8765 network
[echo] http-ok: true
[echo] http-404: false
[echo] http-404-errorsBeginAt: true
[echo] http-head: true
[echo] socket-open: true
[echo] socket-closed: false
[echo] isreachable-localhost: true
BUILD SUCCESSFUL
Total time: 0 seconds
http-404 is false because the default errorsBeginAt is 400, and raising it to 500 makes the same 404 response count as
healthy: the condition checks the status class, not that the page has the content you expect. socket-closed tests port 9,
which nothing listens on. A socket or http condition used in a waitfor is the normal way to wait for a server to come
up. isreachable to a remote host depends on the network and on firewalls, because the JVM may use ICMP or a TCP echo
probe, so prefer socket or http against the port you actually need.
Source: Apache Ant manual: Core Conditions, http, socket and isreachable
scriptcondition needs an engine
<target name="scriptcondition">
<condition property="r">
<scriptcondition language="javascript" value="true">self.setValue(false);</scriptcondition>
</condition>
</target>
$ ant -q scriptcondition
Java 15 has removed Nashorn, you must provide an engine for running JavaScript yourself. GraalVM JavaScript currently is the preferred option.
BUILD FAILED
/work/tidepool/build.xml:96: Unable to create javax script engine for javascript
Total time: 0 seconds
$ echo $?
1
The manual’s scriptcondition example uses JavaScript, which the JDK no longer provides since Java 15. Use a built-in
condition, a custom condition class, or add a script engine as described on
Scripting.
available and uptodate
available and uptodate are the two oldest condition-like tasks. They set a property instead of failing.
available takes exactly one of classname, file or resource, plus classpath, filepath, classpathref, type
(dir or file), ignoresystemclasses and searchparents (1.7), and property with an optional value. uptodate
compares one srcfile or nested <srcfiles> (a fileset) or <srcresources> with a targetfile or a nested <mapper>
and sets the property when no source is newer than its target. With <srcfiles> and no mapper it uses a merge mapper onto
targetfile. See Filters & Mappers for the mapper
types.
<?xml version="1.0" encoding="UTF-8"?>
<project name="tidepool-flow" default="probe" basedir="." xmlns:if="ant:if" xmlns:unless="ant:unless">
<property name="out" location="build"/>
<target name="clean"><delete dir="${out}"/><mkdir dir="${out}"/></target>
<target name="probe" depends="clean">
<available file="data/stations.csv" type="file" property="have.stations"/>
<available file="data/tides.csv" property="have.tides"/>
<available classname="io.tidepool.model.Height" property="have.model"/>
<echo message="have.stations=${have.stations} have.tides=${have.tides} have.model=${have.model}"/>
</target>
<target name="load" depends="probe" if="have.stations">
<echo message="loading data/stations.csv"/>
</target>
<target name="load-missing" depends="probe" unless="have.tides">
<echo message="no tide data, skipping the tide import"/>
</target>
$ ant -q -f flow.xml load
[echo] have.stations=true have.tides=${have.tides} have.model=${have.model}
[echo] loading data/stations.csv
BUILD SUCCESSFUL
Total time: 0 seconds
$ ant -q -f flow.xml load-missing
[echo] have.stations=true have.tides=${have.tides} have.model=${have.model}
[echo] no tide data, skipping the tide import
BUILD SUCCESSFUL
Total time: 0 seconds
have.tides and have.model are unset, so they print literally. The io.tidepool.model.Height class is not on Ant’s own class
path, which is why have.model is unset too: classname looks in Ant’s class path unless classpath is given. The
load-missing target runs because its unless property is unset; its probe dependency ran first, as it always does.
Source: Apache Ant manual: Available and Uptodate
A guarded target still runs its dependencies
A target whose if or unless is not satisfied is skipped, but its depends targets still run first. This is the
question in the Ant FAQ about a target skipped by unless whose prerequisites run anyway:
<target name="guarded" depends="probe" unless="skip.import">
<echo message="importing"/>
</target>
$ ant -q -f flow.xml -Dskip.import=true guarded
[echo] have.stations=true have.tides=${have.tides} have.model=${have.model}
BUILD SUCCESSFUL
Total time: 0 seconds
Only importing was skipped; probe ran. Put the condition on the dependency that does the expensive work, or move the
check into a small target that the others depend on.
uptodate
<target name="uptodate" depends="clean">
<echo file="${out}/report.txt" message="report${line.separator}"/>
<sleep milliseconds="1100"/>
<touch file="data/stations.csv"/>
<uptodate property="report.current" targetfile="${out}/report.txt">
<srcfiles dir="data" includes="*.csv"/>
</uptodate>
<echo message="after touching the sources: report.current=${report.current}"/>
<sleep milliseconds="1100"/>
<touch file="${out}/report.txt"/>
<uptodate property="report.current" targetfile="${out}/report.txt">
<srcfiles dir="data" includes="*.csv"/>
</uptodate>
<echo message="after touching the report: report.current=${report.current}"/>
</target>
$ ant -q -f flow.xml uptodate
[echo] after touching the sources: report.current=${report.current}
[echo] after touching the report: report.current=true
BUILD SUCCESSFUL
Total time: 2 seconds
A property that is already set is never overwritten, so the second uptodate can only add report.current. Two details
matter in practice. The sleep calls exist because file timestamps have a granularity of up to a second or two on some
file systems, so "touch then compare immediately" is not reliable. And uptodate is the building block of Ant’s
incremental build: javac and jar do the same comparison internally, as covered in
Build Automation Fundamentals.
Source: Apache Ant manual: Available
Source: Apache Ant manual: Uptodate
if and unless on tasks
Since Ant 1.9.1 the namespaces ant:if and ant:unless add conditions to any task or nested element, with the three tests
true (the value is true), blank (the value is empty) and set (a property is set). The project element declares
them with xmlns:if="ant:if" and xmlns:unless="ant:unless", as the header of this build file does.
<target name="task-level">
<echo if:true="${strict}">strict build</echo>
<echo unless:true="${strict}">relaxed build</echo>
<echo if:set="release.version">releasing ${release.version}</echo>
<echo unless:blank="${release.version}">version is not blank</echo>
</target>
$ ant -q -f flow.xml task-level
[echo] relaxed build
[echo] version is not blank
BUILD SUCCESSFUL
Total time: 0 seconds
$ ant -q -f flow.xml -Dstrict=true -Drelease.version=2.0 task-level
[echo] strict build
[echo] releasing 2.0
[echo] version is not blank
BUILD SUCCESSFUL
Total time: 0 seconds
The first run shows a trap. With release.version unset, ${release.version} is not expanded to an empty string; it stays
the literal text ${release.version}, which is not blank, so unless:blank does not skip the task. Test a property with
if:set or unless:set, which look at the property by name, and use blank for values you know are defined.
fail
fail throws a BuildException, so it stops the build with a message.
| Attribute or element | Meaning |
|---|---|
|
The text of the exception; the text of the element can be used instead. Without either, the message is |
|
Fail only when the named property is set ( |
|
The exit code of the JVM if the exception is not caught (Ant 1.6.2). |
nested |
Fail when the single nested condition holds (Ant 1.6.2). Without a message, the text is |
<target name="fail-unless"><fail unless="release.version"/></target>
<target name="fail-message"><fail message="data/tides.csv is missing"/></target>
<target name="fail-text"><fail>stations.csv has no header</fail></target>
<target name="fail-condition">
<property name="min.java" value="21"/>
<fail message="Java ${min.java} or newer is required">
<condition><not><javaversion atleast="${min.java}"/></not></condition>
</fail>
<echo message="running on Java ${ant.java.version}, at least ${min.java}"/>
</target>
$ ant -q -f flow.xml fail-unless
BUILD FAILED
/work/tidepool/flow.xml:39: unless=release.version
Total time: 0 seconds
$ echo $?
1
<target name="fail-condition">
<property name="min.java" value="21"/>
<fail message="Java ${min.java} or newer is required">
<condition><not><javaversion atleast="${min.java}"/></not></condition>
</fail>
<echo message="running on Java ${ant.java.version}, at least ${min.java}"/>
</target>
$ ant -q -f flow.xml fail-condition
[echo] running on Java 26, at least 21
BUILD SUCCESSFUL
Total time: 0 seconds
$ ant -q -f flow.xml -Dmin.java=99 fail-condition
BUILD FAILED
/work/tidepool/flow.xml:44: Java 99 or newer is required
Total time: 0 seconds
$ echo $?
1
<target name="fail-status"><fail message="stations check failed" status="3"/></target>
<target name="fail-empty"><fail/></target>
<target name="fail-if"><property name="strict" value="true"/><fail if="strict" message="strict mode"/></target>
<!-- waitfor / parallel / sequential -->
<target name="waitfor" depends="clean">
<parallel>
<sequential>
<sleep milliseconds="700"/>
<echo file="${out}/ready.flag" message="ready"/>
<echo message="producer: wrote ready.flag"/>
</sequential>
<sequential>
<waitfor maxwait="5" maxwaitunit="second" checkevery="100" timeoutproperty="gave.up">
<available file="${out}/ready.flag"/>
</waitfor>
<echo message="consumer: gave.up=${gave.up}"/>
</sequential>
</parallel>
</target>
$ ant -q -f flow.xml fail-status
BUILD FAILED
/work/tidepool/flow.xml:49: stations check failed
Total time: 0 seconds
$ echo $?
3
The exit status is what a CI server sees: any failed build exits with 1, and status lets different failures exit with
different codes, here 3. A <fail/> with nothing reports No message, and the message can be given as an attribute or as the text of the element:
$ ant -q -f flow.xml fail-empty
BUILD FAILED
/work/tidepool/flow.xml:50: No message
Total time: 0 seconds
$ echo $?
1
$ ant -q -f flow.xml fail-message
BUILD FAILED
/work/tidepool/flow.xml:40: data/tides.csv is missing
Total time: 0 seconds
$ ant -q -f flow.xml fail-text
BUILD FAILED
/work/tidepool/flow.xml:41: stations.csv has no header
Total time: 0 seconds
Source: Apache Ant manual: Fail
waitfor
waitfor blocks until all nested conditions are true. It is built to synchronise concurrent branches of a parallel
and to wait for a server.
| Attribute | Meaning | Default |
|---|---|---|
|
How long to wait, in |
180000 milliseconds, that is 3 minutes |
|
The polling interval and its unit. |
500 milliseconds |
|
The property to set if |
none |
<target name="waitfor" depends="clean">
<parallel>
<sequential>
<sleep milliseconds="700"/>
<echo file="${out}/ready.flag" message="ready"/>
<echo message="producer: wrote ready.flag"/>
</sequential>
<sequential>
<waitfor maxwait="5" maxwaitunit="second" checkevery="100" timeoutproperty="gave.up">
<available file="${out}/ready.flag"/>
</waitfor>
<echo message="consumer: gave.up=${gave.up}"/>
</sequential>
</parallel>
</target>
$ ant -q -f flow.xml waitfor
[echo] producer: wrote ready.flag
[echo] consumer: gave.up=${gave.up}
BUILD SUCCESSFUL
Total time: 0 seconds
gave.up is unset, so it prints literally: the file appeared in time. The next target lets the timeout expire:
<target name="waitfor-timeout" depends="clean">
<waitfor maxwait="500" checkevery="100" timeoutproperty="gave.up">
<available file="${out}/never.flag"/>
</waitfor>
<echo message="gave.up=${gave.up}"/>
<waitfor maxwait="500" checkevery="100">
<available file="${out}/never.flag"/>
</waitfor>
</target>
$ ant -q -f flow.xml waitfor-timeout
[echo] gave.up=true
BUILD SUCCESSFUL
Total time: 1 second
With timeoutproperty set, the build learns that the wait ended in a timeout. The second waitfor, which has no
timeoutproperty, also timed out, and the build reached its end with BUILD SUCCESSFUL: in Ant 1.10.18 a waitfor
that times out without a timeoutproperty does not fail the build, although the manual’s wording is that the task will
"fail" if the conditions do not come true in maxwait. If a timeout must stop the build, set timeoutproperty and follow the task
with <fail if="gave.up"/>.
Source: Apache Ant manual: Waitfor
parallel and sequential
parallel runs each nested task in its own thread and waits for all of them. sequential is a container that runs its
children in order, so that one thread of a parallel can do several steps.
| Attribute | Meaning | Default |
|---|---|---|
|
Maximum number of threads running at once. Tasks start in the order written. |
all at once |
|
Maximum threads per processor; overrides |
defers to |
|
Milliseconds before the whole execution is terminated. |
none |
|
Complete as soon as any nested task fails, without waiting for the others. |
|
|
Currently has no effect. |
1000 |
A nested <daemons> element holds tasks that run in daemon threads: parallel does not wait for them, and they cannot
keep Ant alive. The manual’s own caution applies: Ant’s core is believed to be thread safe, but individual tasks are not
tested for it, and javac in particular is not re-entrant, so parallel is for running independent external programs,
such as an application server beside a test run, and not a general way to speed up a build.
<target name="parallel" depends="clean">
<parallel threadCount="2">
<sequential><sleep milliseconds="300"/><echo message="task A done"/></sequential>
<sequential><sleep milliseconds="300"/><echo message="task B done"/></sequential>
<sequential><echo message="task C done"/></sequential>
</parallel>
</target>
$ ant -q -f flow.xml parallel
[echo] task B done
[echo] task C done
[echo] task A done
BUILD SUCCESSFUL
Total time: 0 seconds
With threadCount="2", tasks A and B are started together and C waits for a free thread. The order of the messages is not
guaranteed by parallel; the output above is one real run.
<target name="parallel-fail" depends="clean">
<parallel failonany="true">
<fail message="station SF failed"/>
<sequential><sleep seconds="3"/><echo message="slow task finished"/></sequential>
</parallel>
</target>
$ ant -q -f flow.xml parallel-fail
BUILD FAILED
/work/tidepool/flow.xml:90: station SF failed
Total time: 0 seconds
$ echo $?
1
<target name="parallel-timeout" depends="clean">
<parallel timeout="500">
<sleep seconds="5"/>
</parallel>
</target>
$ ant -q -f flow.xml parallel-timeout
BUILD FAILED
/work/tidepool/flow.xml:96: Parallel execution timed out
Total time: 0 seconds
$ echo $?
1
<target name="daemons" depends="clean">
<parallel>
<daemons>
<sequential><sleep seconds="60"/><echo message="never printed"/></sequential>
</daemons>
<echo message="the main work finished"/>
</parallel>
<echo message="build continues without waiting for the daemon"/>
</target>
$ ant -q -f flow.xml daemons
[echo] the main work finished
[echo] build continues without waiting for the daemon
BUILD SUCCESSFUL
Total time: 0 seconds
failonany ended the build at once with the message of the failing branch rather than waiting three seconds for the slow
branch. A timeout makes the whole parallel fail with Parallel execution timed out. The daemon thread, which would
have slept for a minute, did not hold up the build.
Source: Apache Ant manual: Parallel
Source: Apache Ant manual: Sequential
retry
retry runs one nested task and re-runs it if it fails. retrycount is required; retrydelay is the pause between
attempts in milliseconds (Ant 1.8.3). The task exists since Ant 1.7.1. It is meant for unreliable resources, such as a get
from a flaky server.
<target name="retry" depends="clean">
<parallel>
<sequential>
<sleep milliseconds="450"/>
<copy file="data/stations.csv" tofile="${out}/stations.csv"/>
</sequential>
<retry retrycount="5" retrydelay="200">
<copy file="${out}/stations.csv" tofile="${out}/copy.csv" failonerror="true"/>
</retry>
</parallel>
<echo message="copy.csv exists: ${out}/copy.csv"/>
</target>
$ ant -q -f flow.xml retry
[echo] copy.csv exists: /work/tidepool/build/copy.csv
BUILD SUCCESSFUL
Total time: 0 seconds
The copy fails while stations.csv does not exist, and retry tries again every 200 milliseconds until the other branch
has created it. When every attempt fails, the build fails with the collected messages:
<target name="retry-exhausted" depends="clean">
<retry retrycount="3" retrydelay="100">
<get src="http://localhost:9/stations.csv" dest="${out}/stations.csv"/>
</retry>
</target>
$ ant -q -f flow.xml retry-exhausted
BUILD FAILED
/work/tidepool/flow.xml:125: Task [get] failed after [3] attempts; giving up.
Error messages:
java.net.ConnectException: Connection refused
java.net.ConnectException: Connection refused
java.net.ConnectException: Connection refused
java.net.ConnectException: Connection refused
Total time: 0 seconds
$ echo $?
1
retrycount="3" produced four connection errors in the message: Ant runs the task once and then retries it up to
retrycount more times, so count the number of retries, not the number of attempts, when you size it.
Source: Apache Ant manual: Retry
Why Ant has no loops
Ant’s build file is declarative XML with no loop construct. The <for> and <foreach> tasks that older build files use
come from Ant-Contrib, a third-party task library that the Ant site lists on its projects page but that is dormant: its
newest files on SourceForge date from 2008. Do not add it to a new build. The core alternatives are:
-
A macro (
macrodef) with a parameter, called once per item. It is the right tool when the list is short and known. -
subant(orant) over adirset,filelistorfileset, which runs a target once per directory or build file, with that directory asbasedir. -
A
<script>loop in a language with a script engine. On JDK 15 and later this needs an engine added to the class path, as thescriptfilterexample on Filters & Mappers shows. -
A resource collection or a mapper that makes the task itself process many files, as
copyandapplydo: very often the loop is already inside a task.
<target name="loop-macro">
<report-station id="SF"/>
<report-station id="BOS"/>
<report-station id="HNL"/>
</target>
$ ant -q -f flow.xml loop-macro
[echo] station SF
[echo] station BOS
[echo] station HNL
BUILD SUCCESSFUL
Total time: 0 seconds
<target name="loop-subant">
<subant target="report" genericantfile="${ant.file}" inheritall="false">
<dirset dir="stations" includes="*"/>
</subant>
</target>
$ ant -q -f flow.xml loop-subant
[echo] reporting tidepool-flow in /work/tidepool/stations/BOS
[echo] reporting tidepool-flow in /work/tidepool/stations/HNL
[echo] reporting tidepool-flow in /work/tidepool/stations/SF
BUILD SUCCESSFUL
Total time: 0 seconds
The subant call ran the report target once in each station directory (inheritall="false" keeps the callers' properties
out), and the order is that of the dirset. subant is covered in
Multi-File Builds: import, include & subant.
Source: Apache Ant manual: Macrodef
Source: Apache Ant manual: Subant
Source: Ant-Contrib files on SourceForge
Equivalents in Maven and Gradle
| Need | Ant | Maven | Gradle |
|---|---|---|---|
Decide from the environment |
|
Profile activation by JDK, OS, property or file (see Profiles) |
Plain Kotlin or Groovy |
Fail on an unsuitable environment |
|
Enforcer rules such as |
|
Skip work whose output is current |
|
the compiler plugin’s stale-source detection |
declared task inputs and outputs, |
Run steps concurrently |
|
|
|
Repeat a step |
|
plugin executions, one per module in the reactor |
a loop in the build script, or a task per item |
Source: Gradle: Skipping tasks
Where the books are outdated
Java Development with Ant (Hatcher and Loughran, 2002) was written for Ant 1.5. It describes condition, available,
uptodate, fail, waitfor and sleep, but cannot cover javaversion (1.10.2), antversion, matches, resourceexists
or canCreateSymlink (1.10.16), the else attribute of condition (1.6.3), the ant:if and ant:unless namespaces (1.9.1) or
retry (1.7.1). Examples that use <for>, <foreach> or <if> from Ant-Contrib should be rewritten with the core
tools above.
Common mistakes
-
Reading an unset property as
false: it is unset, and${name}stays as literal text, including inistrue,equalsandunless:blank. Useelse="false"orisset. -
Expecting
xorto mean "exactly one". -
Expecting
waitforto fail on timeout; it only setstimeoutpropertyif you give one. -
Assuming
unlesson a target also skips its dependencies; it does not. -
Using
parallelto runjavacandjavadoctogether: the tasks are not designed for it. -
Sizing
retrycountas the number of attempts: it is the number of retries. -
Checking a file with
uptodateright after writing it on a file system with coarse timestamps. -
Using a
scriptconditionon a JDK with no JavaScript engine.
Where to go next
-
Targets & Extension Points for target-level
ifandunless. -
Properties for immutability and the built-in properties.
-
Utility Tasks for
sleep,tstampandinput. -
Macros & Presets for replacing loops and
antcall. -
Comparing Ant, Maven & Gradle for the wider comparison.