Ant Macros & Presets

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 build file and every console output below comes from a real run of that release on JDK 26 on 2026-10-16, in a scratch copy of the tidepool project; the project directory is shortened to /work/mp in the output and the closing Total time line is omitted. Features newer than Ant 1.6 name their release in the text.

A target is a node in a graph and cannot take arguments (see Targets & Extension Points and the trap of antcall). When a build needs the opposite, a reusable step that takes parameters and can be used any number of times, Ant offers macros. A macro is a new task defined from existing tasks, so using one looks exactly like using javac or echo. Four tasks make the family:

  • <macrodef> defines a task from a <sequential> template with attributes, nested elements and nested text.

  • <presetdef> defines a task or type from another one by presetting some of its attributes and nested elements.

  • <componentdef> defines a nested component (a condition, a selector) rather than a task.

  • <augment> modifies an existing reference (a fileset, a path) by adding attributes or nested elements.

macrodef

A first macro for the tidepool modules

Compiling a module is the same call with different directories and class paths three times in the tidepool build. A macro states it once:

<project name="tidepool" default="all" basedir=".">
  <property name="lib.dir" location="lib"/>

  <macrodef name="compile-module" description="javac one tidepool module into its build/classes">
    <attribute name="module"/>
    <attribute name="release" default="21"/>
    <attribute name="classes" default="@{module}/build/classes"/>
    <element name="extra-classpath" optional="true"/>
    <sequential>
      <mkdir dir="@{classes}"/>
      <javac srcdir="@{module}/src/main/java" destdir="@{classes}"
             release="@{release}" includeantruntime="false">
        <classpath>
          <extra-classpath/>
        </classpath>
      </javac>
    </sequential>
  </macrodef>

  <target name="all">
    <compile-module module="tidepool-model"/>
    <compile-module module="tidepool-engine">
      <extra-classpath>
        <pathelement location="tidepool-model/build/classes"/>
        <fileset dir="${lib.dir}" includes="commons-csv-*.jar"/>
      </extra-classpath>
    </compile-module>
    <compile-module module="tidepool-cli" release="17">
      <extra-classpath>
        <pathelement location="tidepool-model/build/classes"/>
        <pathelement location="tidepool-engine/build/classes"/>
        <fileset dir="${lib.dir}" includes="*.jar"/>
      </extra-classpath>
    </compile-module>
  </target>
</project>
Buildfile: /work/mp/macro1.xml

all:
    [mkdir] Created dir: /work/mp/tidepool-model/build/classes
    [javac] Compiling 4 source files to /work/mp/tidepool-model/build/classes
    [mkdir] Created dir: /work/mp/tidepool-engine/build/classes
    [javac] Compiling 2 source files to /work/mp/tidepool-engine/build/classes
    [mkdir] Created dir: /work/mp/tidepool-cli/build/classes
    [javac] Compiling 1 source file to /work/mp/tidepool-cli/build/classes

BUILD SUCCESSFUL

What the definition shows:

  • <attribute> declares an attribute of the new task. Without a default it is required. A default may use an attribute declared before it (classes uses @{module}).

  • <element> declares a nested element. Whatever the caller writes inside <extra-classpath> is pasted where the template contains <extra-classpath/>. Here the template wraps it in a <classpath>, so the caller supplies only path elements.

  • <sequential> is the template: an ordinary list of tasks run in order. Attribute values replace @{name} wherever it appears in the template, including in nested elements and inside other attribute values.

  • The macro is a task. The log lines are labelled by the inner tasks (mkdir, javac); the macro itself adds no line.

  • The description is documentation only; Ant does not use it.

The attributes of macrodef

Attribute Meaning Default

name

The name of the new task. Required.

uri

The namespace URI the definition lives in. A task defined with uri="urn:tidepool" is used as <tp:name> once the prefix is bound with xmlns:tp="urn:tidepool".

none

description

Documentation only.

none

backtrace

Whether an error inside the macro prints the stack of macro calls (since Ant 1.7).

true

The nested elements

Element Meaning Attributes

<attribute>

A named parameter, substituted as @{name}. Required unless it has a default. The case of the name is ignored.

name (required), default, description (since Ant 1.6.1), doubleexpanding (since Ant 1.8.3, default true)

<element>

A named slot the caller fills with nested tasks or types. The case of the name is ignored.

name (required), optional (default false), implicit (since Ant 1.6.2, default false), description (since Ant 1.6.1)

<text>

