BuildListener using Groovy In Ant Scriptdef

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("finished.run").
                setText(
                 "Build Finished:  message='${event.message}'
                           exception='${event.exception}'")
         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("endtarget")            
   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:\temp\ant\groovyListener>ant
Buildfile: C:\temp\ant\groovyListener\build.xml

default:

BUILD FAILED
C:\temp\ant\groovyListener\build.xml:51: doh!

Total time: 1 second

list:
     [echo] Content of 'finish.run' file:
     [exec] Build Finished:  message='null'  
              exception='C:\temp\ant\groovyListener\build.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

About these ads

3 Responses to BuildListener using Groovy In Ant Scriptdef

  1. [...] my last post BuildListener using Groovy In Ant Scriptdef, I got a scripted BuildListener to work. Was wondering if there were a way to use a Proxy to stand [...]

  2. josefB says:

    Gilbert,
    Good info on that link. That nant capability looks really useful.
    - thanks

  3. Gilbert Rebhan says:

    interesting post ! There’s a similar option of running specific tasks when build has finished via listeners mentioned by Kev Jackson, see : http://stackoverflow.com/questions/1254032/ant-equivalent-of-nant-onsuccess-nant-onfailure/1375833#1375833

    bye4now, Gilbert

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: