http://www.developer.com/

Back to article

Introduction to Custom Ant Tasks


September 7, 2006

Apache Ant is a great tool to manage your build tasks. It streamlines weekend hobby development, and it brings automated sanity to very complex, commercial endeavors. Ant provides well over a hundred different task handlers, and although this is usually sufficient, you may find yourself needing to step beyond Ant's current abilities: You may need to run some complex post-build verification checks that you want to hide behind a simple lone element, you may need to use arcane options beyond those the standard tasks provide, or you just may want to rot13 your source files... just because. Fortunately, it is fairly simple to create your own task handler in Java and connect it to Ant to give you that extra little feature only you need. In this article, I will walk you through the basic steps needed to create a custom task for Ant.

This article assumes a basic familiarity with Ant. I am working with Ant version 1.6.5 and Java 5 on Mac OS X; however, the examples in this article should work with any reasonably modern version of Ant and Java on any platform. I include commands to demonstrate how this works on a command line. Those of you using an IDE should include the "ant.jar" file located in the "lib" folder of your Ant installation. Those of you following along on a command line should take care to include ant.jar into your CLASSPATH system environment variable. (As an aside, an IDE makes a great tool to poke a little deeper into the details of the standard tasks, and source code for the tasks is available both online and via the "source distribute" of Ant.)

Overview

The basic procedure for creating a custom task includes:

  1. Creating a Java class that subclasses org.apache.tools.ant.Task and overrides the execute() method.
  2. Packaging your class file(s) into a jar file.
  3. In a standard Ant build.xml file, registering your custom task by using the standard taskdef task.

I will discuss the above points in further detail below.

It should be noted that your task can interact with other, nested Ant tasks such as the standard FileSet task. Your task also may look up and set project-level properties and emit log messages at any standard log level (warning, debug, and so forth). In short, your custom task exists as much as a first class citizen as any core Ant task.

Example One: Barebones Hello World

No how-to guide would be complete without including a "Hello World" example. (Before you scoff at the apparent uselessness, starting at "Hello World" gives you a known reference point from which you can build out into a new direction.) Consider that you are in the highly hypothetical situation where you need a custom Ant task to emit the phrase "Hello, World!" into the standard output of the execution of an Ant project. Yes, the standard echo task would this quite well, but see how you would accomplish this on your own. Consider this example:

<project name="demo" basedir="." default="demo">

   <target name="demo">
      <helloworld/>
   </target>

</project>

This example is highly simplified: The task has no attributes or nested elements, and it does not need to interact with the project. You will use this to get the mechanics out of the way so that you can focus in more detail later on the things that will allow your tasks to do increasingly useful things.

The choice of element name (or tag name, if you prefer) to be used in the build.xml file is somewhat arbitrary. Here, I have decided to use "helloworld" as the element name. This element name does not have to match the class name of your Java file in any way, but it is obviously advantageous to relate them. Using the javac task as an example, the core tasks in Ant seem to generally follow this pattern:

  1. The element name is in fully lower case, such as javac.
  2. The Java class file is in standard, mixed case, such as Javac.
  3. The Java class file is in a package, such as org.apache.tools.ant.taskdefs.

I will adopt this pattern for the discussion, using element name "helloworld" and a Java package and classname of org.roblybarger.ant.taskdefs.HelloWorld. The declaration and use of a package is highly encouraged. If you are a weekend coder and do not know what to use as a package name, use something like "org.yourname." or perhaps "org." followed by your initials.

Step One: Create the Java Class

The class for your custom Ant task should follow these rules:

  1. Subclass org.apache.tools.ant.Task.
  2. Override public void execute() and, if needed, public void init() method.
  3. Use the inherited log() method for any output message.

If you define a constructor, it should be a public, no-arg constructor. In fact, Ant will call your init() method (prior to calling your other methods to deal with nested elements and attributes and such) to do initialization work, so you can dispense with explicitly defined constructors entirely.

This is how it might look:

package org.roblybarger.ant.taskdefs;

import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;

public class HelloWorld extends Task {

   public void execute() {
      log("Hello, World!", Project.MSG_INFO);
   }

}
Important: We are sending our message by way of the inherited log() method. Ant connects the standard input and output streams to the appropriate places (such as the user running Ant with the "-logfile" option), so use the log method instead of using System.out.println and/or System.err.println methods.

The second argument in the log method is the priority level that the message is emitted at. This is an optional argument. If you use the log("some message") version, you get an "INFO" level message. Possible values are available in a set of constants in the Project class. These include:

  • Project.MSG_ERR
  • Project.MSG_WARN
  • Project.MSG_INFO
  • Project.MSG_VERBOSE
  • Project.MSG_DEBUG
Note: You need to pass the "-verbose" and/or "-debug" option when you run Ant to get the last two message levels to display. This allows you to include diagnostic messages in your task that you might not want to show up all the time.

