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


BuildListener using Groovy In Ant Scriptdef

August 18, 2011

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


Groovy AntBuilder in Ant Scriptdef to Replace Props

August 17, 2011

Intro

In a build process, you have a properties file, the former one, and another one, the latter file.   Now you want to replace all properties in the former that exists in the latter.   A kind of SED for property files.

This can be done easily in any programming language, but Property files in Java follow a spec and we rather not deal with the complexities.  Ant has the PropertyFile Task for this.

Apache Ant provides an optional task for editing property files. This is very useful when wanting to make unattended modifications to configuration files for application servers and applications.

Need to iterate
The problem is that the PropertyFile task requires the explicit listing of each key value to edit (the task can do more then just replace). AFAIK there is no way to use a resource collection. Simple example:

Listing 1 – example of PropertyFile use

   <propertyfile file="old.properties">
      <entry key="tag.name" value="release_1"/>
   </propertyfile>

We instead require the creation of the elements while we iterate thru the new file.

Propertiesreplace scriptdef
Groovy is a perfect fit for this. The Builder DSL allows a more ‘natural’ approach. I thought it would be a challenge to code this, but in a few minutes, it was done!

The trick is to create a new AntBuilder in the script. However, this may be a bad or non-idiomatic approach. Perhaps a Groovy expert can comment on it?

Listing 2 – the scriptdef

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

<scriptdef name="propertiesreplace" language="groovy"
    classpathref="libs" uri="http://com.jbetancourt1.ant">

    <attribute name="oldfile"/>
    <attribute name="newfile"/>

    <![CDATA[
     def ant = new AntBuilder(project)
     ant.propertyfile( file:attributes.get("oldfile") ){
          def nps = new Properties()
          nps.load(
               new File( attributes.get("newfile") )
                     .newReader()
          )

          nps.each{ cur ->
               entry( key:cur.key,value:cur.value )
          }
     }
    ]]>
</scriptdef>

I should have named it replaceproperties. Note that it would be better to put the Groovy script external, such as:

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

<scriptdef name="propertiesreplace" language="groovy"
    classpathref="libs" uri="http://com.jbetancourt1.ant" 
    src="src\ReplaceProperties.groovy">

    the rest of the declarations from listing 2 ...
</scriptdef> 
This is cool; Ant’s XML declarative language is used to create a programmatic macro using a script language, Groovy, that in turn is using a DSL to generate Ant code. A code Ouroborous.

Listing 3 – the old properties file

one=1111
#four=4444
two=2222
five=5555
three=3333

Listing 4 – the new properties file

one=abcd
two=efgh
three=ijkl
four=mnop

Listing 5 – run example Ant script

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

init:
     [copy] Copying 1 file to C:\temp\ant\propMerge

merge:
[propertyfile] Updating property file: C:\temp\ant\propMerge\oldProps.properties

list:
     [exec] one=abcd
     [exec] #four=4444
     [exec] two=efgh
     [exec] five=5555
     [exec] three=ijkl
     [exec]
     [exec] four=mnop

all:

BUILD SUCCESSFUL
Total time: 1 second

We actually save these files as templates, so that as shown in the init task of listing 6, we can recreate the test files. Then in target ‘list’, we print the modified old properties file.

Listing 6 – Ant script for dev of solution

<project name="merge" default="all" basedir=".">
  <!-- File: build.xml, 
       author: J.Betancourt, 
       Date:2011-08-17T20:59-0500 -->

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

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

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

  <scriptdef name="propertiesreplace" 
      language="groovy" classpathref="libs" 
        uri="http://com.jbetancourt1.ant">

  <attribute name="oldfile"/>
  <attribute name="newfile"/>

    <![CDATA[
    def ant = new AntBuilder(project)
    ant.propertyfile( file:attributes.get("oldfile") ){
      def nps = new Properties()
      nps.load(new File( 
        attributes.get("newfile")).newReader())
        nps.each{ cur ->
         entry( key:cur.key,value:cur.value )
      }
     }
  ]]>
</scriptdef>
	
	<!-- use the "propertiesreplace" scriptdef -->
	<target name="merge" depends="init" 
                       xmlns:s="http://com.jbetancourt1.ant">

		<s:propertiesreplace oldfile="oldProps.properties" 
                       newfile="newProps.properties"/>		
	</target>

	<target name="all" depends="init,merge,list"/>		
	
	<target name="init">
		<copy file="oldProps.properties-template" 
                  tofile="oldProps.properties"/>
	</target>	
	
	<target name="list">
	  <exec executable="cmd.exe" >
	    <arg line="/c type oldProps.properties"/>
	  </exec>	
	</target>	
	
</project>

Why version control?
I code this up, then I decided to share it for anyone who may appreciate some examples (and for my own dev notes). I edit a file. It stops working. Can’t figure out why quickly enough. My editor’s (notepad++) undo is not helping.

Fixed it, but to avoid this scenario again I do:
git init
git commit -a -m “initial commit”

Careful
Did you see the future human operator error waiting to happen? In the example, the modified properties file has two entries:
#four=4444
four=mnop

Lets say this file is, for example, involved in code controlling a 800 ton crane, and an admin removes comment mark from the first “four”. If the file is processed with Ant, the first ‘four’ entry wins. Control code is deployed and now crane is waiting to drop its crate at bad moment. A Road Runner coyote scenario. Beep beep.

