Break free of the built-in tool barrier: Write custom tasks for Ant

Ant is a Java-based build tool that offers the benefits of being cross-platform, but that means it can't rely on underlying shell functionality. Luckily, if Ant's built-in tasks can't do the job, you can write your own. This article will teach you how.

In our previous article, we introduced Ant, the Java-based build tool. Ant was specifically designed to work with Java projects, and it benefits from being written entirely in Java. It works anywhere Java is installed, making it completely cross-platform.

However, being cross-platform means that Ant can't call external tools. While build tools like Make can use all the underlying shell tools to help in the build process, Ant has to rely on its built-in tasks alone. Most software builds can get by on the Ant's built-in tasks, but if you hit the limits of those tasks, you'll have to write your own. Fortunately, writing Ant tasks is simple. This article will get you started on writing your own tasks, and by the end, you’ll be ready to fully integrate Ant into your build system.

Getting started
Writing an Ant task requires writing a new class that extends the Ant’s Task class. The class itself is simple. It contains a number of setter methods to set the class values and a method named execute () that returns null and throws BuildException. Each setter value corresponds to an attribute of the task tag in the build configuration. For example, the tag <tstamp/> would use no setters, but the tag <property name="dist" value="dist"/> would use setter methods setName () and setValue ().

Methods can take a variety of parameter types. Most commonly, you’ll accept the data type String, but you can pass any type that takes a string as a constructor, such as Integer. For Boolean data types, Ant will be intelligent about whether it calls the setter. If the value of the attribute in the build configuration is “yes,” “true,” or “on,” Ant will send the Boolean value true; otherwise, it will default to false.

Let’s start by looking at a simple task shown in Listing A. Our example will take the optional attributes message, message_count, and show_message. If show_message is true, the task will print the text message to the console message_count times. As you can see, each method takes a different data type as a parameter. The task will output using the log () method.

To compile this task, we need to compile the class against the Ant.jar file that shipped with Ant. To do this, we’ll use Ant to manage the build. If you’ve properly installed Ant, it will recognize all the Java Archive (JAR) files it needs in your $ANT_HOME/lib directory. Otherwise, you’ll need to specify the JAR files you need in the class path.

The resulting class file needs to be packaged as a JAR file and placed in $ANT_HOME/lib/. Our build file will package the tasks and install them as MyTasks.jar. Listing B shows the build configuration that will do this for us.

To compile and install the (which should be in the src directory), we just need to run the command ant install.

Ensure that the JAR file has been properly installed by looking for MyTasks.jar in your $ANT_HOME/lib directory. Assuming that the JAR file has been installed properly, we can now try out our new class. First, we need to tell Ant to import it. In the build configuration, add the following line:
<taskdef name="Simple" classname="SimpleTask"/>

This tells Ant that it should define a task called Simple from the class SimpleTask. (It will resolve the location of SimpleTask by looking in its class path.) You can name the definition to whatever suits your needs.

Once you’ve defined the task, you need to use it. You can do this by calling Simple anywhere in the build configuration. To test the process, let’s create a new target called SimpleTarget, as shown in Listing C.

If you remember, the SimpleTask iterates a message multiple times if the show_message Boolean was set. So when you run ant SimpleTarget, you get output like Listing D.

Now, let’s look at how Ant deals with exceptions.

Dealing with exceptions
When your task hits a critical error—one where building must stop—you need to throw an exception. When Ant receives a BuildException from a task, it stops the compilation and dumps out the error message. Let’s look at the simple exception example shown in Listing E.

As you can see, the task simply throws an exception. But it isn't the code that's interesting here; it's the impact it has on the build. Figure A shows the changes to the build configuration.

Figure A
Changes to the build configuration

Now, take a look at the output from running ant ExceptionTarget.

Figure B
Output from ant ExceptionTarget

As you can see, Ant tells you that the build failed, where the exception was, and which target it was currently in. Let’s turn our attention to dealing with property values.

Dealing with properties
Ant keeps a set of environment variables that are used for all types of temporary data storage. These variables are important because they offer the only way two tasks can communicate. For example, if one task sets the date, any subsequent task can read the date out of the environment. In addition, environment variables offer one way for tasks to read data from the user—for example, the path to the build directory that was set with a property variable.

The task shown in Listing F demonstrates how to set environment variables and how to read from them.

As you can see, we’ve imported the Project class from Ant, which we’ll need to use for accessing the getProperty()and setProperty() methods. Let’s look at the output from the task. First, make the build configuration additions, as shown in Figure C.

Figure C
Build configuration additions

Figure D shows the output.

Figure D
Ant output

Now, we're ready to tackle a real example of an Ant task.

A real-world example
Let's say that in our build environment, we want to maintain a Web page containing the build history. To allow anyone to make changes to the page, we’ll want our build page to be a Web Log using the Blogger site management software. Blogger allows you to remotely modify a page over XML-RPC, an XML-encoded remote procedure interface.

Using the XML-RPC interface from the Apache Foundation, we can easily modify the page remotely from within our Java task. We’ll want to copy the Xmlrpc.jar file from the distribution to our $ANT_HOME/lib directory. Now, when we build with Ant, it should automatically add Xmlrpc.jar to the class path.

Our task will post a message, along with all the environment variables, to the Web page. This way, we’ll have an online copy of what changes are in the build system. If it can’t update the page for some reason, it will throw an exception with the XML-RPC error. Listing G shows the code for

All that’s left is to add the build information, as shown in Figure E.

Figure E
Add the build information.

Ant power
Now that you’ve seen how to write an Ant task, you’ll be able to properly integrate Ant into your build system. Ant offers a powerful environment to work in, and it enables you to add your own tasks to customize the environment. Not only that, if an existing task doesn’t behave how you expect it to, you can look at the source for Ant and create a task that works best for you.

Editor's Picks