Ant transforms using Groovy template scriptdef

April 9, 2012

Ant provides powerful transformation capability. For example, the copy task can include filters that replace tokens in the text. There is also the XSLT task to perform more complex XML based transforms.

Sometimes you may need more dynamic or procedural transforms for non-xml based templates. While this can be done in Ant, they should not be; Ant should really only be used in a declarative manner. One can do this outside of Ant using custom solutions or use the various templating frameworks, such as Velocity and Freemarker, which provide Ant plugins.

Another alternative is the Groovy language which has always provided text processing in the form of GStrings and templates. These are used in various solutions such as Groovlets and Groovy Server Pages. Below I show how Groovy templates can be easily used in Ant.

listing 1 shows a meeting agenda text template with a list of topics. Note that it has a GString interpolated string using “$” character. It also, analogous to Java Server Pages, uses a scriptlet with <% and %> to execute code. Yes I know, scriptlets are evil.

List 1, template1.txt, a template:

Hello ${name}!
Agenda is:
<% 
   subject.split(',').each{
%>- $it 
<% }	
%>

In listing 2 below, an Ant script invokes the “template” task to transform the template in listing 1.

Listing 2, build.xml, use template task:

<project default="init">
	
<import file="tasks.xml"/>
	
<target name="init" 
  xmlns:jb="http://josefbetancourt.wordpress.com">
  <jb:template 
	src="template1.txt" 
	dest="temp.txt" 
	property="result" 
	failOnError="true">
		
	<property name="name"    
		value="world"/>
	<property name="subject" 
		value="beans,GStrings,templates"/>
		
  </jb:template>
	
  <echo>${result}</echo>
	
</target>	
</project>

Listing 3 shows an example run.

Listing 3, example use:

init:
Saving result to temp.txt
     [echo] Hello world!
     [echo]     Agenda is:
     [echo]     - beans
     [echo]     - GStrings
     [echo]     - templates
     [echo]

BUILD SUCCESSFUL

Listing 4 is the implementation. It is a simple scriptdef using Groovy as the script language. The Groovy code is inline but could have been in external file using the “src” attribute of the scriptdef declaration.

Listing 4, tasks.xml, the scriptdef implementation:

<project>
<!-- config stuff not shown -->

<!--
Render a Groovy template using properties to create the binding 
 -->	
<scriptdef name="template" 
  language="Groovy" 
  classpathref="groovy.lib" 
  uri="http://josefbetancourt.wordpress.com">
	
  <attribute name="src"/>   <!--  required -->
  <attribute name="dest"/>  <!--  optional -->
  <attribute name="property"/> <!--  optional -->
  <attribute name="failOnError"/> <!--  optional, true -->
  <element name="property" type="property"/> <!--  required -->
	
<![CDATA[
def SRC= 'src';
def PROPERTY = 'property'
def DEST = 'dest'
def FAILONERROR = 'failonerror'
		
try{
	// convert element properties to map
	def map = [:]
	elements.get(PROPERTY).each{
		map.put(it.name,it.value)
	}
			
	// render the template
	String result = new groovy.text.GStringTemplateEngine()
	  .createTemplate(new File(attributes.get(SRC))
	  .getText())
	  .make(map).toString()
	
	def prop = attributes.get(PROPERTY)
	if(prop){ // save to property?
		project.setNewProperty(prop, result)
	}
		
	def dest = attributes.get(DEST)
	if(dest){ // write to file?
		project.log("Saving to $dest",project.MSG_INFO)
		new File(dest).write(result)
	}
}catch(Exception ex){
	def f = attributes.get(FAILONERROR)
	def failOnError = f ? f : true;

	if(failOnError == 'true'){
		throw ex;
	}		
}			
]]>
</scriptdef>  <!-- end name="template" -->

</project>

Summary
Shown was an example of using Groovy templates in an Ant task. A simple scriptdef was created to allow reuse in Ant scripts.

Extensions
Kind of begs the question, since scriptlets have a bad rep, should there instead be a way of using taglibs in the templates. I think Grails implemented Groovy taglibs.

References


