Ant hooks using Groovy scripts via Scriptdef

Ant BuildListener interface is used to add a hooks feature that invokes a Groovy script mapped to build events.

One way of managing or customizing a complex Ant build is to use hook scripts that are executed at defined points in the Ant build life cycle. A use case for this is the reuse of build scripts in multiple build environments, like a build server vs a local workstation build, another is to add extra auditing.

We create a listener that take the project name and the current running target’s name to locate a matching hook file. This file contains a Groovy script to be invoked. Further, we allow a global hook script folder that contains hook scripts that would be applied if there are no matching hook scripts in a project level folder, named after the Ant build script project name.

Of course, this is Groovy specific, but it would be very easy to make this into a more generic hook invocation facility.

Similar ideas
Version control systems use the concept of hook scripts. Before a commit, for example, a hook script can ensure that the commit log contains the correct metadata. In AOP we can advise at ‘before’ and ‘after’ pointcuts.

Ant listeners
An Ant listener will be alerted to:

  • build started
  • build finished
  • target started
  • target finished
  • task started
  • task finished
  • message logged

In this demo we use only the target events.

In the Ant script below, we install a listener before any ant targets are invoked. A scriptdef defines the listener as a script file.

Demo build script

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

    <path id="libs">    
        <fileset dir="lib">
                name="groovy-all-1.8.6.jar" />
    <!-- Groovy library -->
    <taskdef name="groovy"
    <!-- sets a BuildListener to the project -->
    <scriptdef name="set-listener" 
    <!-- install the listener -->
    <target name="compile">
        <echo>Hello compile world!</echo>  


Now we want to use the following actual Groovy hook scripts. The start target hook is defined at the project level, and the finished target hook is defined at the root level.


println "hook: {project=${},target=${},when=pre,event=$event}"


println "hook: root,{target=${},when=post,event=$event}"

Listener implementation
The actual listener implementation is shown below. It uses the build event object to find the project name and target name. Then it searches for the matching hook script, _listenerMethod[Started | Finished].groovy, in the folder that matches the project name “demo1” here. If a hook script is not found, it searches for a matching hook script in the root folder. The found script is then evaluated.

In the example, the hooks/demo1 folder contains file: compile_targetStarted.groovy. Thus, when the Ant runtime invokes the targetStarted listener method on the ‘compile’ target, the listener will invoke the targetStarted hook script.

package com.octodecillion.ant

import static

 * Ant build listener that invokes groovy hook scripts.
 * @author josef betancourt
class HookListener implements SubBuildListener {
    def project
    Map rootHooks = [:]
    Map projectHooks = [:] 
    String TARGETHOOK = "target"
    enum When{
        String name
        When(s) { = s}
    /**                          */
    def HookListener(project){
        this.project = project
        // cache the root hooks
        new File("hooks/root").eachFileMatch FILES, ~/.*\.groovy/, 
            { file ->
                rootHooks.put(getBaseName(, file.text)
        // cache the project hooks
        new File("hooks/$").eachFileMatch FILES, ~/.*\.groovy/,
            { file ->
                projectHooks.put(getBaseName(, file.text)
    public void targetFinished(BuildEvent event) {
        evokeTargetHook(event, When.FINISHED)        

    public void targetStarted(BuildEvent event) {
        evokeTargetHook(event, When.STARTED)    
    /** Invoke the 'started' or 'finished' root or target hook script */
    def evokeTargetHook(BuildEvent event, When when){
        def b = new Binding()
        def shell = new GroovyShell(b)
        def hookName = "${}_${TARGETHOOK}${}"        

        def stored = projectHooks[hookName]        
            // use the cached root hooks if found       
            def hook = rootHooks[hookName]            

    /**                          */
    private String getBaseName(fileName){
        fileName.replaceFirst(~/\.[^\.]+$/, '')        
    /**                          */
    private String getSuffix(fileName){
        def parts = fileName.split("\\.")
        parts.size() > 0 ? parts[parts.size()-1] : ''        
    public void subBuildFinished(BuildEvent event) {}
    public void subBuildStarted(BuildEvent event) {}
    public void buildFinished(BuildEvent event) {}
    public void buildStarted(BuildEvent event) {}
    public void messageLogged(BuildEvent event) {}
    public void taskFinished(BuildEvent event) {}
    public void taskStarted(BuildEvent event) {}

// wire in the listener
def listener = new HookListener(project)
listener.project = project

// end Script


Buildfile: C:\Users\jbetancourt\workspace-4.3\AntAroundAdvice\build.xml

hook: {project=demo1,target=compile,when=pre,}
     [echo] Hello compile world!
hook: root,{target=compile,when=post,}

Total time: 1 second

Around Advice
While possibly useful, a further powerful potential is to allow the bypass of an invocation of a target altogether. One way of doing this is to use a library like XMLTask to modify the build script. This would have to be done before the script is parsed by Ant of course.

One issue with described approach is that the target Ant build scripts must be modified (very minor) to hook in the hooks feature. Using the Ant API itself to add targets and change the target dependency chains might be possible. So is changing Ant itself using AspectJ is also possible.

Of course if very complex scenarios are necessary to manage legacy builds, it may be time to replace Ant with something like Gradle.

Project Layout
The project layout used to the test this hook approach is shown below:

|   .classpath
|   .gitignore
|   .project
|   build.xml
|   tree.txt
|       org.eclipse.jdt.core.prefs
|       org.eclipse.jdt.groovy.core.prefs
|       .gitignore
|       Backup of docs.wbk
|       docs.docx
|   +---demo1
|   |       compile_targetStarted.groovy
|   |       
|   \---root
|           compile_targetFinished.groovy
|       ant-antlr.jar
|       ant.jar
|       groovy-all-1.8.6.jar
|       groovy-all-2.2.0-rc-1.jar
|   \---main
|       \---groovy
|           \---com
|               \---octodecillion
|                   \---ant
|                           Hook.groovy

Further reading

2 thoughts on “Ant hooks using Groovy scripts via Scriptdef”

Leave a Reply

Your email address will not be published. Required fields are marked *