Build software with Gant

Flexible builds are easier with the marriage of Groovy and Ant

Gant is a highly versatile build framework that leverages both Groovy and Apache Ant to let you implement programmatic logic while using all of Ant's capabilities. In this tutorial, Andy Glover guides you step-by-step through Gant's fundamental concepts. You'll learn how to define behavior in your build through Gant's flexible domain-specific language, how to reuse Ant features, and how to define functions that make your builds more efficient and even proactive.

Share:

Andrew Glover, President, Stelligent Incorporated

Andrew Glover is president of Stelligent Incorporated, which helps companies address software quality with effective developer testing strategies and continuous integration techniques that enable teams to monitor code quality early and often. Check out Andy's blog for a list of his published work.



27 May 2008

Also available in Chinese

Before you start

About this tutorial

In this tutorial, you'll get to know Gant, a build framework that leverages both Groovy and Apache's Ant to produce a highly versatile medium that lets you implement programmatic logic while reusing all of Ant's functionality.

Objectives

This tutorial guides you step-by-step through the fundamental concepts of Gant. You'll learn how to:

  • Define behavior in your build through Gant's flexible domain-specific language (DSL)
  • Reuse Ant
  • Define functions that enable reuse and even proactivity within your builds

When you are done with the tutorial, you will understand the benefits of using Gant for building software, and you'll be set to start using Gant in your everyday Java™ development.

Prerequisites

To get the most from this tutorial, you should be familiar with Java syntax and the basic concepts of object-oriented development on the Java platform. You should be familiar with Ant as well. In addition, it is highly recommended that you read the tutorial "Fluently Groovy" also by Andrew Glover.

System requirements

To follow along and try out the code for this tutorial, you need a working installation of either:

You'll also need to install Groovy and Gant. The tutorial text includes detailed instructions for those installations.

The recommended system configuration for this tutorial is:

  • A system supporting either the Sun JDK 1.5.0_09 (or later) or the IBM JDK 1.5.0 SR3 with at least 500MB of main memory
  • At least 20MB of disk space to install the software components and examples covered

The instructions and examples in the tutorial are based on a Microsoft® Windows® operating system. All the tools covered in the tutorial also work on Linux® and UNIX systems.


The challenges of Ant