Jenkins CI Server is great

March 16, 2012

Finally got a Jenkins server installed. Had a host of system issues, like communicating to our source code repo.

Jenkins is a joy to use. Well, it is not perfect, what is? Like, I need to pass the user’s name that invoked a build via Jenkins to the target DOS script (yea, Windows) that eventually invokes the legacy Ant scripts. A quick Google search shows that this is asked in various ways, but no answers. For example, here or here. Hmmmm.

Anyway, now comes a trial use, to see if it is what we really need and can we manage it to do what we will want. With 400 plugins, I don’t see how it could lack. Plus, I’m sure I can use the Groovy plugin to cobble something up. Jenkins even includes a Groovy Console. Finally, there is a road map for possible migration of legacy Ant scripts to Gradle using the Gradle Plugin.

I take back my past snarky comment. Jenkins is not just a pretty face on Cron.

BTW, is there some Wiki law that says a wiki shall never ever have a link to the parent project? If you get tossed into a wiki by following a link, invariably you will click in agony at links that should go to the real home. Instead, you have to edit the URL in the address bar. Since I never curse, I can’t write “wtf”.

Off Topic
Was watching the Easyb intro video. BDD is interesting. Definitely “should” is a better then “test”. With so many great tools why are products still bug ridden?

More stuff

  1. Jenkins home page
  2. Continuous integration Not a very good Wikipedia article
  3. Continuous Integration Much better
  4. Continuous Integration in Agile Software Development
  5. Hooking into the Jenkins(Hudson) API
  6. Five Cool Things You Can Do With Groovy Scripts
  7. Parameterized Builds in Jenkins – choosing subversion folders
  8. Groovy Console
  9. Groovy plugin
  10. Switching to Jenkins–Download and Install Artifact Script for Tester
  11. Gradle Plugin

How to fail an Ant Groovy Scriptdef task

February 18, 2012

While you can certainly just throw an exception in a scriptdef to stop an Ant script that is using a scriptdef, perhaps it is best to use Ant’s Exit task. This allows Ant to do whatever it has to do as it handles the resulting BuildException.

The exit task is used in an Ant script as “fail”. To use ‘fail’ in a scriptdef you just use the predefined ‘self’ variable that references the scriptdef instance. This is illustrated in listing 1 below.

An alternative to the scriptdef ant task is to use the ‘groovy’ task that comes with the Groovy environment. This is perhaps the simplest approach when you don’t need the full structure that the scriptdef task makes available to a script. The scriptdef task allows one to pass in Ant XML elements and attributes.

Listing 1

<project name="example" default="demo">

  <!-- reference the groovy libs -->
  <path id= "libs" >
    <fileset dir= "../lib">
      <include name="groovy/groovy-all-1.8.6.jar" />
    </fileset>
  </path>

  <!-- An inline scriptdef that throws an exception -->
  <scriptdef name="solveProblem" 
        language="Groovy" classpathref="libs">

     self.fail("intentionally failed")	 

  </scriptdef>

  <!-- a call of the scriptdef -->
  <target name="demo">
	<solveProblem/>
  </target>
</project>

A run …

>ant -f fail.xml
Buildfile: fail.xml

demo:

BUILD FAILED
fail.xml:15: intentionally failed

Total time: 1 second

Further reading


Archive files using Ant from Groovy

September 12, 2011

In Ant The zip task takes a fileset but only creates one archive. How do you zip files into individual archives?

Use case
One use case for this is log files. Sooner rather then later, every system must deal with the growth of log files. One of the best utilities for handling this is logrotate. This is available on *nix systems. There are probably native versions that do similar tasks in Windows, but I did not locate any ‘free as in beer’ ones.

Approach
Since I’m working with Ant, on Windoze, and can’t install cygwin or other system, will use Groovy to get around Ant limitations. This will not be an attempt to recreate logrotate, of course.

As in a previous post, we can use the Groovy AntBuilder and iterate a fileset to repeatably call an Ant task. In this case we’ll be invoking the zip task. Note that the same approach could be used to create a scriptdef to do this directly in an Ant build script. This would be more usable since we could declaratively define the fileset, etc.

