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:tempantgroovyListener>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
- AntBuilder: Groovy Meets Ant
- Practically Groovy: The @Delegate annotation
- Extending Ant
- Groovy
- Using Ant from Groovy
- Scriptdef
- PropertyFile Task
- Ant Groovy Task
- Ouroboros
- SED
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
Gilbert,
Good info on that link. That nant capability looks really useful.
– thanks