BuildListener using Groovy In Ant Scriptdef

Show how to add a BuildListener inside Script by using Scriptdef and Groovy.

If at the end of an Ant build you must record the build status and, for example, send an email, the Ant manual recommends you add a BuildListener to your project.

A listener is added by running Ant with a -listener argument on the command line. Can it instead be done within the build script itself? Yes, and for rapid prototyping I show how to use Groovy to do so.

Using a Scriptdef
Since Ant executes top level (outside of any target) tasks first, we can execute a custom task that installs a BuildListener implementation. First we must create a task that does this.

Instead of using ‘taskdef’, using a ‘Scriptdef’ defined task allows use of the Groovy language to access the Project object and do this. Of course, there are other languages one could use in Ant, such as JavaScript, BeanShell, JRuby, JPython, etc.

In listing 1 below we also allow the BuildListener to invoke a specified target when the buildFinished event is processed. The example buildFinished event handler method just creates a file with the event info. Then the specified terminal target is invoked and the content of the that file is listed.

Saving the build results to a file allows other processes in a build pipeline to reuse that information.

This just might be what I need to finish my current project.

Listing 1 – Example

<project name="listener" default="default" basedir=".">
<!--
File: build.xml, author: J.Betancourt,
    date:2011-08-18T2137
-->

<path id="libs">
    <fileset dir="lib">
        <include name="*.jar" />
    </fileset>
</path>

<!-- Groovy library -->
<typedef resource="org/codehaus/groovy/antlib.xml" classpathref="libs" />    

<!-- sets a BuildListener to the project -->
<scriptdef name="set-listener" language="Groovy" 
   classpathref="libs" src="lib/BuildListener.groovy">

  <attribute name="endTarget"/>

</scriptdef>

<!-- install the listener -->
<set-listener  endTarget="list"/>    

<target name="default">
    <fail message="doh!"/>    
</target>

<target name="list">
    <echo>Content of 'finish.run' file:</echo>    
    <loadfile srcFile="finished.run" property="finishContent" failonerror="false"/>
    <echo message="${finishContent}"/>

    <!-- <exec executable="cmd.exe" >
        <arg line="/c type finished.run"/>
    </exec>  -->

</target>    

</project>
    
   

Listing 2 – the BuildListener Groovy implementation

   import org.apache.tools.ant.BuildEvent

   class MyListener implements
     org.apache.tools.ant.BuildListener {

       def theTarget
       def project            
            
       def runTargets(){                    
         project.executeTarget(theTarget)                
       }
            
       public void buildFinished(
         org.apache.tools.ant.BuildEvent event){
             new File(&quot;finished.run&quot;).
                setText(
                 &quot;Build Finished:  message='${event.message}'
                           exception='${event.exception}'&quot;)
         runTargets()                    
       }
            
       public void buildStarted(BuildEvent event){}
       public void messageLogged(BuildEvent event){}
       public void targetFinished(BuildEvent event){}
       public void targetStarted(BuildEvent event){}
       public void taskFinished(BuildEvent event){}
       public void taskStarted(BuildEvent event){}    
   } // end class
        
   def targetName = attributes.get(&quot;endtarget&quot;)            
   def listener = new MyListener(
         theTarget:targetName,project:project)          

   project.addBuildListener(listener)       

// end source

That subclass with the empty methods is quite ugly. I think it’s time to read up on the new @Delegate annotation new with Groovy 1.8*. [update] Looked at it. @delegate won’t help. Using a proxy framework that works with Interfaces leads to classloader hell. I tried commons-proxy.

Listing 2 – Example run

C:tempantgroovyListener&gt;ant
Buildfile: C:tempantgroovyListenerbuild.xml

default:

BUILD FAILED
C:tempantgroovyListenerbuild.xml:51: doh!

Total time: 1 second

list:
     [echo] Content of 'finish.run' file:
     [exec] Build Finished:  message='null'  
              exception='C:tempantgroovyListenerbuild.xml:51: doh!'
  
  

Seems to work. Haven’t tested enough.

I got this idea from a presentation by Steve Loughran. In those slides he uses the “internal” approach but by using a taskdef and external Java coding. He also executes a list of subtasks, whereas in my example, I invoke a target. Btw, the book “Ant In Action” is very good.

Practical Use?
There are certain caveats regarding a BuildListener, such as not advisable to do i/o with the standared console and error streams.

Also, as used here, when the buildlistener is added to the build script, very likely, most of the configuration and properties are not known. In my project, I got around this by using reflection on the listener object attached to the project. You get those by:

def vect = project.getBuildListeners()

... iterate and find the matching class

def clz = listener.getClass()
def fld = clz.getField(fieldName)
fld.set(listener, value)

Where fieldName and value are what your particular listener impl require to do its buildFinished handling. I’m sure there are better approaches.
<doh> Could have just used project.getProperties() </doh>

Conclusion
Presented was a simple method of using an Ant BuildListener via ScriptDef using the Groovy language.

But, is an Ant build script really finished if it is busy running targets or tasks? Perhaps it is finishing …

Updates
2011-08-20:

  • Another option to run code when build is finished (even with error?) is to use a CustomExitCode class. See Ant manual.

Further Reading

2 thoughts on “BuildListener using Groovy In Ant Scriptdef”

Leave a Reply

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