Updates
2011-0819: Got stumped regarding exception invoking cvs task in Ant and losing all existing properties at the BuildFinished handler. Did a search and found that someone already created a merge properties task:
. Merging property files…
. A version with fixes
2012-04-02: It may be possible that the Groovy scriptdef taskdef already includes an AntBuilder.

Further Reading


Ant InputHandler GUI Using Groovy

August 16, 2011

Intro

With Apache Ant you can get input from the user using the “input” task. When you run Ant in a console window, the DefaultInputHandler is used. This handler just uses the console’s error and input streams.

When you run Ant in a graphical IDE, a Graphical User Interface based input handler may be used. This handler will use the graphics toolkit (on Eclipse that would be SWT) to pop up dialog boxes.


GUI in console?

Can you use GUI input dialogs when running in a command shell? Yes, and it is very easy. (Of, this is true if a gui environment is available to Java runtime, etc.).

First you create an InputHandler subclass that will be used with the Ant command option “-inputhandler class name”. If you use Ant 1.8* you can use a nested “inputputhandler” task in the “input” task within the build script.

Probably a much better approach is to just create a new custom Ant task, “ginput” for this, or even use a macrodef or scriptdef that uses Groovy inline or from a script file.

Another alternative is to use a browser interface for the Ant input. See “Java’s HTTP Server for browser-based Groovy app” for an example of using an embedded server to invoke browser UI.

Groovy?
Why Groovy for this? Just to be able to use the SwingBuilder that potentially could make the Gui creation much simpler. One can envision a full blown form being used for cases where Ant is used outside its traditional use-case.

GUI InputHandler subclass

Below in listing 1, there is an example Groovy source: AntGuiInputHandler.groovy

Much harder is actually getting Ant to use this handler.

Build file

First lets create an Ant build file that will get some input:

<project name="AntGui" default="all" basedir=".">
<target name="all">

    <input message="What is your name?"
       addproperty="got.response"/>		
    <echo>${got.response}>/echo>		
</target>

</project>

Compiling
Now, compile the Groovy source as follows:

groovyc AntGuiInputHandler.groovy

Running
There are many ways to actually run the Ant build. The simplest but not the best way is to set up your CLASSPATH and then run Ant as usual, for example:

C:\temp\AntGui&gt;set classpath=.;\java\groovy\embeddable\groovy-all-1.8.1.jar

C:\temp\AntGui&gt;ant -inputhandler AntGuiInputHandler
Buildfile: C:\temp\AntGui\build.xml

all:
[input] prompt=What is your name?
[input] Josef
[echo] Josef from Groovy InputHandler

BUILD SUCCESSFUL
Total time: 11 seconds

The resulting input dialog is:

Ant GUI InputHandler prompt

Listing 1
Note: I currently have no Swing mojo, so this is just reuse of code snippets. BTW, SwingBuilder could use more docs or examples.

import java.awt.event.WindowEvent
import javax.swing.ActionPropertyChangeListener;

import groovy.swing.SwingBuilder;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.input.InputHandler;
import org.apache.tools.ant.input.InputRequest;
import org.apache.tools.ant.input.MultipleChoiceInputRequest;

import javax.swing.*

/**
 * @author jbetancourt
 *
 */
class AntGuiInputHandler implements InputHandler {

	/* (non-Javadoc)
	 * @see org.apache.tools.ant.input.InputHandler#handleInput(org.apache.tools.ant.input.InputRequest)
	 */
	@Override
	public void handleInput(InputRequest request) throws BuildException {
		def response = ''
		println "prompt=${request.getPrompt()}"
		
		if(request instanceof org.apache.tools.ant.input.MultipleChoiceInputRequest){
			response = 	getMultipleChoiceInput(request)
		}else{
			response = getTextInput(request)
		}
		
		println response
		request.setInput("${response} from Groovy InputHandler");
	}
	
	String getTextInput(InputRequest request){
		//def swingBuilder = new SwingBuilder()
		def response = ''
                // how to do this with the builder?
		response = JOptionPane.showInputDialog(
                   null, 'Project Name','Enter name', JOptionPane.OK_OPTION)

		return response	
		
	}
	
	
	String getMultipleChoiceInput(InputRequest request){
		def swingBuilder = new SwingBuilder()
		def req = (MultipleChoiceInputRequest)request;
		
		def choices = req.getChoices()
		def defaultValue = req.getDefaultValue()		
		def prompt = request.getPrompt()
		
	    def pane = swingBuilder.optionPane(
                  message:prompt, 
                  selectionValues:choices as Object[], 
                  optionType:JOptionPane.CLOSED_OPTION)
		def dialog = pane.createDialog(null, 'dialog')
		dialog.show()
		
		def response = pane.getInputValue()
		
		println "input: ${response}"		
		
		return response
	}
}

Pretty groovy use of Groovy!

Previously posted at: “Ant InputHandler GUI Using Groovy

Updates
2011-08-16T2159: Just stumbled upon the AntForm project. If I knew about it, I wouldn’t even have tried doing the above. Oh well.


Follow

Get every new post delivered to your Inbox.