I have saved my source file underneath a "src" folder, and I am compiling this file into the "classes" folder. My CLASSPATH environment variable contains ant.jar as well as the classes and src directories. (As I'm on a Mac, this looks like "/Developer/Java/Ant/lib/ant.jar:classes:src")

javac -d classes src/org/roblybarger/ant/taskdefs/HelloWorld.java

Step Two: Package into a Jar File

Strictly speaking, this step is optional, but creating a jar file for your Ant task will enhance portability and ease-of-use. Also, you have a few choices to register your task with Ant in the next step, but the easiest choice, in my opinion, involves the use of a properties file.

Create a text file with this information:

helloworld=org.roblybarger.ant.taskdefs.HelloWorld

Save this as a properties file. I will use "MyAntTasks.properties" for this example. Now, create a standard jar file which includes your class file and this properties file. On a command line, you might do this:

jar cf mytasks.jar MyAntTasks.properties -C classes .

Step Three: Register Custom Task with Ant

With this jar file available, you are nearly ready to use your custom task in your own build.xml file. The final step is to register your task name and class file so Ant knows where to pass control to at the proper time. Because you have a jar file with a properties file, this is rather simple. You started this article with a build.xml file, so you just add a taskdef entry to it:

<project name="demo" basedir="." default="demo">

   <taskdef resource="MyAntTasks.properties" classpath="mytasks.jar"/>

   <target name="demo">
      <helloworld/>
   </target>

</project>

That is it. Run your build.xml file through Ant, and you should see output similar to this:

Buildfile: build.xml

demo:
<helloworld> Hello, World!

BUILD SUCCESSFUL
Total time: 0 seconds

You probably can see the virtues of using the property file method of task registration: New tasks and classes are wired in one place and are packaged up together with the properties file. When you add more tasks to your custom toolset, you just add lines to the properties file instead of adding taskdef statements into your build.xml files, which are likely cluttered enough already.

Example Two: Optional Attributes Support

I will now expand on the previous example by adding an attribute to your task. Consider that you desire to have an optional attribute for your task. The value of this attribute will be a person's name, such as Bob, and you will say "Hello, Bob!" instead of "Hello, World!" if this attribute is set. (If the name attribute is omitted, you'll fall back to the original message.)

You provide attribute support by adding the necessary instance variable in your class and providing a corresponding getter/setter method for it. The name of the attribute should match the name of your instance variable so that Ant can most easily determine which getter/setter method is involved as it is processing your custom task. (Chalk it up to some reflection magic.) In other words, if you decide you want an optional "name" attribute in your custom task, you need to create a "name" instance variable and a standard getName/setName method pair. For a build.xml file that looks like this...

<project name="demo" basedir="." default="demo">

   <taskdef resource="MyAntTasks.properties" classpath="mytasks.jar"/>

   <target name="demo">
      <helloworld name="Bob"/>
   </target>

</project>

...you provide the following Java code:

package org.roblybarger.ant.taskdefs;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;

public class HelloWorld extends Task {

   private String name=null;

   public void setName(String name) {
      this.name = name;
   }

   public String getName() {
      return name;
   }

   public void execute() {
      if (name != null && name.length() > 0) {
         log("Hello, " + name + "!", Project.MSG_INFO);
      }
      else {
         log("Hello, World!", Project.MSG_INFO);
      }
   }

}

Recompile and update your jar file. Run Ant both with and without the additional attribute present. For the sample build.xml file I showed at the beginning of this section, you should get:

Buildfile: build.xml

demo:
<helloworld> Hello, Bob!

BUILD SUCCESSFUL
Total time: 0 seconds

Property References in Attributes

You may refer to properties defined elsewhere in the build.xml file when you set the value of an attribute for your custom task—Ant automatically will expand the property before it calls the setter method in your code. In other words, name="${first} ${last}" would work fine without your needing to detect, parse, and replace the values on your own.

Case-Insensitivity

The exact case for your attribute name does not need to exactly match that of the instance variable in your Java class. In general, attribute names can also be entirely in lower case. In other words, if your instance variable is called "intValue", it is fine for the attribute to be named "intvalue" in the build.xml file.

Type Conversions to Non-String Variables

Ant does provide some automatic type conversions as necessary, so the instance variables in your Java code can be primitive types such as int or boolean or object types such as File.

To see any of the above comments illustrated, view the downloadable files for this example.

Example Three: Required Attributes and Conditionally Failing the Build

Now, consider the case where you insist the name attribute to be given, meaning it is no longer an optional attribute. Where, in Example Two, you were checking for a null (or zero length) value and emitting the default message, you now will cause the Ant output to say "BUILD FAILED" and halt operation. This is done by throwing an org.apache.tools.ant.BuildException object.

Note: I am showing only an updated execute method here.
public void execute() throws BuildException {
   if (name != null && name.length() > 0) {
      log("Hello, " + name + "!", Project.MSG_INFO);
   }
   else {
      throw new BuildException("name attribute is required.");
   }
}

Now, update the build.xml file to use this both with and without the required attribute to see the effect:

<project name="demo" basedir="." default="demo">

   <taskdef resource="MyAntTasks.properties" classpath="mytasks.jar"/>

   <target name="demo">
      <helloworld name="Bob"/>
       <helloworld/>
   </target>

</project>

Running this without the name attribute in the build.xml file should result in this:

Buildfile: build.xml

