Tuesday 13 January 2009

Tired of Ant? Try Gant

Ant has been a solid workhorse for building Java applications since its release in 2000, having many favorable properties including: cross platform, mature, provides a large collection of useful tasks and lets not forget the comprehensive documentation. However, Ant is showing its age and has been for some time. The use of XML to represent an external DSL is now generally regarded as a mistake, not to mention noisy due to all of those angled brackets.

Many in the Java community, especially those with experience with Rake from their Ruby adventures, have for some time been looking for alternative ways to build their applications. One noteworthy alternative is Gant (Groovy with Ant).

Gant allows Ant tasks to be defined using Groovy. This approach provides an internal DSL for describing build tasks, which happens to be much more readable than its XML counterpart. Furthermore, users of Gant require very little Groovy knowledge to become productive. One only has to look at an example Gant script to see how Ant task definitions map to Gant definitions and away you go.

One of the arguments I often hear against moving away from Ant is that almost every Java developer is familiar with Ant. Why would we want to move to something that fewer people know? With Gant this argument is not an issue as all of the Ant tasks that people have come to know and love are available. It is only the representation of those tasks that has changed.

If your project is currently using Ant, you are happy with the functionality provided by Ant tasks, but can no longer stand to define your build script with XML. Gant may be just what you have been looking for.

Here is an example Gant build file that performs a number of standard build operations, such as: compile, run tests, report test results and distribute as a jar.

Example Gant Build


sourceDirectory = 'src'
testDirectory = 'spec'
buildDirectory = 'build'

sourceClassesDirectory = buildDirectory + '/src'
specClassesDirectory = buildDirectory + '/spec'
testReportsDirectory = buildDirectory + '/junit-reports'
distributionDirectory = buildDirectory + '/dist'

includeTargets << gant.targets.Clean
cleanPattern << '**/*~'
cleanDirectory << buildDirectory

libDir = 'lib'

def buildtimeDependenciesJars = ant.path(id: 'jars') {
fileset(dir: libDir) {
include(name: '*.jar')
}
}

target(compile: 'Compile source to build directory.') {
mkdir(dir: sourceClassesDirectory)
javac(srcdir: sourceDirectory, destdir: sourceClassesDirectory, debug: 'on')
}

target(compileTests: 'Compile the examples') {

depends(compile)

mkdir(dir: specClassesDirectory)
javac(srcdir: testDirectory, destdir: specClassesDirectory, debug: 'on') {
classpath {
path(refid: 'jars')
pathelement ( location : sourceClassesDirectory )
}
}
}

target ( test : 'Runs the examples') {

depends (compileTests)

mkdir(dir: testReportsDirectory)
junit ( printsummary : 'yes' , failureproperty : 'testsFailed' , fork : 'true' ) {
formatter ( type : 'plain' )
classpath {
pathelement ( location : specClassesDirectory )
pathelement ( location : sourceClassesDirectory )
path(refid: 'jars')
}

batchtest ( todir : testReportsDirectory ) {
fileset ( dir : testDirectory , includes : '**/*Behaviour.java' )
}
}
}

target ( distribute : 'Distributes the library as a jar.') {
depends (clean, test)
mkdir(dir: distributionDirectory)

echo("Creating example-app.jar ...")
jar ( destfile : distributionDirectory + '/example-app.jar' , basedir : sourceClassesDirectory )
}

setDefaultTarget(distribute)