The text content of the call becomes an attribute of this name (since Ant 1.6.1). Without a <text> element, nested text in a call is an error.

name (required), optional (default false), trim (default false), description

Optional, implicit and text elements

An optional element may be omitted. A required element (the default) that the caller leaves out is an error:

<macrodef name="needs-body">
  <element name="body"/>
  <sequential><body/></sequential>
</macrodef>
<target name="nobody"><needs-body/></target>
nobody:
BUILD FAILED
/work/mp/more.xml:27: Required nested element body missing

An implicit element has no wrapper: everything the caller writes inside the macro goes into the slot. There can only be one implicit element. A text element turns the body of the call into an attribute, optionally trimmed:

<macrodef name="banner">
  <attribute name="char" default="*"/>
  <text name="message" trim="true"/>
  <sequential>
    <echo>@{char}@{char} @{message} @{char}@{char}</echo>
  </sequential>
</macrodef>

<macrodef name="for-each-file">
  <attribute name="dir"/>
  <element name="do" implicit="true"/>
  <sequential>
    <echo>scanning @{dir}</echo>
    <do/>
  </sequential>
</macrodef>

<target name="go">
  <banner>
    tidepool 1.0.0
  </banner>
  <banner char="#">  stations: ${stations}  </banner>
  <for-each-file dir="data">
    <echo>implicit element body</echo>
    <echo>second task</echo>
  </for-each-file>
</target>

Run with ant -Dstations=3:

go:
     [echo] ** tidepool 1.0.0 **
     [echo] ## stations: 3 ##
     [echo] scanning data
     [echo] implicit element body
     [echo] second task
BUILD SUCCESSFUL

The trimmed text lost its surrounding whitespace and newlines, and ${stations} in the body was expanded before the macro saw it. (The macro named for-each-file does not iterate: it only wraps a body. Ant has no loop, see the last section.)

Errors a caller can make

Using a macro wrongly gives precise messages:

<macrodef name="greet">
  <attribute name="who"/>
  <attribute name="greeting" default="Hello"/>
  <sequential>
    <echo>@{greeting}, @{who}! (Who=@{WHO})</echo>
  </sequential>
</macrodef>
Call Result

<greet who="tide"/> and <greet who="moon" greeting="Ahoy"/>

Hello, tide! (Who=tide) and Ahoy, moon! (Who=moon): the case of the name is ignored, so @{WHO} is the same attribute.

<greet/>

err.xml:14: required attribute who not set

<greet who="x" colour="red"/>

err.xml:15: Unknown attribute [colour]

<greet who="x"><echo>nested</echo></greet>

err.xml:17: unsupported element echo

(The line numbers are those of the scratch file; the messages are exactly as Ant printed them.) The location in each message is the call site, which is where the mistake is.

backtrace

When a task inside a macro fails, Ant by default shows both the call site and the line inside the macro, so that the failure can be traced:

<macrodef name="check-loud">
  <attribute name="file"/>
  <sequential>
    <fail message="missing: @{file}"><condition><not><available file="@{file}"/></not></condition></fail>
  </sequential>
</macrodef>
<macrodef name="check-quiet" backtrace="false">
  <attribute name="file"/>
  <sequential>
    <fail message="missing: @{file}"><condition><not><available file="@{file}"/></not></condition></fail>
  </sequential>
</macrodef>
<target name="loud"><check-loud file="nope.csv"/></target>
<target name="quiet"><check-quiet file="nope.csv"/></target>
$ ant loud
loud:
BUILD FAILED
/work/mp/bt.xml:14: The following error occurred while executing this line:
/work/mp/bt.xml:5: missing: nope.csv

$ ant quiet
quiet:
BUILD FAILED
/work/mp/bt.xml:15: missing: nope.csv

backtrace="false" is for macros that are a deliberate, user-facing check: the error should point at the caller’s line only, not at the internals of the macro.

@{} versus ${}: how a macro is expanded

Two kinds of substitution happen in a macro and the order matters.

  • ${name} is the property expansion, done by Ant when a task is configured at run time.

  • @{name} is the macro attribute substitution, done when the macro is called. It works on the template text, before the inner tasks run. Use @@{name} to write a literal @{name}, just as $${name} writes a literal ${name}.

Because the attribute is substituted into the template and the template is then expanded as properties, a value that contains a property reference is expanded twice by default. The Ant FAQ describes it: an attribute of $${basedir} ends up printing the value of basedir and not the text ${basedir} that one expects. Since Ant 1.8.3 the attribute can switch the second expansion off with doubleexpanding="false". Escapes, defaults and double expansion, run together:

