Enhance your build process with Groovy plus Ant If you’ve ever wished for the command of a programming language to drive your build process, then you need to know about Gant. In this article Klaus P. Berg introduces this smart combination of Groovy and Ant and shows you why some Java developers are choosing it as a more expressive alternative for describing complex build logic.Apache Ant is a popular open source tool for automating build tasks. Like Maven, Ant is a de facto standard for creating platform-independent builds of Java applications. Besides providing the typical compile, zip, jar, javadoc, and junit tasks, Ant offers great support for file and directory manipulation. Despite all this power, Ant buildfiles are written in XML, which limits your options for task automation to a given set of XML tags. Although it is possible to use extensions like Ant-Contrib or handwritten Ant tasks to enrich this set, writing Ant buildfiles is an awkward job at best.In this article, I introduce Gant, a new breed of build tool built on the familiar foundation of Ant. Rather than using XML, Gant lets you describe your build process using Groovy scripts. Using a Java-runtime-based programming language to describe automated build tasks gives you many of the same advantages Martin Fowler lists for Rake, a build language based on Ruby. Gant essentially lets you access the full power of Groovy (and Java) any time you need it, without having to drop out of your build description language to do interesting things. For instance, you can easily use control structures to express build logic, and you can even weave Java code into your build script. Using Ant (and thus XML) means switching over to custom Ant tasks or third-party libraries to perform many of the tasks that make your build worthwhile. Another advantage to using Gant is the opportunity to practice writing Groovy scripts. As you’ll learn in this article, Groovy has a steep learning curve for Java developers — meaning that we can actually pick it up quite quickly. You’ll get your chance to see some simple Groovy scripts in this article, as I walk through the steps involved in migrating an Ant build to Gant. You’ll also learn why Gant is becoming a popular alternative for Java developers who need the descriptive power of a programming language to describe complex build tasks.Quick overview of AntWhile professional IDEs have built-in support to compile, run, and debug your applications, complex software projects require more. In Java enterprise development, especially, you are faced with managing all the intricacies of the application lifecycle — namely building, testing, deploying, and maintaining — potentially across multiple platforms, over long periods of time, and/or in a continuous integration environment. Thus, an automated build utility like make, Maven, or Ant (or, as you’ll see, Gant) is a practical requirement for most professional application development.Look into the heart of Ant’s automation capabilities and you will find an XML-based buildfile. It contains several XML tags (or tasks) that will make up your project. The basics of Ant have been covered elsewhere on JavaWorld, so I won’t go into them here. It is important that you have the following conceptual architecture of Ant in mind, however, as it carries over to Gant: Project: Each Ant buildfile contains one project. The project element can give your build activities a name and a description, and may define the default target and the base directory from which all path calculations will be done. Usually, one project corresponds to one build file, although it is possible to access elements of other projects.Target: A target is a set of Ant tasks to be executed. They may depend on other targets, but note that Ant’s depends attribute only specifies the order in which targets should be executed. It does not affect whether the target that specifies the dependency is executed, in the case that the dependent target does not (need to) run. Typical targets are clean, compile, javadoc, or jar. A target is only a container or a wrapper for tasks, which do the real work.Task: A task is a piece of code that can be executed. It can have multiple attributes or command arguments. Tasks have identifying names that must be unique inside one buildfile. Using Ant’s task plug-in mechanism, you can write your own Java tasks or use third-party tasks out of the box.Properties: A project usually has a set of properties that are made up of key/value pairs. They might be set in the buildfile by the property task, or outside Ant in a separate property file. Note that once a property has been set, its value cannot be changed; think of it as similar to the final String constant construct in Java.Ant is a command-line tool, so you will start your build by calling the Ant interpreter and specifying a buildfile (or using the default build.xml). Optionally you can address specific targets or create properties using Ant’s command-line parameters.Meet Gant: Ant’s Groovy new cousinGant is a shorthand term for “Groovy plus Ant.” Gant inventor Russel Winder describes Gant thus:A Groovy-based build system that uses Ant tasks, but no XML. That means Gant makes use of Groovy instead of XML to specify the build logic. A Gant build specification is a Groovy script and so can bring all the power of Groovy to bear directly, something impossible with Ant scripts. While it might be seen as a competitor to Ant, Gant uses Ant tasks for many of the actions, so Gant is really an alternative way of doing builds using Ant, but using a programming language rather than XML to specify the build rules.Poke around in the wide-open Internet and you will soon find the assertion that Gant is what Ant was meant to be like. Much as I appreciate Gant, I stop short of sharing this opinion. Rather, I would say that Gant is just a different way of doing Ant — with the keyword being just. I first encountered Gant when I wrote a midsize GWT application. I started out doing all the necessary preparation, compilation, copying, and other build tasks using Ant and Ant-Contrib. But as my build script grew more complicated, I found the Ant-Contrib control structures and property regex processing cumbersome. After reading a recommendation for Gant in Groovy in Action, I decided to try programming with the best of both worlds: Groovy + Ant = Gant. I liked the result so much that I relied on Groovy and Gant from the start for my next project.The Groovy in GantGant and GrailsThis introduction to Gant would not be complete without mentioning that Gant is used as the build system for Grails. Grails is an open source Web application framework that leverages the Groovy language and complements Java Web development. It aims to bring the “coding by convention” paradigm to Groovy, and is inspired by Ruby on Rails. You can use Grails as a standalone development environment that hides all configuration details, or to integrate your Java business logic. See the Resources section to learn more about Grails. While much of Gant will be familiar to users of Ant, you do need to know some Groovy syntax to use it. Groovy is an object-oriented programming and scripting language for the Java platform. Being a dynamic language, Groovy has features like those of Perl, Ruby, and Python. But Groovy sources are automatically compiled to Java bytecode that works seamlessly with your own Java code or third-party libraries. With the Groovy compiler, you can also produce bytecode for other Java projects. The integration between Groovy and Java code is thorough and smooth. As a result, it has often been said that Groovy has a steep learning curve for Java developers, meaning that we can learn it very quickly.In Gant, Groovy is tightly integrated with Ant by way of AntBuilder. Gant basicsA separate Gant distribution is available for every major version of Groovy including the recently released Groovy 1.5. Installing Gant and Groovy is easy — hardly more than extracting a .zip file and setting some environment variables.Gant relies on Groovy, so you will need some basic Groovy know-how before you start writing Gant scripts:A Gant script is essentially a Groovy script that contains target definitions, calls to a predefined AntBuilder, and operations on some other predefined objects.A Gant target is the Gant equivalent of an Ant target. It consists of a Groovy code sequence that has a name and an associated description, like this: target (target-name : target-description) <b>target-closure</b> The target-description string is used as output when executing gant -p to discover the list of allowed command-line targets. The string must always be present. If it is the empty string, however, the task will not be treated as a target callable from the command line, and so will not appear in the target listing. Only targets with non-empty descriptions are treated as callable targets. The target-closure can contain any legal Groovy code.As with its Ant equivalent, you can make your target depend on other targets, but unlike Ant, the dependency is not specified as a property. Moreover, the depends method is an executable method and can therefore be called anywhere in a target closure, like so: target (myTarget : 'A target called myTarget.') depends (anotherTarget) // this statement may also be placed elsewhere in the closure code } Gant author Russel Winder advises us not to use Groovy GStrings as target names (although you could) because GStrings are evaluated only at runtime. That means that if variable bindings have changed, the target name you might think you have is not the target name you actually have. Usually, you do not need the flexibility of GStrings here, so it’s probably wise to follow Russel’s hint.Default target: The default target is the target that should be used if none is specified on the command line. With Ant, we would define the default target in the project tag, but with Gant, no such project tag is available. So, we define the default target as a special target itself: target ('default' , 'The default target.') {aTarget ( )} You are not required to use this “long” form of setting a default target, however. Instead, Gant comes with a handy shortcut: you can just use setdefault (aTarget). Gant even has the feature of a Maven target set. I haven’t used it, though, so I will not speculate on how well it works 😉Available Ant tasks: You can use all the Ant core tasks within Gant because each Groovy installation ships with Ant (Ant 1.6.5 comes with Groovy 1.0, and Ant 1.7.0 with Groovy 1.5). The optional Ant JUnit task is included in both versions as JUnit 3.8.2. For all other optional tasks, you need to perform some special action as described on the Gant homepage.The default name for the Gant script is build.gant, in the same way that the default for an Ant build script is build.xml. You can call gant with the -h option to get help information, as shown in Listing 1. Listing 1. Help information in Gant>gant -h usage: gant [option]* [target]* -c,--usecache Whether to cache the generated class and perform modified checks on the file before re-compilation. -n,--dry-run Do not actually action any tasks. --classpath <path> Adds a path to search for jars and classes. -D <name>=<value> Define <name> to have value <value> Creates a variable named <name> for use in the scripts and a property named <name> for the Ant tasks. -T,--targets Print out a list of the possible targets. -V,--version Print lots of extra information. -f,--gantfile <build-file> Use the named build file instead of the default, build.gant. -h,--help Print out this message. -l,--gantlib <library> A directory that contains classes to be used as extra Gant modules, -p,--projecthelp Print out a list of the possible targets. -q,--quiet Do not print out much when executing. -s,--silent Print out nothing when executing. -v,--verbose Print lots of extra information. Gant in practiceNow that you’ve learned the basics of Gant we can start playing around with it. The main purpose of this introduction is to guide you through the tasks involved in switching from Ant to Gant. If you are familiar with Ant and its syntax for declaring targets and tasks, then you can use the following procedure to migrate your build process from Ant to Gant:Define your Gant properties in a build.properties file.Access these properties as Groovy variables.Transform all other Ant properties into Groovy variables.Define your classpath variables, if needed, for subsequent processing.Define targets that access the properties and the classpath variables (these will do the real work).Transform Ant targets into Gant targets.Map Ant task method names to method names of the AntBuilder instance that is predefined by Gant.Map Ant task XML attributes to method parameters; if you have embedded elements, they must be transformed to a Groovy closure (using { } as the border).In all places where the Ant script uses strings as attribute values for other data types (for example boolean) you can insert the correct type directly; that means writing debug: true instead of the debug="true" used in XML.We’ll start with Steps 1 through 3: Reading Ant properties from a file, providing and accessing properties on the command line, and accessing a target’s description. The examples here are taken from the build.gant script found in the JavaWorld article “Client-side WSDL processing with Groovy and Gant.” You can also download them from this article’s code archive.Reading Ant properties from a fileUsually, you’ll have to customize your Gant script with some properties defined in a build.properties file that will reside at the same location as your build.gant script does. This property file contains key/value pairs and follows the ordinary rules for property file handling in Java. That means you can use the “#” sign in the first column to start a comment line. The procedure I recommend for reading property files is shown in Listing 2. Listing 2. Reading properties from a fileAnt.property(file: 'build.properties')def antProperty = Ant.project.properties // define a "shortcut" // now you can access a property, e.g., using a Groovy GString notation; // this property can even be an Ant property defining the GROOVY_HOME environment variable; // GROOVY_ADD_ONS is used in classpath definitions later on private static final GROOVY_ADD_ONS = "${antProperty.'groovy.home'}/../groovy-add-ons" // def wsdlRootDir = new File(antProperty.'wsdl.root.dir') You will notice that these properties can be accessed even using a “.” as name separator, although pure Java would not allow such variable names. The statement antProperty.'wsdl.root.dir' is a Groovy beans property access, where the property name is allowed to have dots inside when accessed using the ‘ …’ notation.The corresponding build.properties file is shown in Listing 3.Listing 3. Gant configuration using build.properties (excerpt)# Property file to customize the Gant script for WSDL processing. # ------------------------------------------------------------------------------ # Tell Gant which WSDL checker you have installed # ------------------------------------------------------------------------------ axis.available=yes xfire.available=yes cxf.available=yes wsimport.available=yes # ------------------------------------------------------------------------------ # Axis2 information # ------------------------------------------------------------------------------ axis2.install.dir=./ThirdPartyTools/axis2-1.3 axis2.lib.dir=${axis2.install.dir}/lib axis2.version=1.3 ... # ------------------------------------------------------------------------------ # Javadoc information # ------------------------------------------------------------------------------ javadoc.enabled=yes javadoc.packageNames=com.*,tools.*,org.*,net.* project.copyright=Copyright © 2007 - Mycompany.com # ------------------------------------------------------------------------------ # Optional proxy information # ------------------------------------------------------------------------------ proxy.enabled=no proxy.host=myProxyHost proxy.port=81 Providing properties on the command lineRemember that you can define new properties for Ant by providing them on the command line. The format would be ant -Dproperty1=value1 -Dproperty2=value2 ... If you need some optional parameters to control your Gant script execution you can set them as properties also. The syntax is quite similar but note that for Windows you will need a slightly different syntax:>gant -D "property1=value1" -D "property2=value2" and so on. (I suspect that this is due to the Windows gant batch file.) Gant will create these properties for you, if they are provided on the command line. I recommend code like what you see in Listing 4 when accessing such a property.Listing 4. Accessing properties provided on the command linetarget (init: '') { if (!antProperty.'axis.available'.equals('yes')) { Ant.fail(message: "Installation/configuration error: you must install and enable at least 'Axis2'!") } try { // referencing 'wsdl' in this try block checks for the existence of this property; // if you call Gant with '>gant -D "wsdl=<wsdl_target'>" then the variable 'wsdl' // will be created by Gant for you, otherwise the catch block will be executed // process web service WSDL files def wsdlRootDir = new File(antProperty.'wsdl.root.dir') def wsdlList = [] ... if (shortname ==~ wsdl) {...} // <<< wsdl property access here! } catch (MissingPropertyException e) { // no 'wsdl' property has been provided on the command-line // do whatever will be necessary here: if the property is optional: perhaps do nothing } catch (Exception ex) { // handle all other exceptions here ex.printStackTrace() throw new RuntimeException(ex) } } Accessing a target’s descriptionEvery target has to have a description, even if it is empty (which has a special meaning). You can access this target description string by referencing a variable that has the same name as your target, followed by the fixed suffix _description, as shown here: target (createJars: 'description') { println createJars_description } Writing Gant targetsWe’re now ready to get into the meat of any automated build process, which is writing tasks and targets. I’ve mentioned that Gant targets are Groovy closures. Inside closures you can define new closures and assign them to variables, but you are not allowed to declare a method. So, the following is allowed:target ( ) { def Y = { } // closure definition is OK here } but this isn’t:target ( ) { def Y ( ) { } // this is wrong: a method definition is NOT allowed here } With these hints in mind you can start writing your own targets, or use predefined Gant targets like clean, shown here: includeTargets << gant.targets.Clean cleanPattern << [ '**/*~' , '**/*.bak' ] cleanDirectory << 'build' Listing 5 shows a default target that prints out your automation task’s usage information when called on the command line, like this: ">gant“.Listing 5. A default target that prints out usage informationprivate static final String NL = System.getProperty("line.separator") private static final String SHOW_INSTALLED_FRAMEWORKS_TARGET_DESCRIPTION = "checks your build.properties settings for available frameworks" private static final String WSDLS_TARGET_DESCRIPTION = "prints all wsdl files with their target endpoints, together with wsdl file 'shortnames' to be used by other targets regex" private static final String DIFF_TARGET_DESCRIPTION = "show differences between two wsdl directories" private static final String JAVAGEN_TARGET_DESCRIPTION = "generate java code from wsdl, compile, provide javadoc, and generate necessary jar/zip files" private static final String CHECK_TARGET_DESCRIPTION = "check one or more wsdl files for compatibility with installed code generators & validator" private static final String VALIDATE_TARGET_DESCRIPTION = "validate one or more wsdls files using the CXF validator tool (if CXF is installed)" private static final String ALLJARS_TARGET_DESCRIPTION = "generates a directory with all Axis2 client jars, src/javadoc-ZIPs, and xsb resource files if xmlbeans is used" private static final String NAMESPACE_TO_PACKAGE_MAPPING_TARGET_DESCRIPTION = "prints a namespace-to-package mapping for a wsdl file" target ('default': 'print gant usage') { println """ USAGE: gant available ($SHOW_INSTALLED_FRAMEWORKS_TARGET_DESCRIPTION) gant wsdls ($WSDLS_TARGET_DESCRIPTION) gant diff ($DIFF_TARGET_DESCRIPTION) gant [-D "wsdl=<wsdl_shortname_regex>"] javagen ($JAVAGEN_TARGET_DESCRIPTION) gant [-D "wsdl=<wsdl_shortname_regex>"] check ($CHECK_TARGET_DESCRIPTION) gant [-D "wsdl=<wsdl_shortname_regex>"] validate ($VALIDATE_TARGET_DESCRIPTION) gant [-D collect] alljars ($ALLJARS_TARGET_DESCRIPTION) gant -D "wsdl=<wsdl_shortname_regex>" [-D "replace=<old>,<new>"] ns2p ($NAMESPACE_TO_PACKAGE_MAPPING_TARGET_DESCRIPTION) Produced listings in your 'results' directory: output-<tool>.txt & error-<tool>.txt with infos/errors/warnings for the code generation """ } From Ant to GantIn this section I present two examples that demonstrate the migration from Ant to Gant. Listing 6 shows many typical Ant tasks expressed using simple Groovy “scriptlets.” These tasks include Ant.path, Ant.javac, Ant.javadoc, Ant.copy, Ant.zip, Ant.jar, and Ant.delete. The sample also includes fileset definitions and a “depends” call, all highlighted in bold.Listing 6. Typical Ant tasks expressed in Gant... other definitions here long startTime = System.currentTimeMillis() Ant.property(file: 'build.properties') def antProperty = Ant.project.properties // introduce a shortcut for further access private static final GROOVY_ADD_ONS = "${antProperty.'groovy.home'}/../groovy-add-ons" target (javagen: "$JAVAGEN_TARGET_DESCRIPTION") { depends(init) if (wsdlRegexProvided && !wsdlRegexOK) { return } println javagen_description println "using data binding: ${antProperty.'axis.data.binding'}" new File("${antProperty.'output.axis.file'}").delete() new File("${antProperty.'error.axis.file'}").delete() def modifyAxis2Client = { def javaSrcDirPath = new File(it) javaSrcDirPath.eachFileRecurse{ if (it.isFile() && (it.name.endsWith('Stub.java'))) { println() print "${NL}Modifying $it.name ..." tools.webservices.wsdl.stubutil.ModifyingJavaParser.main(it.absolutePath) println ' Done.' } } } def compileAxis2Client = { jarfileName, srcDir -> def classfileDir = srcDir-"/src" + '/classes' new File(classfileDir).mkdir() def java_compile_classpath = Ant.path { pathelement(location: classfileDir) fileset(dir: GROOVY_ADD_ONS) { include(name: 'commons-*.jar') include(name: 'japa.jar') } fileset(dir: antProperty.'axis2.lib.dir') { include(name: '*.jar') } } Ant.javac( destdir: classfileDir, srcdir: srcDir, classpath: java_compile_classpath, debug: true, source: '1.5', target: '1.5', failonerror: 'no' ) def destDir = srcDir-"/src" Ant.zip(zipfile: "${destDir}/${jarfileName}-src.zip", basedir: srcDir) Ant.jar(destfile: "${destDir}/${jarfileName}.jar", basedir: classfileDir) if (antProperty.'axis.data.binding'.equals('xmlbeans')) { def resourceDir = srcDir-"/src" + '/resources' Ant.jar(destfile: "${destDir}/${jarfileName}-res.jar", basedir: resourceDir) } } def generateJavaDocumentation = { serviceName, srcDir -> def classfileDir = srcDir-"/src" + '/classes' Ant.copy(todir: srcDir) { fileset(dir: "${antProperty.'java-parser.install.dir'}/src") { include(name: 'tools/webservices/wsdl/stubutil/*.java') exclude(name: 'tools/webservices/wsdl/stubutil/Modifying*.java') } } Ant.copy(todir: classfileDir) { fileset(dir: "${antProperty.'java-parser.install.dir'}/classes") { include(name: 'tools/webservices/wsdl/stubutil/*.class') } } def javadocsDir = srcDir-"/src" + '/javadocs' new File(javadocsDir).mkdir() def javadoc_classpath = Ant.path { pathelement(location: classfileDir) fileset(dir: antProperty.'axis2.lib.dir') { include(name: '*.jar') } fileset(dir: GROOVY_ADD_ONS) { include(name: 'commons-*.jar') include(name: 'japa.jar') } } Ant.javadoc( destdir: javadocsDir, author: true, version: true, source: '1.5', packagenames: antProperty.'javadoc.packageNames', windowtitle: "Axis2 client - Version ${antProperty.'axis2.version'}", doctitle: "Axis2 client (Version ${antProperty.'axis2.version'}) for service $serviceName", nodeprecated: true, bottom: antProperty.'project.copyright', sourcepath: srcDir, classpath: javadoc_classpath) def destDir = srcDir-"/src" Ant.zip(zipfile: "${destDir}/${serviceName}-doc.zip", basedir: javadocsDir) Ant.delete(dir: "$destDir/src/tools") // delete the japa src part Ant.delete(dir: "$destDir/classes/tools") // delete the japa bytecode part } def processOneFile = { filename, wsdlFile, javaSrcDir, wsdlPackage -> println() println 'processing file: ' + filename generateJavaCode(wsdlFile, javaSrcDir) if (Ant.project.properties."taskResult_$wsdlFile" == '0') { modifyAxis2Client(javaSrcDir + '/' + wsdlPackage.replace('.', '/')) compileAxis2Client(filename-'.wsdl', javaSrcDir) if (antProperty.'javadoc.enabled' == 'yes') { generateJavaDocumentation(filename-'.wsdl', javaSrcDir) } } else { println " ERROR: cannot generate Java code for file $filename" } } ... long endTime = System.currentTimeMillis() def duration = Math.round((endTime - startTime) / 1000.0f) // in seconds println() println("GANT PROCESSING SUCCESSFULL: $duration seconds") } Listing 7 shows a more complicated Gant task, junit, and its transformation from XML into HTML with junitreport. Listing 7. junit as a Gant task... other definitions here def excludefile = new File("$eclipseProjectDir/.excludes") Ant.junit( printsummary: 'withOutAndErr', fork: 'yes', dir: eclipseProjectDir , errorproperty: "junitResult_$eclipseProjectDir", failureproperty: "junitResult_$eclipseProjectDir") { classpath { pathelement(location: classfileTempDir) pathelement(location: base_soap_classfileDir) pathelement(location: ftsConfigDir) pathelement(location: dist_client_dir) fileset(dir: GROOVY_ADD_ONS) { include(name: 'commons-*.jar') include(name: 'japa.jar') } fileset(dir: antProperty.'axis2.lib.dir') { include(name: '*.jar') } // more fileset definitions here } formatter (type: 'xml') batchtest (todir: reportDir) { fileset(dir: srcDir) { include(name: '**/*Test.java') if (excludefile.exists()) { out("excluding tests: ${excludefile.getText()}") excludesfile(name: excludefile) } } } } if (Ant.project.properties."junitResult_$eclipseProjectDir" == null) { out('TEST OK') } else { out('TEST ENDED WITH ERRORS/FAILURES') } System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl"); Ant.junitreport(todir: "${antProperty.'fts.reports'}") { fileset(dir: "${antProperty.'fts.reports'}") { include(name: 'TEST-*.xml') } report(format: 'frames', todir: "${antProperty.'fts.reports'}") } Some highlights of using GantOne thing you might notice in Listing 7, in the line before the Ant.junitreport call, is how easily I could set the necessary TransformerFactory to run the code with Java 5. A big advantage of using Gant over Ant is the ability to seamlessly mix Ant calls with normal Java or Groovy statements like foreach loops, if-then-else structures, or regular expressions. And you can do this without losing any of the power of Ant!Gant also lets you create modular build scripts with methods and closures that re-use your own components or third-party software. In this regard it is possible to design a smart Gant script much like you would design a Java program. In Listing 8 you can see an example Gant script that interleaves Ant parts with Java program logic to set necessary proxy values as jvmargs and the like.Listing 8. Mixing Ant calls with Groovy and/or Java logic<code>Ant.java</code><code>(classname: 'org.apache.axis2.wsdl.WSDL2Java', classpath: java2wsdl_classpath, fork: true, output: "${antProperty.'output.axis.file'}", error: "${antProperty.'error.axis.file'}", append: "yes", resultproperty: "taskResult_$wsdlFile") { if (antProperty.'proxy.enabled'.equals("yes")) { println "Using proxy ${antProperty.'proxy.host'}:${antProperty.'proxy.port'}" jvmarg (value: "-Dhttp.proxyHost=${antProperty.'proxy.host'}") jvmarg (value: "-Dhttp.proxyPort=${antProperty.'proxy.port'}") } arg (value: '-uri') arg (value: wsdlFile) arg (value: '-d') arg (value: "${antProperty.'axis.data.binding'}") arg (value: '-o') arg (value: outputDir) arg (value: '-p') arg (value: "${antProperty.'stubs.package.prefix'}.$stubPackageSuffix") arg (value: '-u') arg (value: '-s') if (new File("$wsdlFileDir/ns2p.properties").exists()) { println 'using provided namespace-to-package mapping per wsdl file' arg (value: '-ns2p') arg (value: "$wsdlFileDir/ns2p.properties") } else if (antProperty.'ns2p.replace'.equals("yes") && ns2pValues.length() > 0) { println 'using provided namespace-to-package mapping for ALL wsdl files that should be processed' arg (value: '-ns2p') arg (value: ns2pValues) // ns2pValues was calculated elsewhere } arg (value: '-t') }</code> Another highlight of using Gant is that targets are available as commands for the user, and can be combined by first supplying the (optional) set of properties, then providing the set of targets, for example: >gant -D "wsdl=GlobalWeather" -D collect javagen alljars As you might have noticed, it’s even possible to provide a property key without a value, if the value is not necessary for your next steps.Gant also can make use of Groovy’s curry feature. Currying is a technique that takes a function with multiple parameters and transforms it into a function with fewer parameters, by fixing some of the values. This technique, and its name, has its roots in functional programming. Listing 9 shows a smart print utility that I have used all over the place to get homogeneous text outputs.Listing 9. Currying reduces your coding effort<code>def println2 = {s1, s2 -> println s1 + ' ' + s2} def out = println2.curry('--->')</code><code>// now I can easily call out() instead of println() // to present intermediate results and info to the user in a predifined way if (shortname ==~ wsdl) { out("Provided regex matched '$shortname'") // will produce "--->Provided regex matched ...'<some name>'" wsdlFilesMatchingRegexList << wsdlFile atLeastOneMatchingWsdlFileFound = true return; }</code> Gant development with IDEsUnfortunately, IDE support is a real sore point when working with Groovy and Gant. Because Gant scripts are Groovy scripts, you can look for Groovy plugins to enrich your favorite IDE, like Eclipse, NetBeans or IDEA. I have only worked with the Eclipse Groovy plugin that runs with both Eclipse 3.2 and 3.3. The plugin works very well where it claims to work, but when in comes to refactoring, Eclipse JDT and the Groovy support are poles apart from each other (partially, this may be due to the dynamic nature of the Groovy language). I use the Eclipse Groovy plugin to develop Groovy classes, but for Gant I use a text editor like Notepad++ or UltraEdit. Refactoring Gant scriptsDespite the drawbacks of having no satisfactory tool support for refactoring in the Eclipse IDE you should clean up your (growing) Gant scripts in the same way, and with the same accuracy, as you do your Java code. That means applying some of Martin Fowler’s basic refactorings, such asExtract methodExtract local variableRename methodRename fieldReplace magic number with symbolic constantActually doing all this can be quite hard with only a text editor, but is still essential to maintaining smooth builds over time.Most importantly, if you see your script becoming too large to be managed and maintained, or if you have developed some reusable targets, extract them as a separate build file and include them in your main script. For instance, let’s assume you have developed a target demo and it is saved in the file build_extensions.gant (which is located in the same directory where your main script, build.gant resides). You could include this target by using the following line in your main script:includeTargets << new File('build_extensions.gant') where build_extensions.gant contains /** * GANT build script with extension target (demo). */ target (demo: 'echo fixed message (demo only)') { Ant.echo ( message : "Did something for demo purpose." ) } The additional target demo will also appear in your target list when you type >gant -p on the command line. Please note that you cannot name your added script build-extensions.gant because this name would form a Groovy classname, and the "-" character is not a valid part of classnames.In conclusionWhenever you have to perform automation or build tasks you should look for the right tool to do the job. If you are happy with Maven, then no need to change; if you are happy with Ant, and you assume your Ant script is still maintainable, then no need to change. On the other hand, I agree with the view Graeme Rocher presented in a talk about Grails: Ant is a great tool for simple or medium size builds that do not need complex logic, but “as soon as your build needs any kind of logic, the cracks start to show.”Most people I know who use Ant also use Ant-Contrib, a collection of tasks for Ant that allow for the use of conditional and for-loop logic. It’s a good short-term fix, but once you acquire a taste for Groovy and Gant, you will not want to go back to Ant-Contrib. Gant gives you all the power of Ant’s rich task set, plus the flexibility to use Groovy (and Java) to describe your build logic.You also needn’t shrink from learning Groovy to use Gant. Java developers can pick up Groovy quite quickly, and besides you don’t really need to understand Groovy in detail to use Gant. A little Groovy plus everything you already know about Ant goes a long way toward learning and using Gant.I recommend it.Klaus P. Berg has a master’s equivalent (Diplom) in electrical engineering and applied informatics from the University of Karlsruhe in Germany. He was an architect and implementor for projects at Siemens focused on Java GUI development with Swing and Java Web Start as well as acting on the server side creating Java-based intranet applications. Now he works as a senior engineer in the area of software quality, functional and performance testing, mainly for J2EE software. Open SourceSoftware DevelopmentBuild AutomationJavaAgile Development