It is very easy to write your own task:
org.apache.tools.ant.Task
or another class that was designed to be extended.public void
method that takes a single argument. The name of the method
must begin with set
, followed by the attribute name, with the first character of
the name in uppercase, and the rest in lowercase*. That is, to
support an attribute named file you create a
method setFile
. Depending on the type of the argument, Ant will
perform some conversions for you, see below.parallel
), your class must implement the
interface org.apache.tools.ant.TaskContainer
. If you do so, your task
can not support any other nested elements. See below.public void addText(String)
method. Note that Ant
does not expand properties on the text it passes to the task.public
method that takes no arguments and
returns an Object
type. The name of the create method must begin
with create
, followed by the element name. An add (or addConfigured) method must be
a public void
method that takes a single argument of an Object
type
with a no-argument constructor. The name of the add (addConfigured) method must begin
with add
(addConfigured
), followed by the element name. For a more
complete discussion see below.public void execute()
method, with no arguments, that throws
a BuildException
. This method implements the task itself.* Actually the case of the letters after the first one doesn't really matter to Ant, using all lower case is a good convention, though.
UnknownElement
at parse time.
This UnknownElement
gets placed in a list within a target object, or
recursively within another UnknownElement
.
UnknownElement
is invoked using
an perform()
method. This instantiates the task. This means that tasks
only gets instantiated at run time.
project
and location
variables.target
variable.init()
is called at run time.createXXX()
methods or instantiated and added to this task via
its addXXX()
methods, at run time. Child elements corresponding
to addConfiguredXXX()
are created at this point but the
actual addConfigured method is not called.setXXX()
methods, at run time.addText()
method, at run time.setXXX()
methods, at run time.addConfiguredXXX()
methods, those methods get invoked now.execute()
is called at run time. If target1and
target2both depend on
target3, then running ant target1 target2 will run all tasks in
target3twice.
Ant will always expand properties before it passes the value of an attribute to the corresponding setter method. Since Ant 1.8, it is possible to extend Ant's property handling such that a non-string Object may be the result of the evaluation of a string containing a single property reference. These will be assigned directly via setter methods of matching type. Since it requires some beyond-the-basics intervention to enable this behavior, it may be a good idea to flag attributes intended to permit this usage paradigm.
The most common way to write an attribute setter is to use
a java.lang.String
argument. In this case Ant will pass the literal value
(after property expansion) to your task. But there is more! If the argument of you setter method
is
boolean
, your method will be passed the value trueif the value specified in the build file is one of
true,
yes, or
onand
falseotherwise.
char
or java.lang.Character
, your method will be passed
the first character of the value specified in the build file.int
, short
and
so on), Ant will convert the value of the attribute into this type, thus making sure that you'll
never receive input that is not a number for that attribute.java.io.File
, Ant will first determine whether the value given in
the build file represents an absolute path name. If not, Ant will interpret the value as a path
name relative to the project's basedir.org.apache.tools.ant.types.Resource
, Ant will resolve the string as
a java.io.File
as above, then pass in as
a org.apache.tools.ant.types.resources.FileResource
. Since Ant
1.8org.apache.tools.ant.types.Path
, Ant will tokenize the value
specified in the build file, accepting :and
;as path separators. Relative path names will be interpreted as relative to the project's basedir.
java.lang.Class
, Ant will interpret the value given in the build
file as a Java class name and load the named class from the system class loader.String
argument,
Ant will use this constructor to create a new instance from the value given in the build
file.org.apache.tools.ant.types.EnumeratedAttribute
, Ant
will invoke this class's setValue
method. Use this if your task
should support enumerated attributes (attributes with values that must be part of a predefined
set of values). See org/apache/tools/ant/taskdefs/FixCRLF.java
and the
inner AddAsisRemove
class used in setCr
for
an example.EnumeratedAttribute
and can result in cleaner code. Note
that any override of toString()
in the enumeration is ignored; the
build file must use the declared name (see Enum.getName()
). You may wish to use
lowercase enum constant names, in contrast to usual Java style, to look better in build
files. Since Ant 1.7.0What happens if more than one setter method is present for a given attribute? A method taking
a String
argument will always lose against the more specific methods. If
there are still more setters Ant could chose from, only one of them will be called, but we don't
know which, this depends on the implementation of your Java virtual machine.
Let's assume your task shall support nested elements with the name inner
. First of
all, you need a class that represents this nested element. Often you simply want to use one of
Ant's classes like org.apache.tools.ant.types.FileSet
to support
nested fileset
elements.
Attributes of the nested elements or nested child elements of them will be handled using the same
mechanism used for tasks (i.e. setter methods for
attributes, addText()
for nested text
and create/add/addConfigured methods for child elements).
Now you have a class NestedElement
that is supposed to be used for your
nested <inner>
elements, you have three options:
public NestedElement createInner()
public void addInner(NestedElement anInner)
public void addConfiguredInner(NestedElement anInner)
What is the difference?
Option 1 makes the task create the instance of NestedElement
, there are
no restrictions on the type. For the options 2 and 3, Ant has to create an instance
of NestedInner
before it can pass it to the task, this
means, NestedInner
must have a public
no-arg constructor or
a public
one-arg constructor taking a Project
class as a
parameter. This is the only difference between options 1 and 2.
The difference between 2 and 3 is what Ant has done to the object before it passes it to the
method. addInner()
will receive an object directly after the constructor
has been called, while addConfiguredInner()
gets the object after
the attributes and nested children for this new object have been handled.
What happens if you use more than one of the options? Only one of the methods will be called, but we don't know which, this depends on the implementation of your JVM.
If your task needs to nest an arbitrary type that has been defined
using <typedef>
you have two options.
public void add(Type type)
public void addConfigured(Type type)
The difference between 1 and 2 is the same as between 2 and 3 in the previous section.
For example suppose one wanted to handle objects object of
type org.apache.tools.ant.taskdefs.condition.Condition
, one may have a
class:
public class MyTask extends Task { private List conditions = new ArrayList(); public void add(Condition c) { conditions.add(c); } public void execute() { // iterator over the conditions } }
One may define and use this class like this:
<taskdef name="mytask" classname="MyTask" classpath="classes"/> <typedef name="condition.equals" classname="org.apache.tools.ant.taskdefs.conditions.Equals"/> <mytask> <condition.equals arg1="${debug}" arg2="true"/> </mytask>
A more complicated example follows:
public class Sample { public static class MyFileSelector implements FileSelector { public void setAttrA(int a) {} public void setAttrB(int b) {} public void add(Path path) {} public boolean isSelected(File basedir, String filename, File file) { return true; } } interface MyInterface { void setVerbose(boolean val); } public static class BuildPath extends Path { public BuildPath(Project project) { super(project); } public void add(MyInterface inter) {} public void setUrl(String url) {} } public static class XInterface implements MyInterface { public void setVerbose(boolean x) {} public void setCount(int c) {} } }
This class defines a number of static classes that
implement/extend Path
, MyFileSelector
and MyInterface
. These may be defined and used as follows:
<typedef name="myfileselector" classname="Sample$MyFileSelector" classpath="classes" loaderref="classes"/> <typedef name="buildpath" classname="Sample$BuildPath" classpath="classes" loaderref="classes"/> <typedef name="xinterface" classname="Sample$XInterface" classpath="classes" loaderref="classes"/> <copy todir="copy-classes"> <fileset dir="classes"> <myfileselector attra="10" attrB="-10"> <buildpath path="." url="abc"> <xinterface count="4"/> </buildpath> </myfileselector> </fileset> </copy>
The TaskContainer
consists of a single
method, addTask
that basically is the same as
an add method for nested elements. The task instances will be
configured (their attributes and nested elements have been handled) when your
task's execute
method gets invoked, but not before that.
When we said execute
would be called, we lied
;-). In fact, Ant will call the perform
method
in org.apache.tools.ant.Task
, which in turn
calls execute
. This method makes sure that Build
Events will be triggered. If you execute the task instances nested into your task, you should
also invoke perform
on these instances instead
of execute
.
Let's write our own task, which prints a message on the System.out
stream. The task
has one attribute, called message
.
package com.mydomain; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; public class MyVeryOwnTask extends Task { private String msg; // The method executing the task public void execute() throws BuildException { System.out.println(msg); } // The setter for the "message" attribute public void setMessage(String msg) { this.msg = msg; } }
It's really this simple ;-)
Adding your task to the system is rather simple too:
<taskdef>
element to your project. This actually adds your task to
the system.<?xml version="1.0"?> <project name="OwnTaskExample" default="main" basedir="."> <taskdef name="mytask" classname="com.mydomain.MyVeryOwnTask"/> <target name="main"> <mytask message="Hello World! MyVeryOwnTask works!"/> </target> </project>
To use a task directly from the buildfile which created it, place
the <taskdef>
declaration inside a target after the compilation. Use
the classpath attribute of <taskdef>
to point to where the code has
just been compiled.
<?xml version="1.0"?> <project name="OwnTaskExample2" default="main" basedir="."> <target name="build" > <mkdir dir="build"/> <javac srcdir="source" destdir="build"/> </target> <target name="declare" depends="build"> <taskdef name="mytask" classname="com.mydomain.MyVeryOwnTask" classpath="build"/> </target> <target name="main" depends="declare"> <mytask message="Hello World! MyVeryOwnTask works!"/> </target> </project>
Another way to add a task (more permanently) is to add the task name and implementing class name
to the default.properties file in
the org.apache.tools.ant.taskdefs
package. Then you can use it as if it
were a built-in task.
Ant is capable of generating build events as it performs the tasks necessary to build a project. Listeners can be attached to Ant to receive these events. This capability could be used, for example, to connect Ant to a GUI or to integrate Ant with an IDE.
To use build events you need to create an ant Project
object. You can
then call the addBuildListener
method to add your listener to the
project. Your listener must implement
the org.apache.tools.antBuildListener
interface. The listener will receive
BuildEvents for the following events
If the build file invokes another build file
via <ant>
or <subant>
or
uses <antcall>
, you are creating a new Ant
"project" that will send target and task level events of its own but never sends build
started/finished events. Since Ant 1.6.2, BuildListener
interface
has an extension named SubBuildListener
that will receive two new events
for
If you are interested in those events, all you need to do is to implement the new interface
instead of BuildListener
(and register the listener, of course).
If you wish to attach a listener from the command line you may use the -listener option. For example:
ant -listener org.apache.tools.ant.XmlLogger
will run Ant with a listener that generates an XML representation of the build progress. This listener is included with Ant, as is the default listener, which generates the logging to standard output.
Note: A listener must not access System.out
and System.err
directly since output on these streams is redirected by
Ant's core to the build event system. Accessing these streams can cause an infinite loop in
Ant. Depending on the version of Ant, this will either cause the build to terminate or the JVM to
run out of Stack space. A logger, also, may not access System.out
and System.err
directly. It must use the streams with which it has been
configured.
Note: All methods of a BuildListener
except for the
"Build Started" and "Build Finished" events may occur on several threads simultaneously—for
example while Ant is executing a <parallel>
task.
Writing an adapter to your favourite log library is very easy. Just implement
the BuildListener
interface, instantiate your logger and delegate the
message to that instance.
When starting your build provide your adapter class and the log library to the build classpath and activate your logger via -listener option as described above.
public class MyLogAdapter implements BuildListener { private MyLogger getLogger() { final MyLogger log = MyLoggerFactory.getLogger(Project.class.getName()); return log; } @Override public void buildStarted(final BuildEvent event) { final MyLogger log = getLogger(); log.info("Build started."); } @Override public void buildFinished(final BuildEvent event) { final MyLogger logger = getLogger(); MyLogLevelEnum loglevel = ... // map event.getPriority() to enum via Project.MSG_* constants boolean allOK = event.getException() == null; String logmessage = ... // create log message using data of the event and the message invoked logger.log(loglevel, logmessage); } // implement all methods in that way }
The other way to extend Ant through Java is to make changes to existing tasks, which is positively encouraged. Both changes to the existing source and new tasks can be incorporated back into the Ant codebase, which benefits all users and spreads the maintenance load around.
Please consult the Getting Involved pages on the Apache web site for details on how to fetch the latest source and how to submit changes for reincorporation into the source tree.
Ant also has some task guidelines which provides some advice to people developing and testing tasks. Even if you intend to keep your tasks to yourself, you should still read this as it should be informative.