Example

First the configuration. In listing 1, we use a JSON configuration file. No reason, I’m just tired of Property files.

Listing 1, JSON configuration file

{ "about":
    {
    	"project":"archivelogs",
    	"author":"jbetancourt",
    	"date":"20110909T2156"    	
    },
  "config":
	{
		"basedir":"test/logs",
		"archivedir":"test/logs/archive",
		"includes":"*.log",
		"propertyName":"found.files",
		"when":"before",
		"update":"true",
		"days":"1",
		"level":"9"
	}
}

In listing 2, the main entry point calls the config loader and then the ‘archiveLogs’ method.

The fileset created with the AntBuilder uses an Ant ‘date’ selector to find the files that are x amount of days old. Then the resulting fileset is scanned in a loop, invoking the zip task as we go and then deleting the just zipped file. It is assumed here that the target file name has descriptive information, like a date. And, we are not adding a “rotate” counter to the name for future logrotate like behavior.

Listing 2, Groovy archive source

package ant

import org.apache.tools.ant.*
import java.text.*
import groovy.json.JsonSlurper

/**
 * @author jbetancourt
 */
class ArchiveLogs {
	def format = "yyMMdd'T'kkmm"
	def df = new SimpleDateFormat(format)

	/** entry point */
	static main(args) {
		def als = new ArchiveLogs()	
		
		def p = (
		  (args) ?
		  args[0] : "test/conf/archivelogs.json")
				
		def map = als.loadConfiguration(p)
		als.archiveLogs(map)
	}

	/**  */
	def archiveLogs(conf) {
		def base = conf.base
		def targetDateString = df.format(
			(new Date() - Integer.parseInt(conf.days))
		)
		
		def ant = new AntBuilder()		
		ant.fileset( 
			dir:conf.basedir, 
			includes:conf.includes, 
			id:conf.propertyName){			
				date( 
					datetime:targetDateString,
					when:conf.when,
					pattern:format)
		}	
		
		def ref = ant.project.getReference(
			 conf.propertyName)
		
		ref.each{ fileResource ->
			File file = fileResource.getFile()
			def name = file.getName()
			
			ant.zip(
				basedir:conf.basedir,
				destfile:
				 "${conf.archivedir}/${name}.zip",
				update:conf.update,
				level:conf.level,
				includes:name
			)
			
			ant.delete(verbose:"true",file:file.getPath())			
		}		
		
	}	
	
	/** load json file */
	def loadConfiguration(String path){
		def slurper = new JsonSlurper()
		def obj = slurper.parse(
			 new File(path).newReader())
		
		if(obj.config.debug == 'true'){
			obj.config.each{
				println it
			}		
		}
		
		return obj.config
		
	}
}

Caveats
This is, of course, a simplistic approach. For example, what if the file is being used when we attempt to delete it? Or we fail to zip the file, but continue to delete it? And, this source, being an example, does not have any error handling.

Alternatives
The ant-contribs project has looping support, foreach. Directly using the Java util packages for archiving, such as java.util.zip. Or, the libraries found in, for example, the Apache commons project.

Further Reading


“cafe”, egberto gismonti


Groovy Scriptdef to detect properties with trailing spaces

September 2, 2011

One potential source of errors in a project is the presence of undesired terminating spaces on properties loaded by java.util.Properties. Since property files are also used to deploy or configure applications, it is possible for these sources of errors to disrupt deployed services.

Alternatives
There are of course many options to prevent this from happening, such as:
1. Property file or general editors that can detect or show “spaces”.
2. Another is to create property file loaders that can ‘trim’ spaces.
3. And, yet another is that configuration properties are part of the resources that a management layer monitors as part of an autonomic processing.
4. Perhaps an optimal and most immediate approach is to just have actual tests triggered on any changes, and, where it matters, properties are validated according to specified rules.

The simple approach
An Ant Scriptdef definition implemented in the Groovy language. This can be easily used in the build and deployment process. It can also be used at the deployment location or remotely to check deployed property files.

