ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Open Source Java: Ant

by David Thomson
02/22/2001

Ant, an open source Java project, has been gaining in usage and attention lately -- and rightfully so. Ant is driving ease of use in cross-platform software development by dragging the redoubtable "make"-style of build tool into this new century. But where did Ant come from?

Jakarta

Ant is based at the Apache Jakarta project's site (see resources listed at the end of this article), which says Ant is "kind of like make without make's wrinkles". It goes on to explain the reasoning behind the development of a replacement for make and make-like programs: all of these programs have serious flaws when it comes to building cross-platform software, which is of particular impact for certain Java projects.

As such, Ant is an ideal choice for building other Java-based projects, although it can also be used for building projects written in other languages.

Makefiles (and IDEs) Considered Harmful

Traditionally, makefiles are based around an arcane ordering of tab characters and spaces. This makes life particularly awkward for programmers who are new to using makefiles. Even those who have worked with them for a while can have trouble.

Integrated Development Environments (IDEs) appear to remove this reliance on buildfiles, but in fact they are not interchangeable between different IDEs, even if your favorite IDE happens to exist on another target platform.

Why Use Ant?

So why has Ant experienced such a rapid uptake in the development community? There are several reasons, but I feel the main ones are as follows.

Open

Ant is an open source project available under the Apache license. You can download and modify the source code as with all other open source projects. Ant's extensibility means, however, that most modifications you may require can be included via mechanisms which we'll cover later.

Additionally, Ant uses XML buildfiles. This means that anyone conversant with basic XML structures and formatting can comprehend and write an Ant file. And from a development point of view, almost everyone understands basic XML.

Cross Platform

Along with XML, the use of Java to develop Ant makes it the perfect solution for those people developing programs designed to run or be built across a range of different operating systems.

Extensible

Ant is extensible in two ways: new tasks and build listeners. New tasks are used to extend the capabilities of the build process, while build listeners are used to help hook into the build process to add extra error tracking functionality.

Integration

Finally, the fact that Ant is extensible and open means that integration with your favorite editor or development environment becomes very easy. Indeed, several people have contributed packages to hook Ant into the most popular environments.

Ant in Practice

If you're convinced of Ant's merits, you'll want to know how to use it. There's not enough space to cover all the details of creating Ant buildfiles. So we'll go over a couple of simple files to examine how Ant can be extended with Java.

There are a few basic elements used throughout these buildfiles that you should be aware of.

Projects

This is the central element of the buildfile. There should be only one project per buildfile. Each project may contain one or more targets, and you should always specify a default target to be executed.

Targets

A target is a set of tasks that are to be executed which may depend on other targets. For example, it is good practice to have an initialization target which executes a timestamp task prior to an actual compilation target.

Tasks

A task is a piece of code that can be executed. It can have multiple attributes or arguments. The value of an attribute might contain references to a property. These references will be resolved prior to task execution. Ant includes qtasks written for everything you might want to do during a build process. As we'll see later, it is easy to add new tasks in case you have a specific requirement.

Properties

Each project can have a set of properties, which are simply name-value pairs. They can be defined either in the project file or external to Ant. Ant includes these properties:

Basedir

the absolute path of the project's basedir (as set with the basedir attribute of <project>)

ant.file

the absolute path of the build file

ant.java.version

the Java VM version Ant detected

Running Ant

In order to run Ant, you need the Java Development Kit from Sun (1.1 or higher), an XML parser, and Ant itself. You can use Sun's free XML parser if you don't have one.

Installing Ant requires adding Ant and the XML parser class files to the classpath.

A Simple Example

This example assumes we have a typical Java project -- a directory structure of source files which we want to build into a separate directory containing the compiled class files.

<?xml version="1.0"?>

<project name="HelloProject" default="compile" basedir=".">

<!-- set global properties for this build -->
<property name="src" value="." />
<property name="build" value="classes" />

<target name="init">
<!-- Create the time stamp -->
<tstamp/>
</target>

<target name="compile" depends="init">
<!-- Compile the java code from ${src} into ${build} -->
<javac srcdir="${src}" destdir="${build}" />
</target>
</project>

Taking each section in turn, we see that the <project> element defines a project name, a default target, and a base directory. Secondly, we see that two properties are set and available for use in the project targets: where the source files are, relative to the base directory; and where the compiled classes should be written to.

Next, we define the task init, which serves only to create a project timestamp. This sets the DSTAMP, TSTAMP, and TODAY properties in the current project.

Finally, we see the actual compile target which depends on init. Before compile is executed, init will be executed in order to prepare the environment. Ant will attempt to execute the attribute targets, depending on the order they appear (from left to right).