<macrodef name="show">
  <attribute name="text"/>
  <sequential><echo>twice : @{text}</echo></sequential>
</macrodef>
<macrodef name="show-once">
  <attribute name="text" doubleexpanding="false"/>
  <sequential><echo>once  : @{text}</echo></sequential>
</macrodef>
<macrodef name="esc">
  <attribute name="x" default="v"/>
  <sequential><echo>literal @@{x} versus @{x}, dollar $${x}</echo></sequential>
</macrodef>
<macrodef name="deflt">
  <attribute name="two" default="@{one}"/>
  <attribute name="one"/>
  <sequential><echo>one=@{one} two=@{two}</echo></sequential>
</macrodef>
<macrodef name="deflt2">
  <attribute name="one"/>
  <attribute name="two" default="@{one}"/>
  <sequential><echo>one=@{one} two=@{two}</echo></sequential>
</macrodef>

<target name="go">
  <show text="$${basedir}"/>
  <show-once text="$${basedir}"/>
  <esc/>
  <deflt one="test"/>
  <deflt2 one="test"/>
</target>
go:
     [echo] twice : /work/mp
     [echo] once  : ${basedir}
     [echo] literal @{x} versus v, dollar ${x}
     [echo] one=test two=@{one}
     [echo] one=test two=test
BUILD SUCCESSFUL

The five lines show, in order: the double expansion (the value of basedir), doubleexpanding="false" (the literal text), the @@{x} escape, a default that refers to an attribute declared after it (two=@{one}, because one was not yet set), and the same default declared before it (two=test). The rule for defaults is therefore "declare the attribute you refer to first".

The double expansion is deliberate. It is what makes this idiom possible, a nested property name built from a macro attribute, which ${thing.${choice}} cannot do in plain Ant (the FAQ’s propertycopy example is the same idea):

<property name="choice" value="2"/>
<property name="thing.1" value="one"/>
<property name="thing.2" value="two"/>
<macrodef name="pick">
  <attribute name="n"/>
  <sequential><echo>thing @{n} = ${thing.@{n}}</echo></sequential>
</macrodef>
<target name="go">
  <echo>direct nesting: ${thing.${choice}}</echo>
  <pick n="1"/>
  <pick n="${choice}"/>
</target>
go:
     [echo] direct nesting: ${thing.${choice}}
     [echo] thing 1 = one
     [echo] thing 2 = two
BUILD SUCCESSFUL

The first line shows that the plain nesting is not expanded; the macro does it. A macro that passes untrusted text or text from a file through to a task should use doubleexpanding="false" so that a stray $ in the data is not interpreted.

local inside a macro

Properties are global and immutable (see Properties), so a macro that sets a property to hold an intermediate value has a classic bug: the first call defines it for the rest of the build and every later call silently keeps the first value. The <local> task declares a property that is visible only in the enclosing scope, and a macro body is a scope:

<macrodef name="stamp-bad">
  <attribute name="module"/>
  <sequential>
    <property name="out" value="@{module}.out"/>
    <echo>bad : @{module} writes ${out}</echo>
  </sequential>
</macrodef>
<macrodef name="stamp-good">
  <attribute name="module"/>
  <sequential>
    <local name="out"/>
    <property name="out" value="@{module}.out"/>
    <echo>good: @{module} writes ${out}</echo>
  </sequential>
</macrodef>
<target name="go">
  <stamp-bad module="model"/>
  <stamp-bad module="engine"/>
  <stamp-good module="model"/>
  <stamp-good module="engine"/>
  <echo>after the macros: out=${out}</echo>
</target>
go:
     [echo] bad : model writes model.out
     [echo] bad : engine writes model.out
     [echo] good: model writes model.out
     [echo] good: engine writes engine.out
     [echo] after the macros: out=model.out
BUILD SUCCESSFUL

The second stamp-bad still reports model.out: the property from the first call won. The good calls each got their own out, shadowing the global one that the bad macro had left behind. local has been available since Ant 1.8.0; before it, the usual workaround was to build the property name from an attribute, <property name="@{module}.out" .../>. Alternatively avoid the property and pass the value straight into the nested tasks with @{...}, as compile-module does.

Macros in shared files and in namespaces

A macro can live in an imported file; once the import has been processed it is available in the whole project. A uri puts it in a namespace so that names cannot clash with other libraries or with built-in tasks:

<project name="tp-macros" xmlns:if="ant:if" xmlns:unless="ant:unless">
  <macrodef name="check-file" uri="urn:tidepool" description="fail unless a file exists">
    <attribute name="file"/>
    <attribute name="strict" default="true"/>
    <sequential>
      <echo>checking @{file}</echo>
      <fail message="missing: @{file}" if:true="@{strict}">
        <condition><not><available file="@{file}"/></not></condition>
      </fail>
      <echo unless:true="@{strict}">lenient mode: @{file} is optional</echo>
    </sequential>
  </macrodef>
</project>
<project name="use" default="go" xmlns:tp="urn:tidepool">
  <import file="tp-macros.xml"/>

  <target name="define-late">
    <macrodef name="late"><sequential><echo>defined inside a target, visible afterwards</echo></sequential></macrodef>
  </target>

  <target name="go" depends="define-late">
    <tp:check-file file="data/stations.csv"/>
    <tp:check-file file="data/tides.csv" strict="false"/>
    <late/>
  </target>
</project>
define-late:
go:
     [echo] checking data/stations.csv
     [echo] checking data/tides.csv
     [echo] lenient mode: data/tides.csv is optional
     [echo] defined inside a target, visible afterwards
BUILD SUCCESSFUL

With strict="false" removed (so the default true applies) the second check fails and the error names both files:

BUILD FAILED
/work/mp/use.xml:10: The following error occurred while executing this line:
/work/mp/tp-macros.xml:7: missing: data/tides.csv

Three things to note. The if:true and unless:true attributes (the ant:if and ant:unless namespaces, Ant 1.9.1) are evaluated after @{strict} has been substituted, so a macro attribute can switch a nested task on and off (see Targets & Extension Points). A macro defined inside a target is visible to the rest of the project after that target has run, not only in the target, so the definition is global, as for any other definition task. And the best home for macros that many builds share is an antlib (a JAR with an antlib.xml that holds the macrodef and presetdef entries), loaded through a namespace URI such as antlib:com.example.tidepool; see Antlibs & Namespaces.

Redefinition

A second definition of the same name replaces the first, and Ant says so:

Trying to override old definition of task hello
go:
     [echo] two
BUILD SUCCESSFUL

The message appears at the default log level, before the targets run. The same happens when a macro is named like a built-in task. A macro named echo that uses echo in its body calls itself, not the built-in one; In a test the redefined echo called itself and failed with The "echo" macro does not support nested text data. Give macros distinct names, or put them in a namespace.

presetdef

<presetdef> creates a new task or type from an existing one with some attributes and nested elements fixed. The preset values are ordinary defaults: the caller can override any attribute, and add nested elements. Presets are the right tool when the "macro" would only forward every attribute unchanged, as with a javac that always uses the same release and compiler flags.

<project name="preset" default="go" xmlns:tp="urn:tidepool">
  <property name="classes.dir" location="build/preset-classes"/>

  <presetdef name="tp.javac">
    <javac release="21" includeantruntime="false" debug="true" destdir="${classes.dir}">
      <compilerarg value="-Xlint:all"/>
    </javac>
  </presetdef>

  <presetdef name="jar" uri="urn:tidepool">
    <jar compress="false" level="0"/>
  </presetdef>

  <target name="go">
    <mkdir dir="${classes.dir}"/>
    <tp.javac srcdir="tidepool-model/src/main/java"/>
    <tp.javac srcdir="tidepool-engine/src/main/java" debug="false">
      <classpath>
        <pathelement location="${classes.dir}"/>
        <fileset dir="lib" includes="commons-csv-*.jar"/>
      </classpath>
    </tp.javac>
    <tp:jar destfile="build/preset.jar" basedir="${classes.dir}"/>
  </target>
</project>
go:
    [mkdir] Created dir: /work/mp/build/preset-classes
 [tp.javac] Compiling 4 source files to /work/mp/build/preset-classes
 [tp.javac] Compiling 2 source files to /work/mp/build/preset-classes
   [tp:jar] Building jar: /work/mp/build/preset.jar
BUILD SUCCESSFUL

The task is logged under its new name. The first use relies on the preset only, the second overrides debug and adds a nested <classpath>, and the third task is a namespaced jar. Inspecting the JAR with unzip -v shows the entries as Stored, i.e. the preset compress="false" took effect.

