Writing Ant Tasks
by Michael Fitzgerald06/02/2004
Apache Ant is an increasingly popular open source, cross-platform build tool written in Java. Ant's build files are written in XML and generally consist of a project and a set of interdependent targets. These targets contain one or more tasks that can perform all kinds of functions, such as compiling Java source code, creating .zip, .gzip, or .bzip2 archives, cleaning up old files, and so on.
A nice feature of Ant is that it is designed to allow you to add your own tasks and use them in an build. This article shows you the basics of writing an Ant task and how to get a task to work.
This article assumes that you are familiar with Ant and with the Java programming language. It walks you through the creation of a simple Ant task in Java, and then looks at a more complex task written by James Clark for Jing, an open source RELAX NG and Schematron validator written in Java. Both RELAX NG and Schematron are schema languages for XML. (I won't demonstrate the features of Schematron in this article). The example code and other files mentioned in this article are available for download from the Resources section below and at www.wyeast.net/task.zip. The examples have been tested with Ant version 1.6.1 (the latest version of Ant at the time of this writing), Java version 1.4.2_03, and Microsoft Windows XP Professional version 5.1.2600. (I'll be using the command-line version of Ant, though some GUIs for the tool are available.)
Writing a Simple Task
Before getting started, download and
install Ant version 1.6.1 or later on your system, if it is not already
there. When you extract the files from the Ant archive (in .zip, .gzip, or
.bzip2 format, depending on what you download), you'll notice a
bin directory in the distribution. Place the bin
directory in your path, and then type ant -version at a
command or shell prompt. If you get back a response like Apache Ant
version 1.6.1 compiled on February 12 2004, you're in business.
In the example archive, you'll find a file named Add.java. This file contains an Ant task that simply reports the sum of two integers. The code is not complex, but it provides a skeleton for your own tasks. Here's a listing of the file:
package net.wyeast.ant;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
public class Add extends Task {
private int op1;
private int op2;
private int sum;
// The method executing the task
public void execute()
throws BuildException {
sum = op1 + op2;
System.out.println("The sum of the " +
"operands is " + sum + ".");
}
// The setter for the "op1" attribute
public void setOp1(int op1) {
this.op1 = op1;
}
// The setter for the "op2" attribute
public void setOp2(int op2) {
this.op2 = op2;
}
}
|
Related Reading
Ant: The Definitive Guide |
You don't have to use a package name as I did (net.wyeast.ant),
though it's axiomatic to disambiguate class names that could
possibly collide with other class names in an open environment.
Only two Ant classes are imported: org.apache.tools.ant.BuildException
and org.apache.tools.ant.Task, an abstract class.
The Add class extends the Ant Task class. Other
options are possible, but Task is the base class for all Ant tasks. By the way, because only the class Task
is declared as abstract, you do not need to implement all of its methods.
The other imported class is BuildException. You'll notice
that the execute method in Add, which overrides
the method of the same name from Task, throws a
BuildException. A BuildException is thrown
if something goes wrong with the build or if the task cannot be
properly configured.
The three private variables of type int,
op1, op2, and sum, are used to
perform the arithmetic you see in the execute method. The
execute method is mandatory: it's the method Ant uses to
execute a task. The Add task adds two
operands and then prints the sum with System.out.println.
But where do these operands come from during a build? They are picked up
from the values of the attributes op1 and op2 in
the file build.xml file, which you will see in a moment. The two setter
methods setOp1 and setOp2 correspond with the
attributes op1 and op2. Setter methods such as
these process attribute values, which is the main way an Ant task gets input
and passes it on to an underlying program.
Now it's time to compile and package the task code into a .jar. To compile
Add.java, you'll need to add ant.jar
to your classpath so the compile can pick up the Task and
BuildException classes. You'll find this .jar in
the lib subdirectory in the Ant distribution. At a prompt
in the working directory where you extracted the files from the
example archive, type something similar to the
following line, taking care to use the correct path to
ant.jar, wherever it might be on your system:
javac -classpath ant.jar net/wyeast/ant/Add.java
Once you are successful at compiling Add.java, package
Add.class into a .jar file:
jar cvf add.jar net/wyeast/ant/Add.class
The class or classes you use for the task apparently must be in a .jar
file to work -- I've tried just using bare classes in a variety of ways
with no success. For convenience, place a copy of add.jar
in the lib subdirectory mentioned earlier. This is where all
of the Ant .jar files happily live. With add.jar there, Ant will be
able to find the class in it at build time. Granted, as long
as you make sure add.jar is in your classpath (using Ant's
-lib option), Ant will be able to find it, but I
find it's most convenient to drop .jars into lib.
Exercising the Task
With the code compiled and the .jar in place, your are ready to try out the task. In the example archive you will also find the following Ant build file, build.xml:
<?xml version="1.0"?>
<project default="main">
<taskdef name="adder" classname="net.wyeast.ant.Add"/>
<target name="main">
<adder op1="23" op2="77"/>
<target>
<project>
This is a small example of a build file, but it suits the needs of the
moment. It contains only one target, called main. The
default attribute on project is required and
names the default target to be used; that is, main, the only
target in the file. The main target contains the task
adder, which is dependent on taskdef.
The taskdef element preceding the target defines a name
for the task and the class name where the code for the task lives.
The value of the name
attribute names the task. That name need not match the name
of the class, although it must match the name of the task element
that triggers it. Also, taskdef does not need to precede
the target or targets that use the task.
The value of the classname attribute gives the name of
the class that executes the task. The class net.wyeast.ant.Add is
found in add.jar, which you previously copied to the Ant lib
subdirectory. If add.jar is not there (or
elsewhere in the classpath), this task will simply not work.
The adder child element of target executes
the task using its two attributes mentioned earlier, op1 and
op2. While in the working directory, test out the task by
typing ant at the command or
shell prompt. Ant automatically invokes the instructions in the file
build.xml because build.xml is the default
Ant build file name (you can use build files with different names if
you use the equivalent -f, -file, or
-buildfile option with Ant). After you type ant,
you should get the following response:
Buildfile: build.xml
main:
[adder] The sum of the operands is 100.
BUILD SUCCESSFUL
Total time: 1 second
There you are. You now know the basics of how to write an Ant task in Java, compile and package it as a .jar, and put the .jar in a place where Ant can find it. You also learned how to define a task and invoke it within an Ant build file. Now that you understand the process, it's a good time to do some experimenting. Some things you could try include:
- Changing the values of
op1andop2in build.xml. - Renaming the task in both
taskdefand its element name. - Adding another attribute setter method to Add.java, such as
setOp3, and then placing the corresponding attributeop3in the invocation of the task in build.xml (also, add a variable to handle the attribute value and add it to the sum). - Invoke the task more than once within a target.
- Add new targets that invoke the task.
After you play around with the build file and code, try writing an entirely new task yourself with a different and more interesting result than adding integers!
Pages: 1, 2 |