demo:
[helloworld] Hello, Bob!

BUILD FAILED
/Users/rob/Projects/Articles/Article1/ex3_files/build.xml:7:
   name attribute is required.

Total time: 0 seconds

Reasons for you to do this might involve some critical file not being present (or readable), some required property not being set as expected, or whatever else might cause your business logic to go horribly awry. To address some problems ahead of time, you might be able to have some default values; however, I encourage you to emit a "WARNING" level log message that informs the user (which may be you, later, when you forgot how your task works) that a default value is being used.

Example Four: Getting and Setting Project Properties

Suppose you want to set a project-level property in your task that contains the output message instead of directly sending the message to the log() method. Because you have subclassed the Task class, you can call the getProject() to return a reference to the Project you are running under, and then you may call getProperty and setProperty on that Project reference. Consider this modified build.xml file:

<project name="demo" basedir="." default="demo">

   <taskdef resource="MyAntTasks.properties" classpath="mytasks.jar"/>

   <target name="demo">
      <helloworld name="Bob" property="greeting"/>
      <echo message="${greeting}"/>
   </target>

</project>

I have added a property attribute to your custom task. To prove the property is indeed being set, I have also added a standard echo task to display the value of that property. (The standard Ant tasks tend to call the attribute "property" as I have done here.)

To support this, you must add a property instance variable and its corresponding getter/setter method to your class file (not shown, but see a preceding example for details or look at the downloadable files for this example). Then, you modify your execute method slightly:

/* Add 'private String property=null;' and implement
   its getter/setter methods normally*/

public void execute() throws BuildException {

   if (property==null || property.length() == 0) {
      log("Cannot set property as attribute was not given.",
          Project.MSG_ERR);
      return;
   }

   if (name != null && name.length() > 0) {
      Project project = getProject();
      String propertyValue = "Hello, "+name+"!";
      project.setProperty(property, propertyValue);
      log("Set property: " + property + " -> " + propertyValue,
          Project.MSG_VERBOSE);
   }
   else {
        throw new BuildException("name attribute is required.");
   }

}

Note that my approach implies the property attribute is optional: If it is not specified, an "ERROR" priority level message is generated and no further action takes place. (Note the 'return' statement.) This might not be a good idea in reality—you might choose, instead, to require the property attribute in the same manner that the name attribute is required (that is, by throwing a BuildException if it is omitted). The real change to pay attention to is the way you get a reference to your Project and then set a new property in it.

Important: Recall that, in Ant, property values are immutable!

Also note that I am sending a message at the "VERBOSE" priority level that tells you what property name is being set to what property value. This is mainly for behavioral consistency to the standard property task in Ant. To see it, remember to run Ant with the "-verbose" option. If you do, you should see this output:

...(verbose output info skipped)...

demo:
[helloworld] Set property: greeting -> Hello, Bob!
     [echo] Hello, Bob!

BUILD SUCCESSFUL
Total time: 0 seconds

Summary

This article has shown you how to create and use your own custom Ant tasks. A great deal of credit should be given to Ant's developers for making this as relatively easy as it is as well as the community of developers who have contributed to the standard task set. Although the manual that is included with Ant does touch on custom task creation, its examples and explanations leave a bit to be desired, in my opinion. With that being said, I hope this article has made things a bit easier to follow. Even though the running "Hello World" example is fairly simplistic, you should be able to see the power available: any code you can write in Java to accomplish some task can be turned into a custom Ant task with little effort. Extend the Task class, provide an execute() method, and package the code with a properties file for maximum ease of use.

What's Left?

A couple things haven't been shown in this article, and they both related to nesting information inside your task's element. First, I'll mention briefly the "public void addText(String text)" method, which your custom task inherits from the Task class. This lets you capture text you might put between the opening and closing tags of a custom task. (That is, "text nodes" in XML-speak.) I opted not to demonstrate this for a couple reasons: First, you should be able to work it out for yourself now, and second, property values are NOT automatically expanded in such text (although the Project class has some facilities to help you do this yourself).

The other more important thing that was not shown is how to handle nested elements. I will address this in a future article. Although the material is not necessarily more difficult than that in this article, I feel it does merit more focused attention. People who have dipped their toe into custom Ant tasks may also wonder about a "type" as opposed to a "task." Because a "type" really needs to be nested underneath another element to be of any real use, I'll again defer discussion to a future article. (The notion of "types" exists to provide helper elements that do not necessarily need to "execute()" anything, but which may nonetheless serve as helper to their parent element.)

Finally, I would like to mention that there is a set of Ant tasks available for your use by way of the "ant-contrib" SourceForge project. This adds some nice capabilities such as "if/then/else", "foreach", and even "try/catch" to your build.xml files. See the Resources section for a link.

Download the Code

You may download the code that accompanies the article here.

Resources

About the Author

Rob Lybarger is a software engineer who has been using Java for nearly a decade and using Ant since its earliest days. He has used various versions of Windows, various distributions of Linux, and, most recently, Mac OS X. Although knowing a wide array of software languages, the cross-platform nature of Java and Ant has proven to be a valuable combination for his personal and professional efforts.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date