Ant is the de facto standard build solution for the Java platform. Although alternatives such as Maven and Maven 2 are available, no build solution has as much developer mindshare as Ant. JUnit, PMD, Tomcat, and many other open source and even commercial tools all provide Ant tasks. Yet, Ant has its fair share of warts that can make certain chores challenging. The challenges stem from the fact that Ant is excellent at allowing you to tell it what to do, but not how to do it. This section explains the ways in which Ant lacks the flexibility that (as you'll learn in subsequent sections) Gant is designed to provide.

The need for flexibility

It's easy to use Ant for the simple task of deleting files within a series of directories. The simple target in Listing 1 deletes a series of files and directories recursively when invoked:

Listing 1. Simple Ant delete target
<target name="clean" description="deletes recursively through generated directories">
  <delete dir="${default.target.dir}" />
</target>

But, suppose you want more flexibility associated with deleting directories or files. For example, you might want to delete a file (or directory) only if it is more than three hours old. Although Ant facilitates using filters, which help narrow down specific file types (such as all .class files), it isn't necessarily good at decision logic that's not file-based.

This particular goal requires a bit of logic that's quite easy to implement if you approach the problem with Java code. For instance, Listing 2 defines a method that deletes any file that's older than the time passed in:

Listing 2. Java method for deleting files of a certain age
public static void deleteFiles(File directory, Date time){
 File[] files = directory.listFiles();
 for(int x = 0; x < files.length; x++){
  File ifl = files[x];
   if((ifl.isFile()) && (ifl.lastModified() < time.getTime())){
   ifl.delete();
  }
 }
}

As you can see in Listing 2, this isn't terribly hard to do, provided you understand the Java File API.

But translating this logic into Ant is no easy undertaking. The good news is that Ant provides several options for implementing more imperative-like logic. At least three high-level options are available to address the challenge of deleting particular files based upon time stamps:

  • Adding logic directly into the build file itself
  • Coding a custom task in the Java language
  • Scripting the required logic inside the build file with a scripting language

The bad news is that each choice has some drawbacks.

XMLing it the hard way

Ant's build files are implemented as XML documents. XML is excellent at describing what, which is known as declarative programming. But it's quite unsuited for describing how, or what's better known as imperative programming. Nevertheless, various developers have managed to provide some handy libraries to help overcome XML's unforgiving nature when it comes to imperative programming.

If you were daring and wanted to solve the file-deletion challenge in XML directly, you could try to add conditional logic directly to Ant via the Ant-Contrib project's if task (see Resources), as shown in Listing 3:

Listing 3. Adding conditional logic with Ant-Contrib if
<if>
 <equals arg1="${file.name}" arg2="temp.txt" />
 <then>
   <delete file="${file.name}"/>
 </then>
 <else>
   <echo message="Doing nothing" />
 </else>
</if>

Although this particular task (and associated XML) facilitates conditional logic, it is highly verbose and doesn't handle the meat of the problem — determining a file's timestamp easily. And looping is a whole other ball of wax. In fact, out of the box, Ant doesn't support looping. So, you must use another Ant-Contrib task, dubbed foreach, as shown in Listing 4:

Listing 4. Adding looping with Ant-Contrib foreach
<foreach param="file_var" target="delete_file">
 <path>
  <fileset dir="${src.dir}">
   <include name="**/*.class"/>
  </fileset>
 </path>
</foreach>

Not exactly easy.

Customization through inheritance

Another option for making Ant more flexible: You can always create a custom Ant task in Java code. That is, you can extend from Ant's Task object and code to the template pattern by implementing the execute method.

Although this technique works (and can actually be kind of fun), it is a lot of work, and the testing-through-deployment life cycle can be time-consuming. If you need to make changes, you can't exactly do them in Ant's build file; you must modify Java source code, recompile, and retest. The development community has come a long way in making this life cycle easier, but it still requires added steps because the customization (which can easily solve the delete challenge I've posed) is in another medium (the Java language).

Dynamic Ant

Last, but not least, you always have the option of implementing the delete logic in an Ant build file via the various tasks that facilitate using a scripting language, such as Ruby, Jython, or Groovy.

For instance, using the groovy Ant task that Groovy provides, you could add the code in Listing 5 to your Ant build file:

Listing 5. Using the groovy Ant task
<target name="delete-files" depends="groovy-init">
 <groovy classpathref="build.classpath" >
 new File("${properties.delete_dir}").eachFileRecurse{ ifl ->
  if((ifl.isFile()) && (ifl.lastModified() < 
     Long.parseLong("${properties.delete_dir_time}"))){
	ifl.delete()
  }
 }
 </groovy>
</target>

This works but is a bit of a stretch. Not only does the Ant build file contain a lot of XML, but now (via the groovy task) a different medium — Groovy code — is added to the mix. And notice how it is somewhat awkward to work with the properties that surround the imperative code. (Obtaining the date requires parsing the long value — it's fundamentally a String because of its inclusion as a property in Ant.)

It should clear by now: Ant wants to be flexible. It wants to do conditional logic and looping and extensibility easily but can't. But as you can infer, the problem isn't Ant itself, but XML. What's lacking is the flexibility to drop into an imperative programming paradigm — that is, leverage everything Ant has to offer in the way of tasks, but do it in normal code, not XML.

Gant to the rescue.


Gant: A giant leap forward

Gant is a build framework that leverages both Groovy and Ant to produce a highly versatile medium that enables programmatic logic while reusing everything available to Ant. Consequently, with Gant, you can easily implement logic that does fairly interesting things during software assembly. Although Gant is fundamentally Groovy, it's quite easy to pick up if you have any familiarity with Ant. In fact, Gant uses a Domain Specific Language (DSL) that appears a lot like an Ant target. This section walks you through the installations you need to perform to use Gant on your system.

Installing Groovy

To use Gant, you must have Groovy installed. Follow these easy steps to install Groovy:

  1. Download the latest version (1.5.4 as of this writing) from Groovy's Web site (see Resources).
  2. Unzip the archive to any location on your system. (You can be on any operating system; Groovy is ultimately platform agnostic because it's just Java code.)
  3. Add Groovy's bin directory to your path.
  4. Create an environment variable named GROOVY_HOME that points to the directory where you've installed Groovy.

Now you're ready to install Gant.

Installing Gant

Download the latest version of Gant (which, at the time of writing, was 1.1.1) and unzip it (see Resources).

Inside the created directory, you should see two subfolders: bin and lib. The lib folder should contain a few JAR files. One is a gant JAR containing the core of Gant. An ivy JAR and a maven-ant JAR contain handy utilities that, respectively, leverage Ivy for dependency management and let you use various Maven features within Ant (see Resources).

The bin directory should contain at least three scripts. The most important is the install.groovy script.

Run the install.groovy script now. The script copies the three JAR files in the lib directory, and the other two scripts in the bin directory, into Groovy's home. Now Groovy's bin directory contains two Gant scripts, and Groovy's lib contains the gant, ivy, and maven-ant JARs.

Firing it up

Gant is now a part of your Groovy installation. Fire up a Groovy console and type gant in the command line. You should see an error message that reads something like Cannot open file build.gant.


Getting started with Gant

Of course, to do something useful with Gant you need some Java code to build. This section gets you started working with Gant hands-on via a simple project that has a few source files and test files.

Project setup

The simple project you'll work with in the rest of this tutorial runs JUnit tests and uses PMD for source-code analysis (see Resources). The project has a src directory under the root project directory for storing source files and a test directory for storing test files. The lib directory contains a series of JAR files required for compiling JUnit tests and for running PMD.

This project lacks an automatic way to compile both source files and test files, run JUnit tests, and analyze the code base via PMD. Although you can implement this automation with Ant, you're going to do it with Gant. Along the way, you'll see that Gant makes these seemingly easy tasks even easier and ultimately more flexible.

Download the project files now and unzip the archive.

Gant's DSL

A Gant build script is a Groovy script. It is general practice to name Gant build files as build.gant (much as it's common practice to name Ant build files build.xml).

Create an empty file called build.gant in the root of the project. This is where you'll add tasks for assembling the project. But before you can do that, I'll take a few moments to help you understand Gant.

At a high level, a Gant file is a series of target definitions (though it's not limited to targets) that look like Listing 6:

Listing 6. Gant's target syntax
target(name:"description"){
 //do groovy code
}

Each target can have a name (such as compile or clean) followed by a description. This pattern matches quite nicely with Ant's target element, which also has a name attribute and optional description attribute. For instance (and as a refresher), Ant's model looks like Listing 7:

Listing 7. Ant <target/> syntax
<target name="compile" description="compiles source code">
   <!-- do ant stuff -->
</target>

As you can see, Gant trades < and > delimiters for { and }. Of course, the key to all of this is what's between the delimiters.

Any task that you can use in an Ant build file can be used in a Gant file. All you need to do is reference the task via the Ant. call and make sure the code for that task (if it's custom, such as the PMD task) is in your classpath.

For instance, if you'd like to use Ant's echo task in Gant, you can easily call it:

Ant.echo(message:"hello world")

Note how a pattern is emerging with respect to Gant vs. Ant. Ant's XML elements become method calls on the Ant object, and XML element attributes become name-value pairs. For instance, the echo call in Ant looks like this:

<echo message="hello world"/>

Comparing both Ant's XML DSL and Gant's Groovy DSL should reveal a simple pattern. And if you've ever played with Groovy's Builders (see Resources), you'll realize instantly that they are the core of what's at work with Gant. Accordingly, if you do find yourself with nested Ant tasks (which you'll see shortly), your top-level Gant task creates sibling closures that follow the same pattern.

Testing it out

Now that you've seen a simple Ant-to-Gant task transformation, go ahead and give Gant a spin. Open up your favorite editor (I prefer Eclipse; see Resources). In your build.gant file, add a target called echo, and inside the body of the target, add an echo call:

target(echo:"my first gant target"){
 Ant.echo(message:"hello world")
}

Save your file and then go back to the command line and type gant echo. If all goes well, you should see a simple message on your command line that looks like this:

[echo] hello world

This might not be terribly exciting, but once you understand this pattern, using Ant's myriad tasks in Gant becomes light work.


Gant in action

Now that you've mastered the basics of Gant, this section takes you to the meat of software builds for the project at hand. First and foremost, it is time to add two compilation targets: one for the source code and one for the testing code.

Project setup

To compile Java code with Gant, you need to use Ant's javac task. You must also specify where compiled code should reside. It's a common practice to place generated code into temporary directories within a project's root. For this tutorial, you'll place all compiled code into a directory named target. You'll further segregate that directory by function: you'll place compiled code (.class files) into a classes subdirectory under the target directory.

The process I've just outlined requires two steps. First, the target/classes directory must be created, and then javac must be invoked. If you've ever crafted an Ant build file, you've done this at least once. To refresh your memory and give you a model, Listing 8 shows what you'd do in Ant's XML file to achieve a successful compilation step (remembering that this doesn't assume the code itself compiles successfully):

Listing 8. Compilation tasks in Ant
<target name="compile">
 <mkdir dir="target/classes" />
 <javac srcdir="src" destdir="target/classes" />
</target>

The XML in Listing 8 uses two core Ant tasks: mkdir and javac. So translating this into Gant requires two calls to the Ant object. Remember to follow the element-attribute to name-value pattern you learned about in Getting started with Gant.

Taking the XML in Listing 8 and translating it into Gant yields the code in Listing 9:

Listing 9. Compilation steps in Gant
target(compile:"compiles source code"){
 Ant.mkdir(dir:"target/classes")
 Ant.javac(srcdir:"src", destdir:"target/classes")
}

So far so good — nothing complicated up to this point. Is there any room for refactoring, though? Can you improve this code? Remember, what you are looking at isn't XML anymore — it's living code. I'm willing to bet that if you saw this in a normal Java file, your first instinct would be to create a String constant to take the value of target/classes.

Project properties

In Ant, if you want to create a common value that can be referenced throughout a build file, you use Ant's property mechanism. As a refresher, Ant property definitions look like this:

<property name="default.target.dir" value="target" />
<property name="classes.dir" value="${default.target.dir}/classes" />

In this code, the target value can now be referenced as default.target.dir, for instance. This can be handy if the values are referenced more than once and have the chance of being changed, because the change has to happen in only one place.

Ant properties also have the facet of being immutable: they can't be changed once they are set. This is a handy feature and something to keep in mind as you use Gant. If you are observant, you're probably already thinking that because a Gant file is a Groovy script, you can do away with using Ant's property mechanism and just use plain old variables inside your script. That is correct! Keep in mind, however, that if you'd like those values to remain immutable, you'll need to make them final, just as you would in Java code.

Accordingly, in Gant you can replace the target/classes string with a constant that I'll call clzzdir:

Listing 10. Replacing directory string with a constant
final clzzdir = "target/classes"

target(compile:"compiles source code"){
 Ant.mkdir(dir:clzzdir)
 Ant.javac(srcdir:"src", destdir:clzzdir)
}

Notice that Listing 10 makes the clzzdir variable final. Keep in mind that naming patterns are different in Gant. For instance, the Ant properties in the previous Ant property definition use a . name pattern (default.target.dir, for example). Using this pattern in Ant is a helpful way to ascertain quickly that the string in an XML body is really a property. But Gant is Groovy, and Groovy is really just Java code, so the . pattern won't work. Nothing is stopping you from adopting your own pattern, though (such as underscore variable: _clzzdir, anyone?).

Chaining targets

A powerful Ant feature that you can also use with Gant is task chaining. Task chaining is the act of creating dependencies between tasks in Ant — for instance, between code compilation and test compilation. Test classes have an implicit dependency on source code. A JUnit test often imports the code under test, so before you can compile any JUnit tests, you must compile the code under test.

This is commonly done in Ant using a target's depends attribute; in Gant you do it slightly differently. Rather than augmenting the target call itself in Gant, you specify dependencies via the depends call within a target's body.

Using the depends clause is easy. Just reference it inside the body of a target and pass in one to many (separated by commas) other targets that must be called before the rest of the body executes:

target(name:"description"){
 depends(another, other)
 //do groovy code
}

And just as with Ant, when you specify a dependency within a target, that dependency is idempotent: the dependency is called only once even if it is referenced more than once.

But this is a Groovy file, and Groovy is Java, and Java code is a programming language, which isn't XML. This means flexibility: you can directly call dependencies as well — that is, you can just call other targets directly if you want to. Just remember, though, that directly calling a target obviates idempotent relationships.

For instance, the two targets in Listing 11 are linked via a direct call:

Listing 11. Linked targets
target(echo:"my first gant target"){
 echoAgain()
 Ant.echo(message:"hello world")
}

target(echoAgain:"my first gant target again"){
 Ant.echo(message:"hello world first!!")
}

Note the echoAgain call in the echo target. This means that before hello world is printed, hello world first!! is printed. Try it out for yourself.

Remember, idempotence within a build script is quite handy. I highly recommend you stick with the depends clause unless you have a good reason not to.

Default target

Ant supports the notion of default targets — targets that are called if no target is specified on the command line when Ant is invoked. This is often a nice feature because it can reduce keyboarding. If you find that the most-used target within an Ant build is the test one, then making that the default target means you don't need to pass in the test identifier via the command line.

Gant supports two mechanisms for specifying the default target. You can either use the default keyword in Gant when creating a target, or you can use the setDefaultTarget call within the body of a Gant script.

Using the default keyword in Gant is simple. You simply create a target whose name is default. Then, in the body of that target, use the depends call to force the default target. For example, this code makes the echo target the default target:

target(default:"echo"){
 depends(echo)
}

Optionally, you can use the setDefaultTarget call to specify a default target:

setDefaultTarget(echo)

You can use either mechanism. Just note that use of setDefaultTarget requires that it appear after the referenced target is defined. Using the default keyword doesn't impose this restriction.

Linking test compilation

Now that I've covered a bit more of Gant's features, it's time to link the implicit dependency between compiling test code (which relies on JUnit) with source code. But before you can compile JUnit tests, remember to put JUnit's JAR file in your classpath. Ant supports building paths quite nicely via the path element. So, using Gant, you need to create a path that includes JUnit's JAR, which is in the lib directory. You'll use other JARs in that directory later for PMD, so just create a path that includes everything within that directory, as shown in Listing 12:

Listing 12. Creating a path
Ant.path(id:"build.cp"){ 
 fileset(dir:"lib", includes:"**/*.jar") 
}

Now you're ready to compile the code that's in the test directory. Because this code uses JUnit and imports the code that's in the source directory (which, as you'll recall, is being compiled into the target/classes directory) two logical units must be on the classpath during test-code compilation: JUnit's JAR file and the class files from the compilation step.

The test-compilation step first needs to depend on the compile target. Then, in the javac Ant call, two paths need to be referenced. That is, the build.cp in Listing 13 references path from Listing 12 and the location of the generated class files:

Listing 13. Referencing the path and class-files location
target("compile-test":"compiles test source code"){
 depends(compile)
 Ant.mkdir(dir:"target/test-classes")
 Ant.javac(srcdir:"test", destdir:"target/test-classes"){
  classpath(){
   path(refid:"build.cp")
   path(location:clzzdir)
  }
 }
}

Note that the target's name is a String rather than a literal in this case. This is because of the - character. In Groovy, names in a Map cannot include a - character; however, if you'd like to use a -, you can just force the name to be a String by putting it inside "".

Note too how the javac Ant call is rather nested: a classpath call is followed by two path references. This is Groovy's Builders in action, and they work quite nicely.

Of course, at this point, there is more room for refactoring. Go ahead and pull out the target/test-classes String and replace it with a constant variable.


Adding behavior one function at a time

Because (as I've mentioned on more than one occasion) Gant scripts are Groovy scripts, and Groovy is basically Java code, Gant users are not limited to defining behavior inside of targets. You can define methods (or free-standing functions) as you see fit. This section shows you how.

Refactoring through functions

The fact that Gant is built on the highly dynamic and flexible Groovy language makes more options available to you in a Gant build file than those you've seen so far. You might be wondering if more refactoring is possible than what I've shown up to this point. You've factored out common Strings into variables, which you'd do anyway if this were a normal Java class. But can you do more?

Look again at the compilation targets and notice that they do basically the same thing: they both use Ant's javac to compile code. Why not put that code into a function — the same thing you'd probably do if this were a normal Java class?

Of course, the compilation targets differ slightly. Compiling test code (which relies on JUnit and the resultant compiled source code) requires path information, such as where to find JUnit's JAR file and where to find the compiled source-code classes. That's why the compilation code adds Ant's classpath structure. So it sounds like we should add some logic to use a path structure if needed.

Before you code the compilation function, you need to do two things. First, rename the original path id to something more reflective of what it is — a collection of libraries. This code changes the id to libs:

Ant.path(id:"libs"){
 fileset(dir:"lib", includes:"**/*.jar")
}

Next, it's time to create another path object that combines the libraries with the compiled source code that's in the target/classes directory (otherwise known as clzzdir, remember?):

Ant.path(id:"build.cp"){
 path(refid:"libs")
 path(location:clzzdir)
}

Defining two different path structures provides a nice degree of flexibility. For instance, when dealing with PMD, you won't need to work with the generated classes — just the libraries. Thus, you can reference a more precise path object that is more reflective of what's actually required (rather than a default one that takes everything into account).

Creating Gant functions

Groovy permits the creation of free-standing functions within a script file, and you can do the same in Gant files. Creating a function permits reuse and so is in line with what we all learned as we began do object-oriented programming. Because your build file uses Ant's javac twice, you create a function (called javac) that takes:

  • A directory containing source files as the first parameter.
  • A destination directory for generated class files to go as a second parameter.
  • The path reference as an optional parameter.

Note that Groovy permits optional parameters. That is, you can assign parameters a default value inline, as shown in Listing 14:

Listing 14. Assigning default parameter values
def javac(basedir, destdir, cpref=null){
 Ant.javac(srcdir:basedir, destdir:destdir){
  if(cpref != null){
   classpath(refid:cpref)
  }
 }
}

If the caller doesn't provide a value, the default value is used. As you can see in Listing 14, if cpref isn't null, then it is used.

Refacorting the targets

Now that you have a function within your Gant build file, you can refactor both the compile-test and compile targets to reference the newly defined javac:

Listing 15. Assigning default parameter values
target("compile-test":"compiles test source code"){
 depends(compile)
 Ant.mkdir(dir:tstclzzdir)
 javac("test", tstclzzdir, "build.cp")
}

Remember, compiling tests requires a classpath; accordingly, you must pass in the reference to the path structure that contains both the libraries and the compiled source files. Lastly, compiling source code doesn't require (at this point) any libraries, so the last parameter isn't required:

target(compile:"compiles source code"){
 Ant.mkdir(dir:clzzdir)
 javac("src", clzzdir)
}

As you can see, the ability to define functions within a build script is quite handy. If you've ever dug deeply into normal Ant, you know that Ant has a similar feature: macros. But if you've ever coded a macro, you'll undoubtedly find that Gant functions are more natural.


Logically happy in Gant

Recall the challenge in The challenges of Ant at the start of this tutorial: add some behavior to a build that deletes a particular file only if it is older than some timestamp. This section shows you how easy it is to code this logic with Gant.

Closures

Just as Gant supports the creation of free-standing functions within a build script, it also support the creation of closures. Closures are like free-standing functions in that they are essentially first-class objects. They can be passed around like other objects, and more important, they can be invoked at a later time.

Coding the delete logic from this tutorial's The challenges of Ant section becomes an exercise in creating a closure. (Remember, you could just as easily create a function too.) The closure takes as parameters the directory in which to scan for files and a time on which to base any decision about deleting files. Because you're working in Groovy, you get to do something nice. You can handle recursion quite easily and take advantage of Groovy's shortcuts associated with normal Java objects, as shown in Listing 16:

Listing 16. Using Groovy closures
def deleteAfter = { directory, time ->
 new File(directory).eachFileRecurse{ ifl ->
  if((ifl.isFile()) && (ifl.lastModified() < time.getTime())){
   ifl.delete()
  }
 }
}

Listing 16 creates a closure (named deleteAfter) that takes two parameters: a directory to scan and a time. Note how Listing 16 makes the directory object a normal Java File object and then recursively obtains each file and directory below the base (which is the directory parameter).

The rest of the logic should look extremely similar to that of the Java code example back in the The challenges of Ant section (see Listing 2), where I explain that Ant sometimes makes it difficult to do easy logic. In contrast, you've just seen how easy it is in Gant.

Now go ahead and create a new target that uses this logic. Listing 17 creates some logic to remove only files that are older than a day:

Listing 17. File-deletion logic
target(cleanDayOld:"cleans up dirs"){
 deleteAfter(clzzdir, (new Date() - 1))
}

Although this sort of specific logic might not be appropriate for generated class files, the example still holds. Gant supports the creation of logic almost effortlessly. When logic comes to mind, it's almost always easier to think in an imperative language like the Java language — not XML. And with Gant you were able to code this logic (even more easily because of Groovy's features, I might add) in a snap.


Static analysis the flexible way

Now that you've compiled code with your Gant script, it's time to change course and start to evaluate the code that is being compiled. In this section, you'll use a static-analysis tool that checks source code (not much unlike a compiler) for coding issues, such as long names and long methods.

PMD with Ant

PMD is an open source static code analysis library that has well over 300 rules you can apply to your code base (see Resources). For instance, PMD can check for if/else conditionals that don't include {}. Although such Java code is completely valid, omitting the braces can cause subtle defects if you aren't careful.

PMD categorizes all of its rules into rulesets. When using PMD, you need to tell it which rulesets to load as a part of its Ant task. And, before you can do any of that, you've got to load PMD's Ant task.

Using PMD is fundamentally different from anything you've done so far in this tutorial because you must load PMD's binary via Ant's taskdef task so that Ant can find PMD when you invoke it. If aren't convinced by now that Gant can do everything that Ant can do (and more), then this next example should convince you.

For instance, using PMD in a normal Ant build.xml file would look something like Listing 18:

Listing 18. Using PMD in an Ant build file
<target name="pmd">
 <mkdir dir="target/reports"/>
 <taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask" 
     classpathref="build.classpath"/>
 <pmd shortFilenames="true">
  <ruleset>rulesets/basic.xml</ruleset>
  <ruleset>rulesets/braces.xml</ruleset>
  <ruleset>rulesets/codesize.xml</ruleset>
  <ruleset>rulesets/coupling.xml</ruleset>
  <ruleset>rulesets/naming.xml</ruleset>
  <ruleset>rulesets/unusedcode.xml</ruleset>
  <ruleset>rulesets/design.xml</ruleset>
  <formatter type="html" toFile="target/reports/pmd_report.html" />
  <fileset dir="src">
   <include name="**/*.java"/>
  </fileset>
 </pmd>
</target>

In Listing 18, note that the taskdef call at the beginning of the target uses a classpath reference. This reference is fundamental to Ant's plug-and-play architecture and is the reason why so many tools bundle Ant tasks. What's more, note that to use multiple PMD rulesets, you must specify them serially, one by one.

Flexible PMD with Gant

Keeping in mind that Gant is Ant and Gant is Groovy and Groovy is Java, the first step to including PMD in Gant is to remove the long list of specifying ruleset elements and use something more programmatic, such as a Collection.

Add the following Groovy list of PMD rulesets to your Gant build file. Feel free to add other rulesets or subtract from this initial list:

final allrules = ["rulesets/naming.xml", "rulesets/braces.xml",
 "rulesets/basic.xml", "rulesets/unusedcode.xml",
 "rulesets/design.xml", "rulesets/coupling.xml"]

As you can imagine, you're going to leverage programmatic iteration to iterate over this list and pass it to the PMD Ant task. Along the way, you'll see that when you leverage PMD in Gant, Gant really is using Ant. Add the code in Listing 19 to your build.gant file:

Listing 19. Using PMD in build.gant
target(pmd:"runs static code analysis"){
 Ant.mkdir(dir:"target/reports")
 Ant.taskdef(name:"pmd", classname:"net.sourceforge.pmd.ant.PMDTask",
   classpathref:"libs")
 Ant.pmd(shortFilenames:"true"){
   allrules.each{ rfile ->
    ruleset(rfile)
   }
   formatter(type:"xml", tofile:"target/reports/pmd_report.xml")
   formatter(type:"html", tofile:"target/reports/pmd_report.html")
   fileset(dir:"src"){
     include(name:"**/*.java")
   }
  }  
}

In Listing 19, see how Gant allows you to use Ant's taskdef task. What's more, look how Listing 19 uses the allrules collection of rulesets for a much more compact coding construct. And note how all of this works just fine with Groovy's Builder logic — if you ask me, writing this sort of task is a lot of fun and much, much easier.


Ultimate flexibility

So far, you've seen how once you enter a programming paradigm in your build files, you can do some interesting things, such as leveraging logic to delete specific files, iterate over a collection naturally, and create functions for reuse. This section shows you how entering a programmatic paradigm also lets you easily add a level of proactivity to your builds.

Using PMD proactively

Let me ask you a question: How many times do you look at a report that was generated by your build? For instance, in the preceding section, you added capability to generate PMD reports. You'll probably look at this report once. Harsh, but true, right?

And no matter how many times you read the PMD report, by the time you read it, it's often too late. That is, what's done is already done: the code has been coded and is probably already checked into your code repository. Wouldn't it be more effective if your build were more proactive — that is, if it told you quickly that things are awry?

Adding a level of proactivity to your builds is quite easy when you sit down and work it out. Tools like PMD produce reports, so all you need to do is verify that generated report's values; if the values aren't what you expect or require or want, then you can take appropriate action, such as failing the build (or sending an e-mail message, and so on).

Proactive builds the Gant way

PMD's XML file is quite logical. It contains a list of file elements, and each file element contains a list of violation elements. Now you'll create a quick function that reads a PMD report XML file and checks each violation element's priority attribute. If the priority is equal to some threshold, then you'll fail the build.

If you've never seen Groovy's XmlSlurper at work, prepare to be amazed. Watch how easy it is to parse an XML document using it.

Add the code in Listing 20 to your build.gant file:

Listing 20. Parsing PMD's XML
def threshold(pmdfile, thresh){
 def root = new XmlSlurper().parse(new File(pmdfile))
 root.file.each{ fl ->
  fl.violation.each{ vio ->
   if(Integer.parseInt(vio.@priority.text()) == thresh){
    Ant.fail(message:"priority was ${vio.@priority.text()}")
   }
  }
 }
}

A lot's going on in Listing 20. First, the XmlSlurper object parses the incoming PMD file and returns a reference to the root of the XML. Next, the file element collection is iterated over, and for each violation within the file element, the priority attribute is compared to an incoming thresh value (which represents a threshold). If the incoming threshold value is equal to any priority attribute, then the build is failed using Ant's fail task. How's that for a proactive build?

Of course to use this newly defined function, you've got to reference it from a target. Add it to the PMD target you created earlier (see Listing 18. Just add the following line of code to the bottom:

threshold("target/reports/pmd_report.xml", 1)

Now, if PMD finds any priority 1 violations within your code base, your build will fail, thereby informing you quickly that you may have a problem.

Consider how wild a feat it would be to obtain the same feature in normal Ant via XML.


In conclusion

As you've seen, Gant provides an unparalleled level of flexibility for building software — flexibility that's not available in normal Ant's XML. But you've also seen that Gant leverages Ant completely — anything you do in Ant you can do in Gant. What's more, Gant leverages Groovy, which is the mechanism for this newfound flexibility.

With Groovy at your fingertips, in your Gant file you can create functions, which enable reuse, and you can create closures for additional flexibility. And you can do compelling things such as adding a high degree of proactivity to your builds in a facile manner unmatched in XML.

The marriage of Ant and Groovy provides a compelling alternative to plain old Ant XML build files. If you find yourself needing conditional logic, for example, then you might want to give Gant serious consideration. As you've seen, Gant is quite easy to use and a lot of fun in the process.


Download

DescriptionNameSize
Tutorial source codej-gant.zip7MB

Resources

Learn

  • Podcast: Scott Davis on Groovy (JavaWorld, November 2007): Regular developerWorks contributor Scott Davis talks about Groovy as well as other dynamic languages for the Java platform, including JRuby and Jython.
  • Ant-Contrib Tasks: The Ant-Contrib project is a collection of tasks for Apache Ant.
  • Maven and Ivy: Gant includes facilities for leveraging these Apache tools.
  • "Fluently Groovy" (Andrew Glover, developerWorks, March 2008): Learn how to use Groovy and Java code together in your everyday Java application development.
  • Practically Groovy (Andrew Glover et al., developerWorks, August 2004 through September 2006): Go beyond basics with introductions to Groovy Builders and templates, Ant scripting with Groovy, Groovy on the server side, smooth operators, Groovy's Meta Object Protocol, and more.
  • "Zap bugs with PMD" (Elliotte Rusty Harold, developerWorks, January 2005): Read more about the PMD static-analysis tool used in this tutorial's examples.
  • "Practically Groovy: Mark it up with Groovy Builders" (Andrew Glover, developerWorks, April 2005): Groovy Builders let you mimic markup languages such as XML, HTML, Ant tasks, and even GUIs with frameworks like Swing.
  • The Disco Blog's Groovy articles (Andrew Glover, thediscoblog.com): Read about various topics related to Groovy, such as unit testing, metaprogramming, and Groovy techniques.
  • "Automation for the people: Build Java projects with Raven" (Paul Duvall, developerWorks, November 2007): Build tools that support a more expressive paradigm than XML are entering the scene. Automation expert Paul Duvall describes how Raven, a build platform built on top of Ruby, leverages the power of a full-featured programming language with the simplicity of a build-centric DSL.
  • Browse the technology bookstore for books on these and other technical topics.
  • developerWorks Java technology zone: Find hundreds of articles about every aspect of Java programming.

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=309597
ArticleTitle=Build software with Gant
publish-date=05272008