In listing 1, an Ant script shows the definition of the scriptdef and its use.

Listing 1, Snippet of Ant script

<scriptdef name="blankpropertycheck" 
      language="Groovy" 
      classpathref="libs" 
      src="TrailingSpacePropertiesCheck.groovy">		
		
             <attribute name="failonerror"/>
             <attribute name="property"/>
             <attribute name="verbose"/>
		
             <element name="fileset" type="fileset"/>		
</scriptdef>

<target name="init"  description="---initialize build">
    <!-- use the scriptdef -->	
    <blankpropertycheck
           failonerror="true"
           property="anyFound">		

        <fileset dir=".">
             <include name="**/*.properties"/>
        </fileset>		
			
    </blankpropertycheck>
		
    <echo>Properties with trailing spaces:</echo>
    <echo>${anyFound}</echo>
		
</target>

The new task definition specification:

Attributes
failonerror optional
verbose optional, one of verbose or property
property optional, one of verbose or property
Elements
fileset required

Now we create two example property files in the directory (not shown here) and run the Ant script, with results shown in listing 2. This output was produced with a debug variable set true, to show the data binding in the scriptdef.

Listing 2, sample run

C:\temp\ant\blanksCheck>ant
Buildfile: C:\temp\ant\blanksCheck\build.xml

init:
[blankpropertycheck] ****************************************************************
[blankpropertycheck] failOnError='true'
[blankpropertycheck] propToSave='anyFound'
[blankpropertycheck] verbose='null'
[blankpropertycheck] fileset='abc.properties;lib\log4j.properties;lib\xyz.properties'
[blankpropertycheck] ****************************************************************
     [echo] Properties with trailing spaces:
     [echo] C:\temp\ant\blanksCheck\abc.properties
     [echo]     four=[4    ]
     [echo]     twoWithSpaces=[2  ]
     [echo] C:\temp\ant\blanksCheck\lib\xyz.properties
     [echo]     badwiththreespace=[m   ]

BUILD SUCCESSFUL
Total time: 1 second

The Groovy script is shown in listing 3 below. As implemented, the failonerror is for script or variable binding errors. Since properties could by design have trailing spaces, this is not considered an error. It is easy to change the script to behave differently, of course.

The scripts also serves as an example (to myself) of:

  • regular expression use
  • dynamic object to boolean conditionals
  • accessing object properties via a map
  • reusing closures as actions

Listing 3

/*
    File: TrailingSpacePropertiesCheck.groovy
    Author: josef betancourt
*/

import org.apache.tools.ant.*
import org.apache.tools.ant.types.*

boolean debug = true

failOnError = attributes.get("failonerror")
propToSave = attributes.get("property")
verbose = attributes.get('verbose')
fileset = elements.get('fileset')[0] as FileSet

if(debug){
    band = '*' * 72
    println(band)
    ['failOnError','propToSave','verbose','fileset'].each{
        println "$it='${this[it]}'"
    }
    println(band)
}

// Do we have all essential attributes and elements?
def msg =''
msg =  !fileset ? "'fileset' not found;" : ''
msg += !(verbose || propToSave) ?
         "both 'verbose' and 'propToSave' not found" : '';

if(msg){
    if(failOnError){
        throw new BuildException(msg)
    }
    
    println msg
    return    
}


pattern =  ~/(\s+)$/

/** Count any trailing spaces in string  */
int countTrailingSpaces(aString){
    def matcher = pattern.matcher(aString)
    def result = matcher.find()
    
    if(result){
        def blanks = matcher[0][1]
        return blanks.size()
    }
    
    return 0
}


def props = new Properties()
def count
// map of  file:[list of potential properties in error]
def results = [:]

// analyize each file in fileset
fileset.each{
    File file = it.getFile()    
    props.load(file.newReader());
    def list = []
    
    props.each{ entry ->
        count = countTrailingSpaces(entry.value)
        if(count){
            list.push(entry)
        }
    }
    
    if(list){
        results[file.getPath()] = list
    }
    
    props.clear()

} // end each file

def action // a closure
def resString = ""