A name given to presetdef may reuse the name of an existing task only in a namespace, because otherwise the definition replaces the built-in (a practice that surprises later readers). The example uses the dotted name tp.javac, which is legal as a task name, and the namespaced jar.

When the properties are evaluated

The values in a preset are evaluated each time the definition is used, not when it is defined. This is deliberate and differs from macrodef's attribute substitution at call time:

<presetdef name="showmessage">
  <echo>message is '${message}'</echo>
</presetdef>
<target name="go">
  <showmessage/>
  <property name="message" value="Message 1"/>
  <showmessage/>
  <antcall target="called"><param name="message" value="Message 2"/></antcall>
</target>
<target name="called"><showmessage/></target>
go:
[showmessage] message is '${message}'
[showmessage] message is 'Message 1'
called:
[showmessage] message is 'Message 2'
BUILD SUCCESSFUL

The manual shows a trick to fix the value at definition time: wrap the presetdef in a macro and pass the property as an attribute, because @{...} is substituted when the macro is called:

<macrodef name="showmessage-presetdef">
  <attribute name="messageval"/>
  <sequential>
    <presetdef name="showmessage">
      <echo>message is '@{messageval}'</echo>
    </presetdef>
  </sequential>
</macrodef>

<target name="trick">
  <property name="message" value="at definition"/>
  <showmessage-presetdef messageval="${message}"/>
  <showmessage/>
  <antcall target="called"><param name="message" value="in the call"/></antcall>
</target>
trick:
[showmessage] message is 'at definition'
called:
[showmessage] message is 'at definition'
BUILD SUCCESSFUL

The called target saw the value from the definition, not the <param>. This helps when the preset is used in child builds started with inheritAll="false", which would not see a property.

macrodef or presetdef?

<macrodef> <presetdef>

What it wraps

a list of tasks (<sequential>)

exactly one task or type

Parameters

declared: attributes, elements, text

none: the original’s attributes and elements, overridable

Typical use

a multi-step operation with arguments (compile-module)

a standard configuration of one task (the house javac)

Property evaluation

when the macro runs (and again after @{} substitution)

when the preset is used

Since

Ant 1.6 (several attributes later)

Ant 1.6

Use a preset whenever the new task would only repeat the attributes of one existing task; use a macro when there is more than one step or when the template needs a computed value.

componentdef

<componentdef> is a typedef with two restrictions: the component can only be used inside other types or tasks that accept it, and several components may share a name if they implement different interfaces. Ant itself uses this so that <or> can be both a condition and a selector. A component is therefore never a top-level task:

<componentdef name="either" classname="org.apache.tools.ant.taskdefs.condition.Or"/>
...
<condition property="found">
  <either>
    <available file="data/stations.csv"/>
    <available file="data/other.csv"/>
  </either>
</condition>

and the same component used as a task fails:

<target name="go">
  <either><istrue value="true"/></either>
</target>
go:
BUILD FAILED
/work/mp/comp2.xml:4: Problem: failed to create task or type either
Cause: The name is undefined.
Action: Check the spelling.
Action: Check that any custom tasks/types have been declared.
Action: Check that any <presetdef>/<macrodef> declarations have taken place.

Used inside <condition> as above, the component printed found=true (the build file had <echo>found=${found}</echo> after the condition). Use componentdef for a custom condition or selector class that should share a name with another one; for everything else use typedef or an antlib (see Custom Types, Conditions & Selectors). A wrong class name fails when the definition is read: componentdef class ... cannot be found.

augment

<augment> (since Ant 1.8.1) reloads a previously declared reference by its id and treats the augment element as if it were the original element. It can add attributes (replacing them) and nested elements. It throws a build exception when no reference of that id exists. It is useful for adding to a type a shared file defined without editing that file.

<fileset id="sources" dir="tidepool-model/src/main/java" includes="**/*.java"/>
<augment id="sources" excludes="**/Height.java"/>
<augment id="sources">
  <filename name="**/S*.java"/>
</augment>

<target name="go">
  <pathconvert property="listing" refid="sources" pathsep=" ">
    <mapper type="flatten"/>
  </pathconvert>
  <echo>sources: ${listing}</echo>
</target>
go:
     [echo] sources: Station.java
BUILD SUCCESSFUL

Without the two augment elements the same build prints sources: Constituent.java Height.java Station.java TideEvent.java. The four model sources were reduced to one: the first augment set excludes, which removed Height.java, and the second added a <filename> selector that keeps only names starting with S. In a shared common.xml, a module can define a path or fileset with a given id and a later augment in the importing file extends it.

Replacing antcall and Ant-Contrib

Two habits from older builds have direct replacements.

antcall as a function. The two builds below print the same greeting twice. The first calls a target through antcall, the second calls a macro:

<target name="init"><echo>init</echo></target>

<target name="greet-target" depends="init"><echo>hello ${who}</echo></target>
<target name="with-antcall" depends="init">
  <antcall target="greet-target"><param name="who" value="tide"/></antcall>
  <antcall target="greet-target"><param name="who" value="moon"/></antcall>
</target>

<macrodef name="greet">
  <attribute name="who"/>
  <sequential><echo>hello @{who}</echo></sequential>
</macrodef>
<target name="with-macro" depends="init">
  <greet who="tide"/>
  <greet who="moon"/>
</target>
$ ant with-antcall
init:
     [echo] init
with-antcall:
init:
     [echo] init
greet-target:
     [echo] hello tide
init:
     [echo] init
greet-target:
     [echo] hello moon
BUILD SUCCESSFUL

$ ant with-macro
init:
     [echo] init
with-macro:
     [echo] hello tide
     [echo] hello moon
BUILD SUCCESSFUL

antcall started a new project twice and ran init three times in total; the macro ran in the same project, once.

Ant-Contrib. Many build files downloaded from the internet use the <if>, <for>, <foreach>, <var> and <propertycopy> tasks of Ant-Contrib, a third-party task library. It is listed on the Ant projects page but dormant (the last release is 1.0b3), so new builds should avoid it. Core Ant covers most of what it was used for:

Ant-Contrib idiom Core Ant replacement

<if> / <then> / <else>

if:true, if:set, unless:... attributes on any task (Ant 1.9.1); <condition> to compute a property; if and unless on targets

<var> (a changeable property)

<local> plus a <property> in a macro; a macro attribute; a new property name per use

<propertycopy> (a property whose name is computed)

a macro attribute and ${thing.@{n}} (double expansion, above)

<for> over a list

a macro called once per item; <apply> or <copy> with a mapper for files; <subant> over a <filelist> or <dirset> for sub-builds; <script> for a real loop

<foreach> calling a target

<subant> with genericantfile and a <dirset>, or a macro

<antcallback> (returns a property)

a child build that writes a properties file, loaded with loadproperties

The iteration idioms are on Conditions & Control Flow and scripting is on Scripting; the subant forms are on Multi-File Builds. What a macro cannot do, such as real exception handling or computed values, belongs in <script> or a custom task (Writing Custom Tasks).

The same ideas in Maven and Gradle

Maven has no build-file-level macros. Reuse is built into the model: a parent POM carries shared plugin configuration and version management, a plugin is the unit of reusable logic, and an antrun execution can hold a macrodef when a step needs Ant. Gradle builds are programs, so the language supplies the reuse: functions, classes and task types.

Ant Maven Gradle (Kotlin DSL)

<macrodef> with attributes

a plugin goal with parameters (tidepool:check-stations)

a function, or a task type (abstract class Check : DefaultTask()) with @Input properties

<presetdef> (a house javac)

pluginManagement in the parent POM, or the <configuration> of a plugin in the parent

tasks.withType<JavaCompile>().configureEach { options.release = 21 }

<augment>

inheritance of a <configuration> with combine.children

named("compileJava") { ... } after the original definition

<antcall>

rerun a phase

not available: tasks run at most once per build

a macro in an antlib

a plugin JAR

a convention plugin in build-logic

Pitfalls and good practice

  • Name macros so they do not collide. A macro with the name of a built-in task replaces it for the whole project (Ant prints Trying to override old definition of task ...). Use a prefix, a dotted name or a uri.

  • Declare @{...} defaults in the order of use. A default can refer only to an attribute declared before it.

  • Use <local> for intermediate values in a macro, or the second call silently keeps the first call’s value.

  • Remember the double expansion. Data that may contain $ goes in a doubleexpanding="false" attribute.

  • Remember the scope of a definition. A macro defined in a target is global once that target has run, and in an imported file it is global after the import.

  • Choose presetdef for one task, macrodef for several.

  • Keep description on public macros, and backtrace="false" only for deliberate checks.

  • Do not use antcall as a function call. It re-runs depends in a new project; use a macro.

  • Avoid Ant-Contrib in new builds; it is dormant and the core replacements above are shorter.

Where to go next