A Less Simple Example

As you can see, this file follows the same structure as the simple example above. This time, however, the project will by default create a jar file named LessSimple with the date appended. A target which acts to clean up the build areas is included too.

<?xml version="1.0"?>

<project name="LessSimple" default="dist" basedir=".">

  <!-- set global properties for this build -->
  <property name="src" value="." />
  <property name="build" value="build" />
  <property name="dist" value="dist" />

  <target name="init">
    <!-- Create the time stamp -->
    <tstamp/>
    <!-- Create the build directory structure used by compile -->
    <mkdir dir="${build}" />
  </target>

  <target name="compile" depends="init">
    <!-- Compile the java code from ${src} into ${build} -->
    <javac srcdir="${src}" destdir="${build}" />
  </target>

  <target name="dist" depends="compile">
    <!-- Create the ${dist}/lib directory -->
    <mkdir dir="${dist}/lib" />

    <!-- Put everything in ${build} into the LessSimple-${DSTAMP}.jar file -->
    <jar jarfile="${dist}/lib/LessSimple-${DSTAMP}.jar" basedir="${build}" />
  </target>

  <target name="clean">
    <!-- Delete the ${build} and ${dist} directory trees -->
    <delete dir="${build}" />
    <delete dir="${dist}" />
  </target>
</project>

Extending Ant

As mentioned previously, Ant can be extended with your existing Java skills in two different ways: through the build events mechanism and through adding new task objects.

Build Event Listeners

As with other areas of the Java platform, Ant generates build events as it runs through the build process. BuildListener objects can be attached to Ant. The listener receives BuildEvent objects for the following events:

Here's an example listener class to get you started. It simply outputs a message when the build begins and ends.

public class MyBuildListener implements BuildListener
{
public void buildStarted( BuildEvent event )
{
System.out.println( event.getProject().getName() + ": Build started..." );
}

public void buildFinished( BuildEvent event )
{
System.out.println( event.getProject().getName() + ": Build finished..." );
}

public void targetStarted( BuildEvent event )
{
System.out.println( event.getTarget().getName() + ": Target started..." );
}

public void targetFinished( BuildEvent event )
{
System.out.println( event.getTarget().getName() + ": Target finished..." );
}

public void taskStarted( BuildEvent event )
{
System.out.println( event.getTask().getTaskName() + ": Task started..." );
}

public void taskFinished( BuildEvent event )
{
System.out.println( event.getTask().getTaskName() + ": Task finished..." );
}

public void messageLogged( BuildEvent event )
{
System.out.println( "A Message: " + event.getTask().getMessage() );
}
}

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

This will run Ant with a listener which generates an XML representation of the build progress.

New Tasks

Creating a new task for Ant is very straightforward. The actual need for such work is quite rare since for the majority of cases Ant has a built-in task to do what is required.

To begin, create a class that extends org.apache.tools.ant.Task and a setter method (i.e., setXXXX) for each attribute. Finally, add a public void execute method, with no arguments, that throws a BuildException. This method implements the actual task itself.

This simple example, taken from the Ant documentation, provides a task which logs the message attribute to System.out.

package com.domain;

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

public class NewTask extends Task
{
// 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;
}

private String msg;
}

To add the task to Ant, simply declare a taskdef element to your project:

<taskdef name="newtask" classname="com.domain.NewTask"/>

And to use the new task, simply use it within a target as with any other task:

<newtask message="Hello World! NewTask works!"/>

Before running Ant, ensure that your new task class is in the classpath. An alternative method for adding your task is to add the task name and implementing class name to the default.properties file in the org.apache.tools.ant.taskdefs package. Now your task can be used as though it was built into Ant.

Summary

Additional Resources

The Jakarta Project homepage: this is where to find the Ant homepage

The Ant User Mail List

Ant Development Mail List

JDOM XML Library

As with any new tool, it's up to the various individuals and teams out there to decide if Ant fits with the way they currently work or the way they want to work. Many other Java open source projects now use Ant as the build tool, particularly the various Jakarta projects but also external projects like JDOM.

At The Games Kitchen, we have used Ant in several trial projects and are now migrating our entire build process to Ant owing to its success. The fact that the supplied tasks provide integration to several existing source control systems, including ClearCase, Perforce, and Visual SourceSafe, should make most migrations relatively straightforward.

I recommend people either make the change to Ant or at least investigate it as a serious option for managing the build process.

David Thomson is CEO & Head Chef at The Games Kitchen Ltd.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.