if(!results){
    return
}

if(verbose){
    action = { x -> println x}
}else{
    // converts to StringBuilder
    resString <<= ""
    action = {x -> resString << (x + "\n") }
}
    
results.each{
    action("${it.key}")
    it.value.each{
        action("\t${it.key}=[${it.value}]")
    }
}
    
if(!verbose){
    project.setNewProperty(propToSave,resString.toString())
}

// end of file TrailingBlankPropertiesCheck.groovy

Properties
Should any properties in configuration or data files have trailing spaces? Seems like having one of those “kick me” signs on your back. If one could use visible terminators like a quote or brace, but usually a Java based properties file will not have terminators, afaik.

Summary
Shown was a simple method of checking property files for entries with trailing spaces on values.

Further Reading

  • The Groovy Truth” Groovy has special rules for coercing non-boolean objects to a boolean value. Using this language feature makes coding much more productive.

Extend a Groovy Scriptdef with inline task

August 23, 2011

If you use AntBuilder within a Scriptdef (which does sound weird), how do you then use inline task elements?

Intro
In a prior post, “Groovy AntBuilder in Ant Scriptdef to Replace Props,” I used an AntBuilder to iteratively create an Ant PropertyFile task. The task was then used as shown in listing 1.

Listing 1 – Snippet of prior Ant script

  <!-- use the "propertiesreplace" scriptdef -->
  <target name="merge" depends="init">

       <propertiesreplace 
              targetfile="oldProps.properties" 
              newpropfile="newProps.properties"
       />

  </target>

Using elements
But now I want (don’t ask, I really did have a good use case) to also explicitly define other replacements using the PropertyTask directly. Sure I could use it after the use of the scriptdef, but how would it be used inside the scriptdef itself? So now the use case is:

Listing 2 – New Ant script with PropertyFile task element

  <!-- use the "propertiesreplace" scriptdef -->
  <target name="merge" depends="init">

       <propertiesreplace 
              targetfile="oldProps.properties" 
              newpropfile="newProps.properties"

              <propertyfile file="oldProps.properties">
                <entry key="ten" value="XX"/>			
              </propertyfile>
       />

  </target>

We still want to use the scriptdef defined task since we are merging property files, but we also want to make other replacements (or modify those that the files would create).

Solution
You’d think, at least I did for a while, that you have to delve into the arcane internals of Groovy’s NodeBuilder and all that. You don’t. See listing 3; you just execute it.

Listing 3 – New Scriptdef handling element

<!-- Replace properties in oldfile 
whose key match those in newfile -->

<scriptdef name="propertiesreplace" language="groovy"
    classpathref="libs">

    <attribute name="mergefile"/>
    <attribute name="propfile"/>

    <![CDATA[
     def ant = new AntBuilder(project)

     ant.propertyfile( file:attributes.get("mergefile") ){
          def props= new Properties()
          props.load(
               new File( attributes.get("propfile") )
                     .newReader()
          )

          props.each{ cur ->
               entry( key:cur.key,value:cur.value )
          }
     }

     def pf = elements.get("propertyfile").get(0)
     pf.execute()
    ]]>
</scriptdef>

Update
Feb 18, 2012: Wow, I looked at listing 3 and couldn’t remember how that worked.
“elements” is a map of the xml nodes within the scriptdef invocation shown in listing 2. Since it is a map we get the corresponding element by key, get(key). Why the final get(0)? Hmmmm. Oh yeah, there may be more then one element with the same name, so we just get the first in this present example.

Off topic
1. It would be nice to have a new GDK extension on Properties class, so that we can load a properties file and iterate as we do with Map. For example:

             new Properties(filepath).each{
                     println "${it.key}=${it.value}"
             }

2. AntBuilder makes using Ant effortless. Imagine copying a log4j properties file to target folder, editing an entry, then printing out the file. Sure you can you use the copy task with nested tokenizer, but not always. Here it is with PropertyFile:

def ant = new AntBuilder()

ant.tstamp(){
  format(property:"BUILD.DATE",pattern:"yyyy-MM-dd'T'HHmmss")
}

