ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Ant 1.7: Using Antlibs

by Kev Jackson
08/09/2006

A new release of Ant is just around the corner, so it's a good time to introduce one of the coolest new features that Java developers will soon be able to play with: antlibs. These are a better way for Java developers to create and distribute custom Ant tasks, types, and macros, and a much better way for the Ant developers to distribute the optional tasks included with the Ant distribution.

So, what was wrong with normal taskdefs that desperately needed to be fixed?

  • Classpath lookups for taskdef classes--people still have problems getting JUnit to work properly with Ant.
  • No standard way to distribute custom tasks.
  • Optional tasks are tightly coupled to Ant core.

Antlibs help to alleviate these problems:

  • By default, antlibs are placed in $ANT_HOME/lib and specified using XML namespaces.
  • The antlib format provides a good standard method of distribution (single .jar containing tasks, types, and macros, together with an XMLdescriptor).
  • Optional tasks can be de-coupled from the Ant core code and released on a different schedule.

Using an Antlib

If you already have an antlib, it can be used in a build file in a few different ways. Use <typedef file="example-antlib.xml"/> if the antlib is just a directory of classes and an example-antlib.xml descriptor. This is the most similar to a normal external Ant task.

Another approach is to use:

<typedef resource="com/mycompany/ant/example-antlib.xml" 
            uri="example:/mycompany.com"/>

This is appropriate if the antlib is a .jar file of classes and the antlib descriptor. This can then be used with an xmlns declaration as follows:

<example:task xmlns:example="example:/mycompany.com">
...
</example>

This assumes that there's a task called task defined in the example-antlib.xml file.

Finally, there's the most convenient way of using antlibs. If the following conditions are met, Ant will automatically load all definitions of tasks and types declared in the antlib:

  • The antlib .jar is placed in $ANT_HOME/lib
  • The antlib .jar contains a file called antlib.xml
  • The build.xml is defined as:
<project xmlns:example="antlib:com.mycompany.ant">
  <target name="test">
    <example:task/>
  </target>
</project>

Antlib Overview

Before we start creating our own antlib, let's ask ourselves: what exactly is an antlib, and what are the ingredients of one? To begin with, an antlib is simply a collection of classes bundled with an XML descriptor file. Typically, an antlib is distributed as a .jar file, but this isn't a strict requirement. The root element of the XML file is <antlib>. Any classes can go into the makeup of an antlib, but only some classes can be declared in the antlib.xml file. The following are allowed to be declared in the antlib.xml:

  • <typedef>
  • <taskdef>
  • <macrodef>
  • <presetdef>
  • <scriptdef>
  • Any class that subclasses org.apache.tools.ant.taskdefs.AntLibDefinition

So now that we have an idea of what goes into an antlib, let's get our hands dirty and make one.

Creating a New Antlib

Let's start with a simple goal: wrap a command-line executable to give us a nicer interface than <exec>. For this I've decided to pick one of the many open source version control systems, Arch. First, let's get Arch installed (if it isn't already): find the correct package for your system and follow the install instructions. Finally, type tla help and you should see something similar to Figure 1.

arch installed
Figure 1. GNU Arch installed

For Ant tasks that wrap around a command-line tool, a good starting point is to build a base class that handles the main setup of the environment, and then create a subclass for each command you want to support. For example, the Ant SVN library has an AbstractSvnTask, and then a Svn task that handles any commands, along with SvnRevisionDiff and SvnTagDiff, which are specialized for performing the svn diff command.

Because GNU Arch is a similar tool to Subversion (ignoring the whole distributed versus client/server argument here), the same strategy of writing a base class and then creating specialized subclasses should work just as well. The only problem is that the Ant Svn doesn't (yet) ship with ant.

However, we don't need to re-invent the wheel, since Ant already contains a task for CVS, which behaves in an identical manner to the Svn task (indeed, the Svn task was modeled closely on the Cvs task). It also wraps a command line tool and it deals with version control, an almost perfect fit!

With the strategy of "baseclass + specialized subclasses" chosen, and an example to model our code on, we can start writing our antlib.

Because we are creating an antlib, we should put the code in a separate Java package/namespace; indeed an entirely new project seems appropriate here. I'm using Eclipse, but the choice of IDE (or to even use an IDE) has no bearing on the way an antlib is developed. For our package, I've decided on org.apache.ant.tla, and following the example of the AbstractCvsTask, I'm naming my base class AbstractTlaTask.

The most important part of the code is shown below, the runCommand() method; all of the other code in AbstractTlaTask sets the environment up before calling this method to actually perform the work. It isn't essential for all antlibs to have a runCommand() method, but in our case, as we have to interface with a command-line tool, this is one way to achieve that level of interaction, which works well with the AbstractCvsTask. As you can see, when executing system commands we have to deal with a lot of potential Exceptions. Check out the sample code to see the full source of this Task.

protected void runCommand(Commandline toExecute) 
        throws BuildException {
    Environment env = new Environment();
    Execute exe = new Execute(
        getExecuteStreamHandler(), null
    );
    exe.setAntRun(getProject());
    exe.setCommandline(
        toExecute.getCommandline()
    );
    exe.setEnvironment(env.getVariables());
    try {
        String actualCommandLine = 
            executeToString(exe);
        log(
            actualCommandLine, 
            Project.MSG_VERBOSE
        );
        int retCode = exe.execute();
        log(
            "retCode=" + retCode, 
            Project.MSG_DEBUG
        );
        if (failOnError && 
            Execute.isFailure(retCode)) {
            throw new BuildException(
            "tla exited with error code "
                + retCode
                + StringUtils.LINE_SEP
                + "Command line was ["
                + actualCommandLine + "]", 
                getLocation()
            );
        }
    } catch (IOException e) {
        if (failOnError) {
            throw new BuildException(
                e, getLocation()
            );
        } else {
            log(
                "Caught exception: " + 
                e.getMessage(), 
                Project.MSG_WARN
            );
        }
    } catch (BuildException e) {
        if (failOnError) {
            throw (e);
        } else {
            Throwable t = e.getException();
            if (t == null) {
                  t = e;
            }
            log(
                "Caught exception: " + 
                t.getMessage(), 
                Project.MSG_WARN
            );
        }
    } catch (Exception e) {
        if (failOnError) {
            throw new BuildException(
                e, getLocation()
            );
        } else {
            log(
                "Caught exception: " + 
                    e.getMessage(), 
                Project.MSG_WARN
            );
        }
    }
}

Pages: 1, 2

Next Pagearrow