Java Development with Ant: Ant

As you should expect, this project leverages Ant to do the work for constructing the deliverables. This document will explain some of the more interesting features of the Ant build file.  It is not designed to document Ant, as this already well done with Ant's own documentation and of course our Java Development with Ant book.

The interesting features of the build file include:

See the projects build.xml file for other features not mentioned here.

Site concept

Throughout the build file and this documentation there will be references to "site".  This corresponds to a site name, by default its "sample".  The site name maps to a directory (by default its under the sites directory).  Any number of sites can be placed under this directory (or elsewhere by overriding site.dir property) and built.  Most build artifacts are common to all sites, there are a few that are specific to a site such as its index, some deployment descriptors, and the search results view.  See the FAQ for more details on working with the site concept.

Library dependencies

In order to tame the complexity of dealing with library dependencies within a project or across multiple projects, a scheme was developed to map them using Ant properties which refer to version number, directory, and JAR libraries within a common lib directory.  Benefits include allowing easy switching of one library version to another on a per-project, per-build, or even a per-user basis.

The key to this scheme is Ant's property immutability. Once a property is set, its value may not be changed (with some exceptions, but generally speaking this is true). Property files are loaded by the main build file in this order (and order is important!):

<property file="${user.home}/.${ant.project.name}-build.properties"/>
<property file="${user.home}/.build.properties"/>
<property file="build.properties"/>

<!-- Load environment variables -->
<property environment="env"/>

<!-- Assign a friendly name for JBOSS_HOME -->
<property name="jboss.home" location="${env.JBOSS_HOME}"/>

<!-- Capture the computer name in a cross-platform manner -->
<property name="env.COMPUTERNAME" value="${env.HOSTNAME}"/>

<!-- Load generally common properties, such as javac.debug -->
<property file="common.properties"/>

The first three lines load user-project, user, and project specific settings, with the user settings loaded from the ${user.home} directory.  Then environment variables are loaded and prefixed with "env.", allowing the earlier property files a chance to override environment variable properties.

Next, a jboss.home property is set to make a friendlier appearance in the remainder of the build file where it is needed.

The env.COMPUTERNAME property line is an interesting trick to get the host name of the machine running the build in a cross-platform manner.  Windows machines use the COMPUTERNAME environment variable while 'nix platforms use HOSTNAME (except Mac OS X, through version 10.2.3 at least, *arg*).

Finally, common.properties is loaded.  It contains the general properties that most build files have, such as build.dir.

Now back to the library properties.  Just a few lines later in the build file, the lib/lib.properties file is loaded:

<property name="lib.dir" location="lib"/>
<property file="${lib.dir}/lib.properties"/>

The lib.properties file contains properties for each of the dependent libraries. The Struts library is a good example:

struts.version     = 1.1-b2
struts.dir=${lib.dir}/jakarta-struts-${struts.version}-lib
struts.jar=${struts.dir}/struts.jar

Have a look at the directory structure under the lib directory to get an idea of how this works, and match it up to other libraries listed in lib.properties.  Basically, the dependent libraries are extracted in-tact into the lib directory, directory structure and all (sometimes the top-level directory must be named manually, sometimes not). The mapping in lib.properties takes this structure into account and maps the .dir and .jar properties appropriately.

All libraries referenced in the build file are done so through the .jar properties (except XDoclet, but thats just out of laziness, but to be more precise they should be also).  For example, the classpath to compile the web code in this project is:

<path id="web.compile.classpath">
<pathelement location="${dist.dir}/antbook-common.jar"/>
<pathelement location="${dist.dir}/antbook-ejb.jar"/>
<pathelement location="${struts.jar}"/>
<pathelement location="${oro.jar}"/>
<pathelement location="${commons-digester.jar}"/>
<pathelement location="${commons-fileupload.jar}"/>
<pathelement location="${commons-lang.jar}"/>
<pathelement location="${commons-resources.jar}"/>
<pathelement location="${commons-validator.jar}"/>
<pathelement location="${j2ee.jar}"/>
</path>

Now to the slick part of this scheme, making it all worthwhile.... to build with a newer version of a library, simply install it in the lib directory as the others are and build overriding the appropriate .version property.  For example, to build with a new release (perhaps the 1.1 final release) of Struts:

ant -Dstruts.version=1.1

Or if you have a newly built version of Lucene that needs to be validated against the test cases, simply point the .jar property to its location:

ant -Dlucene.jar=/path/to/new/lucene.jar
Or modify (possibly create, if not already in existence) any of the three .properties files that are loaded and override the properties there for highly flexible control.  The flexibility is not just for library properties, its for any of the other properties too.

Reusable compilation and testing targets

This project consists of four sub-"modules": anttask, common, ejb, and web. These each contain a separate tier of the application.  Rather than having four copies of the same <javac> task within the build, a resuable target "compile-module" is used.  This target compiles both the production and test source code, which provides a partial test simply during compilation ensuring that the test code compiles successfully before the possibility of deployment.  Likewise, testing a module is done through a reusable "test-module" target.  Both of these targets are invoked with <antcall> where appropriate.  See build.xmlfor the details.

<fileset file="..."> usage

Ant 1.5.1 contains a new feature (no, its not in Ant 1.5) to make a single file fileset much easier to create. This feature is used extensively in this project because of its great value, especially using the library dependency scheme detailed above to include a single JAR file by property mapping.  See build.xml for details. Note that <lib> within the <war> task is a fileset reference too.

Custom Ant task development

The "build-site-index" target of the main build file uses a custom Ant task to index a directory of files.  The inner workings of this task are detailed in the Lucene document, but its usage is explained here.  To index a complete directory tree of text and HTML files, the<index> task is used:
<index index="${index.dir}"
overwrite="false">
<fileset dir="${site.dir}"/>
</index>

A Lucene index is built/updated.  The index (technically a directory with many strangely named files in it) is left in place and the application refers to it there. See the FAQ for more on the index location.

Embedding build information into application

Capturing the build information such as build date/time, user who performed the build, and which machine it was built on is handy developer debugging information.  Optionally this could be enhanced to include the source code repository label (aka tag) that was used to build this system. This is accomplished using the <propertyfile> task.
In this particular application, the properties file is included in the classpath of the web application, making it easily available to the BuildPropertiesTag custom tag so that it can be generated in an HTML comment at the bottom of every page (see the Struts template for how this works, and view the HTML source generated by the application in your browser for the results).
<propertyfile comment="Build Information"
file="${build.dir}/web/classes/build.properties">
<entry key="build.date"
type="date"
pattern="EEEE MMM dd, yyyy"
value="now"/>
<entry key="build.time"
type="date"
pattern="kk:mm:ss"
value="now"/>
<entry key="build.timestamp"
type="date"
pattern="yyyy-MM-dd'T'HH:mm:ss"
value="now"/>
<entry key="build.user.name" value="${user.name}"/>
<entry key="build.computer" value="${env.COMPUTERNAME}"/>
</propertyfile>

Filterset replacements

It can be necessary to substitute some build-time information into text files during the build process.  This project uses Ant's builtin filterset feature in two places, to build a custom search.jsp results view for each site, and to customize the EAR's application.xml file with the EJB JAR and WAR file names based on the site name.  Here is the application.xml substitution:
<copy todir="${build.dir}/${site}" overwrite="true">
<fileset dir="metadata/app" includes="application.xml"/>
<filterset>
<filter token="SITE" value="${site}"/>
</filterset>
</copy>
Within the templated application.xml, the string "@SITE@" ('@' is the default delimiter) is replaced by the actual site name, which is in the value of the${site} Ant property.