def now = ant.project.getProperty("BUILD.DATE")
def path = "../../../user/temp/${args[0]}"

ant.copy(
  file:"../lib/log4j.properties",
  todir:path
)

ant.propertyfile(file:"${path}/log4j.properties"){
    entry(
      key:"log4j.appender.LogFile.file",
      value:"builder-${now}.log"
    )
}

print (new File(targetFile).getText())

That is pretty nice. Not my code, the concept.

3. Along these lines some projects provide even better use of Groovy to handle ‘build’ stuff. The Gant project puts a lightweight facade upon the AntBuilder to create a tool for Ant scripting. And, Gradle takes that to a whole new level to create a “build framework”.

Further Reading

  1. Using Ant from Groovy
  2. Gradle
  3. Gant
  4. Writing Domain-Specific Languages
  5. Groovy for Domain-Specific Languages

Proxy A Groovy Ant BuildListener?

August 20, 2011

In 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 in for the listener.

See update below for another attempt that succeeds.

In short, I can’t. The first problem was classloader issues, but I think I fixed that by clearing the CLASSPATH:
set CLASSPATH =

Then a host of issues, until finally I give up. I think the big complexity is that BuildListener is an Interface. That means you have to jump hoops to proxy it. I’m sure Groovy can do this, but I don’t see it yet. May need more info on Groovy’s MOP.

Perhaps there is too much indirection being used. Is modern software engineering just ingenious ways to hide a GOTO? Just kidding.

Using commons-proxy library
Apache’s commons-proxy puts a more usable framework around different proxy factories.

Listing 1 – Proxy using Javassist library

import org.apache.commons.proxy.factory.javassist.*
import java.lang.reflect.*
import org.apache.commons.proxy.*
import org.apache.tools.ant.*

class MyListener implements Invoker  {
    static int count = 0;

    Object invoke(Object proxy, Method method, Object[] arguments) 
      throws Throwable{
	new File("finished.run").setText("count: ${count++}")   
    }

}

MyListener proxy = new MyListener()		
java.lang.ClassLoader loader = proxy.class.getClassLoader()

JavassistProxyFactory factory = new JavassistProxyFactory()

Object listener = factory.createInvokerProxy(  
	loader,
	proxy,
	[BuildListener.class] as Class[]
);

project.addBuildListener(listener)		

// end source

Listing 2 – build script

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

<path id="libs">
    <fileset dir="lib">
	<include name="commons-proxy-1.0.jar" />
	<include name="javassist.jar"/>
	<include name="groovy-all-1.8.0.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>

</project>
    
   

Listing 3 – stacktrace snippet

C:\temp\ant\groovyListener>ant
Buildfile: C:\temp\ant\groovyListener\build.xml

BUILD FAILED
java.lang.ClassCircularityError: groovy/runtime/metaclass/java/io/FileMetaClass
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:169)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:127)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:242)
        at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:71)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
        at MyListener.invoke(Script1.groovy:10)
        at JavassistUtilsGenerated_0.messageLogged(JavassistUtilsGenerated_0.java)
        at org.apache.tools.ant.Project.fireMessageLoggedEvent(Project.java:2254)
        at org.apache.tools.ant.Project.fireMessageLogged(Project.java:2290)


Update

This works, but just as wordy as the original using a BuildListener class implementation.

import org.apache.tools.ant.*

listenerMap = [
  theTarget : attributes.get("endtarget"),
  buildFinished: { event ->	
  new File("finished.run").
    setText(
     "Build Finished:  message='${event.message}' exception='${event.exception}'"
    )
	
    project.executeTarget(listenerMap.theTarget)
	
  },

  buildStarted : {},
  messageLogged : {},
  targetFinished : {},
  targetStarted : {},
  taskFinished : {},
  taskStarted : {},
]

listener = listenerMap as BuildListener

project.addBuildListener(listener )		

Uses the technique outlined at:
Groovy Way to implement interfaces

Conclusion

Updates

Further Reading

Listing 4 – Full stacktrace

C:\temp\ant\groovyListener>ant
Buildfile: C:\temp\ant\groovyListener\build.xml

BUILD FAILED
java.lang.ClassCircularityError: groovy/runtime/metaclass/java/io/FileMetaClass
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:169)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:127)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:242)
        at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:71)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
        at MyListener.invoke(Script1.groovy:10)
        at JavassistUtilsGenerated_0.messageLogged(JavassistUtilsGenerated_0.java)
        at org.apache.tools.ant.Project.fireMessageLoggedEvent(Project.java:2254)
        at org.apache.tools.ant.Project.fireMessageLogged(Project.java:2290)
        at org.apache.tools.ant.Project.log(Project.java:470)
        at org.apache.tools.ant.Project.log(Project.java:459)
        at org.apache.tools.ant.AntClassLoader.log(AntClassLoader.java:396)
        at org.apache.tools.ant.AntClassLoader.findClass(AntClassLoader.java:1310)
        at org.apache.tools.ant.AntClassLoader.loadClass(AntClassLoader.java:1064)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:169)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:127)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:242)
        at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:71)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
        at MyListener.invoke(Script1.groovy:10)
        at JavassistUtilsGenerated_0.taskFinished(JavassistUtilsGenerated_0.java)
        at org.apache.tools.ant.Project.fireTaskFinished(Project.java:2206)
        at org.apache.tools.ant.Task.perform(Task.java:364)
        at org.apache.tools.ant.Target.execute(Target.java:390)
        at org.apache.tools.ant.helper.ProjectHelper2.parse(ProjectHelper2.java:180)
        at org.apache.tools.ant.ProjectHelper.configureProject(ProjectHelper.java:82)
        at org.apache.tools.ant.Main.runBuild(Main.java:793)
        at org.apache.tools.ant.Main.startAnt(Main.java:217)
        at org.apache.tools.ant.launch.Launcher.run(Launcher.java:280)
        at org.apache.tools.ant.launch.Launcher.main(Launcher.java:109)

Total time: 1 second
java.lang.ClassCircularityError: groovy/runtime/metaclass/java/io/FileMetaClass
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:169)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:127)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:242)
        at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:71)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
        at MyListener.invoke(Script1.groovy:10)
        at JavassistUtilsGenerated_0.messageLogged(JavassistUtilsGenerated_0.java)
        at org.apache.tools.ant.Project.fireMessageLoggedEvent(Project.java:2254)
        at org.apache.tools.ant.Project.fireMessageLogged(Project.java:2290)
        at org.apache.tools.ant.Project.log(Project.java:470)
        at org.apache.tools.ant.Project.log(Project.java:459)
        at org.apache.tools.ant.AntClassLoader.log(AntClassLoader.java:396)
        at org.apache.tools.ant.AntClassLoader.findClass(AntClassLoader.java:1310)
        at org.apache.tools.ant.AntClassLoader.loadClass(AntClassLoader.java:1064)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:169)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:127)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:242)
        at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:71)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
        at MyListener.invoke(Script1.groovy:10)
        at JavassistUtilsGenerated_0.taskFinished(JavassistUtilsGenerated_0.java)
        at org.apache.tools.ant.Project.fireTaskFinished(Project.java:2206)
        at org.apache.tools.ant.Task.perform(Task.java:364)
        at org.apache.tools.ant.Target.execute(Target.java:390)
        at org.apache.tools.ant.helper.ProjectHelper2.parse(ProjectHelper2.java:180)
        at org.apache.tools.ant.ProjectHelper.configureProject(ProjectHelper.java:82)
        at org.apache.tools.ant.Main.runBuild(Main.java:793)
        at org.apache.tools.ant.Main.startAnt(Main.java:217)
        at org.apache.tools.ant.launch.Launcher.run(Launcher.java:280)
        at org.apache.tools.ant.launch.Launcher.main(Launcher.java:109)
groovy/runtime/metaclass/java/io/FileMetaClass

 

 

 


 

“Timeless” solo 12 string guitar played by Ralph Towner on his “Solo Concert” CD. YouTube


Follow

Get every new post delivered to your Inbox.