Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

Introduction

Recent version of Ant have included a mechanism called Antlibs. These allow you to define your own custom tasks, group them together with the appropriate definitions needed by Ant and use them in your Ant environment without nameclashes. Nameclashes are avoided by using namespaces. Numerous Antlibs are now available from both Apache (the developers of Ant) and other sources. Using these libraries with Groovy is fairly easy - though you have to be careful with some of the details.

AntUnit

The AntUnit antlib includes predefined <assert> tasks corresponding to the most common kind of checks you want to do within your build files. They are using thoughout the Ant codebase to test many of the ant tasks but you can use these assertions in your own build files (or any Groovy code) too.

Here is an example the uses the assertFileDoesntExist and assertFileExists checks.

First, we'll consider the traditional way of incorporating this antlib, by using namespaces (you'll need the antunit jar in your classpath before you begin - as we are relying on Ant's autodiscovery of antlibs mechanism here):

Code Block
def ant = new AntBuilder()

ant.'antlib:org.apache.ant.antunit:assertFileDoesntExist'(file:'copytest1.tmp')
ant.copy(file:'src/antunit.groovy', tofile:'copytest1.tmp')
ant.'antlib:org.apache.ant.antunit:assertFileExists'(file:'copytest1.tmp')
ant.delete(file:'copytest1.tmp')
ant.'antlib:org.apache.ant.antunit:assertFileDoesntExist'(file:'copytest1.tmp')

Notice that the antunit assertions all exist within their own namespace. That's OK for now, Groovy allows special symbols in method names so long as you include the method name in quotes.

We can also incorporate the antlib directly into the default namespace as follows:

Code Block
import org.apache.tools.ant.taskdefs.Antlib
def ant = new AntBuilder()
def url = this.class.getResource('org/apache/ant/antunit/antlib.xml')
Antlib.createAntlib(ant.antProject, url, 'antlib:org.apache.ant.antunit').execute()

ant.assertFileDoesntExist(file:'copytest1.tmp')
ant.copy(file:'src/antunit.groovy', tofile:'copytest1.tmp')
ant.assertFileExists(file:'copytest1.tmp')
ant.delete(file:'copytest1.tmp')
ant.assertFileDoesntExist(file:'copytest1.tmp')

This makes our code look simpler for this example but be careful with this approach though as you need to avoid name clashes. The preferred way is to use the NamespaceBuilder. Using this, our code becomes:

Code Block
import groovy.xml.NamespaceBuilder
def ant = new AntBuilder()
def antunit = NamespaceBuilder.newInstance(ant, 'antlib:org.apache.ant.antunit')
def destfile = 'copytest1.tmp'

antunit.assertFileDoesntExist(file:destfile)
    ant.copy(file:'src/antunit.groovy', tofile:destfile)
antunit.assertFileExists(file:destfile)
    ant.delete(file:destfile)
antunit.assertFileDoesntExist(file:destfile)

Maven Ant Tasks

Another useful antlib is the Maven Ant Tasks. They allow you to use Maven's artifact handling features from within Ant including:

  • Dependency management - including transitive dependencies, scope recognition and SNAPSHOT handling
  • Artifact deployment - file and SSH based deployment to a Maven repository
  • POM processing - for reading a Maven 2.0.x pom.xml file

Here is how you could use these tasks to download some required jars into your local maven repository cache (~/.m2 directory).

Code Block
import groovy.xml.NamespaceBuilder
def ant = new AntBuilder()

items = [[groupId:'jfree', artifactId:'jfreechart', version:'1.0.5'],
         [groupId:'jfree', artifactId:'jcommon', version:'1.0.9']]

def mvn = NamespaceBuilder.newInstance(ant, 'antlib:org.apache.maven.artifact.ant')

// download artifacts
mvn.dependencies(filesetId:'artifacts') { items.each { dependency(it) } }
// print out what we downloaded
ant.fileScanner { fileset(refid:'artifacts') }.each { println it }

When run, this produces a log of the maven ant task activity, such as:

No Format
Downloading: jfree/jfreechart/1.0.5/jfreechart-1.0.5.pom
...
Transferring 298K
C:\Users\Paul\.m2\repository\jfree\jcommon\1.0.9\jcommon-1.0.9.jar
C:\Users\Paul\.m2\repository\jfree\jfreechart\1.0.5\jfreechart-1.0.5.jar

We can take this example further and show how to create the JFreeChart example from Plotting graphs with JFreeChart without having the JFreeChart jars statically defined in our classpath.

First another helper class:

Code Block
class MavenDependency {
    static void require(params) {
        MavenDependencyHelper.getInstance().require(params)
    }
    static MavenDependencyHelper using(classLoader) {
        MavenDependencyHelper.getInstance(classLoader)
    }
}

private class MavenDependencyHelper {
    private classLoader
    private MavenDependencyHelper(classLoader) {
        this.classLoader = classLoader
    }

    static MavenDependencyHelper getInstance(classLoader) {
        return new MavenDependencyHelper(classLoader)
    }

    static MavenDependencyHelper getInstance() {
        return new MavenDependencyHelper(MavenDependencyHelper.classLoader)
    }

    MavenDependencyHelper require(params) {
        def ant = new AntBuilder()
        def mvn = groovy.xml.NamespaceBuilder.newInstance(ant, 'antlib:org.apache.maven.artifact.ant')
        mvn.dependencies(filesetId:"artifact_${params.groupId}_${params.artifactId}_${params.version}") { dependency(params) }
        ant.fileScanner { fileset(refid:"artifact_${params.groupId}_${params.artifactId}_${params.version}") }.each {
            classLoader.addClasspath(it.toString())
        }
        this
    }
}

Now, here is the code we require to dynamically download the JFreeChart jars and add them to our classpath then run the script:

Code Block
// no jfreechart imports required (we'll find them programmatically)
import groovy.swing.SwingBuilder
import static javax.swing.WindowConstants.EXIT_ON_CLOSE

def classLoader = Thread.currentThread().contextClassLoader

// load jars and add to classpath
def maven = MavenDependency.using(classLoader)
maven.require(groupId:'jfree', artifactId:'jfreechart', version:'1.0.5')
maven.require(groupId:'jfree', artifactId:'jcommon', version:'1.0.9')

// define used classes/instances programmatically
def factoryClass     = classLoader.loadClass('org.jfree.chart.ChartFactory')
def orientationClass = classLoader.loadClass('org.jfree.chart.plot.PlotOrientation')
def dataset = classLoader.loadClass('org.jfree.data.category.DefaultCategoryDataset').newInstance()

// normal code below here
dataset.addValue 150, "no.1", "Jan"
dataset.addValue 210, "no.1", "Feb"
dataset.addValue 390, "no.1", "Mar"
dataset.addValue 300, "no.2", "Jan"
dataset.addValue 400, "no.2", "Feb"
dataset.addValue 200, "no.2", "Mar"

def labels = [ "Bugs", "Month", "Count" ]
def options = [true, true, true]
def chart = factoryClass.createLineChart(*labels, dataset,
        orientationClass.VERTICAL, *options)
def swing = new SwingBuilder()
def frame = swing.frame(title:'Groovy LineChart',
        defaultCloseOperation:EXIT_ON_CLOSE) {
    panel(id:'canvas') { rigidArea(width:400, height:400) }
}
frame.pack()
frame.show()
chart.draw(swing.canvas.graphics, swing.canvas.bounds)

Ivy Tasks

We can also download jars using Ivy. In this case we use MarkupBuilder to build an XML file that the Ivy retrieve task will use:

Code Block
import groovy.xml.NamespaceBuilder

def ant = new AntBuilder()
def ivyfile = 'ivy.xml' // default file used by Ivy
ant.delete(file:ivyfile, quiet:true)

new File(ivyfile).withWriter { writer ->
    def builder = new groovy.xml.MarkupBuilder(writer)
    builder.'ivy-module'(version:'1.0') {
        info(organisation:"codehaus", module:"GroovyExamples")
        dependencies {
            dependency(org:'jfree', name:'jfreechart', rev:'1.0.5')
            dependency(org:'jfree', name:'jcommon', rev:'1.0.9')
        }
    }
}

def ivy = NamespaceBuilder.newInstance(ant, 'antlib:org.apache.ivy.ant')
ivy.retrieve()
ivy.report(toDir:'reports') // optional

When run, this results in the files being downloaded:

No Format
[antlib:org.apache.ivy.ant:retrieve] :: Ivy 2.0.0-alpha-1-incubating - 20070416155158
...
[antlib:org.apache.ivy.ant:retrieve] downloading http://repo1.maven.org/maven2/jfree/jfreechart/1.0.5/jfreechart-1.0.5.jar
...
[antlib:org.apache.ivy.ant:retrieve] 	[SUCCESSFUL ] [ jfree | jfreechart | 1.0.5 ]/jfreechart.jar[jar] (16735ms)
[antlib:org.apache.ivy.ant:retrieve] downloading http://repo1.maven.org/maven2/jfree/jcommon/1.0.9/jcommon-1.0.9.jar
...
[antlib:org.apache.ivy.ant:retrieve] 	[SUCCESSFUL ] [ jfree | jcommon | 1.0.9 ]/jcommon.jar[jar] (6812ms)
[antlib:org.apache.ivy.ant:retrieve] :: resolution report ::
	---------------------------------------------------------------------
	|                  |            modules            ||   artifacts   |
	|       conf       | number| search|dwnlded|evicted|| number|dwnlded|
	---------------------------------------------------------------------
	|      default     |   2   |   2   |   0   |   0   ||   2   |   2   |
	---------------------------------------------------------------------
[antlib:org.apache.ivy.ant:retrieve] :: retrieving :: [ codehaus | grails ]
[antlib:org.apache.ivy.ant:retrieve] 	confs: [default]
[antlib:org.apache.ivy.ant:retrieve] 	2 artifacts copied, 0 already retrieved

If you included the optional report step (and add ant-trax.jar from your ant distribution to our classpath), then you would have some additional log information and it would produce the following pretty report on dependencies: