Chapter 19. The Groovy Backend

Table of Contents

19.1. A Crash Course in Groovy
19.2. The Abuild.groovy File
19.2.1. Parameter Blocks
19.2.2. Selecting Rules
19.3. Directory Structure for Java Builds
19.4. Class Paths and Class Path Variables
19.5. Basic Java Rules Functionality
19.5.1. Compiling Java Source Code
19.5.2. Building Basic Jar Files
19.5.3. Wrapper Scripts
19.5.4. Testing with JUnit
19.5.5. JAR Signing
19.5.6. WAR Files
19.5.7. High Level Archives
19.5.8. EAR Files
19.6. Advanced Customization of Java Rules
19.7. The Abuild Groovy Environment
19.7.1. The Binding
19.7.2. The Ant Project
19.7.3. Parameters, Interface Variables, and Definitions
19.8. Using QTest With the Groovy Backend
19.9. Groovy Rules
19.10. Additional Information for Rule Authors
19.10.1. Interface to the abuild Object
19.10.2. Using org.abuild.groovy.Util

Note

This part of the manual is not as narrative and thorough as it ideally should be. However, between this chapter and the material in abuild's help system, all the basic information is presented, even if in an overly terse format. As a supplement to this chapter, please refer to the help text for the java rules in Section E.8, “abuild --help rules rule:java. You can also refer to the complete code for the java rules in Appendix J, The java.groovy and groovy.groovy Files.

A Groovy-based backend, primarily intended for building Java-based software, was introduced in abuild version 1.1. This framework replaces the older, now deprecated, xml-based ant framework that was present in abuild version 1.0. [36] The old ant framework was extremely limited in capability in comparison to abuild's make backend, and it was always considered tentative. Abuild's groovy backend is at least as powerful as its make backend offers comparable functionality across the board. As of abuild 1.1, the specific rules provided for building Java code lack the maturity of the C/C++ rules provided as part of abuild's make backend, but they still represent a significant improvement over what was available in abuild 1.0.

You might wonder why you should consider using abuild's Groovy backend when other Groovy/ant-based build systems, such as Gant and Gradle are available. You may wonder why you should use abuild for Java at all when you could get transitive dependency management with Ivy or Maven. Surely those tools may be the right tools in some environments, particularly for Java-only projects, but, at least at their current stage of development, they lack the same cross-platform/cross-language interoperability support offered by abuild, even as they offer more mature rules for building Java code and better integration with other “standard” Java build environments. But this is the abuild manual, not a comparison of various Java build options, so, without further delay, we'll continue with our description of abuild's Groovy backend.

19.1. A Crash Course in Groovy

Although you don't really have to understand Groovy to use the above-described features of the Groovy backend, if you want to get the most out of abuild's Groovy backend, it helps to have a decent understanding of the Groovy language, and you will certainly need to understand at least some basic Groovy to take advantage of more advanced customization or to write your own rules. If you are already comfortable with Groovy, feel free to skip this section.

Providing a tutorial on Groovy would be out of scope for this manual. However, there are a few Groovy idioms that abuild (as well as many other Groovy-based systems) make heavy use of, and understanding at least that much, particularly if you are already a Java programmer, will certainly help you to make sense of what is going on here.

Closures

A closure is an anonymous block of code that is treated as an object. When a closure is run, variables and functions that it uses are generally resolved in the context in which the closure was defined rather than in the context in which it is run. You can't get very far in Groovy without having a basic understanding of closures. You don't have to understand closures to use abuild's Groovy backend, but you certainly have to understand them, at least at some level, when you get to the point of writing custom rules.

Although a closure is often written as a literal block of code enclosed in curly braces, Groovy allows you to treat any function as a closure. In particular, Groovy allows you take a particular method call of a specific instance of an object and treat that method call as a closure. This feature is sometimes known as bound methods and is present in many modern programming languages. The syntax for creating a closure from a method in Groovy is object.&methodName. Abuild's Groovy backend uses this construct heavily in its rule code.

Automatic Bean Formation

In Groovy, calling object.field is just “syntactic sugar” for object.getField(). In other words, if an object has a method called getField, then accessing object.field is exactly the same as calling object.getField(). It is important to understand this when looking at Groovy code that is interfacing with the Java standard library. For example, object.class.name is the same as object.getClass().getName(), which may not be obvious to a Java programmer with no prior Groovy experience.

List and Map Literals

Groovy supports lists and maps that are similar to those in Java. However, Groovy has a syntax for list and map literals that can appear directly in code. We make heavy use of these in the abuild Groovy backend, and in fact, you will find heavy use of these in just about any Groovy code.

The syntax for a list literal is [val1, val2, val3, ...]. The syntax for a map literal is ['key1': value1, 'key2': value2, ...].

The << Operator

Groovy overloads the left-shift operator (<<) for appending to lists. For example, this code:

def var = []
var << 1
var << 2

would result in a list whose value is [1, 2]. The << operator, like all operators in Groovy, is just a shortcut for calling a specific corresponding method. This method returns the object being appended. So the above code could also have been written as

def var = []
var << 1 << 2

We use this syntax sometimes to append maps to lists of maps as it's a little cleaner (in the author's opinion) than explicitly coding lists of maps.

Named Parameters

Under the covers, Groovy runs on top of the Java virtual machine. As such, Groovy function calls are really just like Java function calls: a function may take a specific number of arguments that appear in a specific order. The Java language doesn't support named parameters, so there is no encoding of them in Java byte code. Yet Groovy appears to support named parameters, so how does this work?

With Groovy, you often see function calls that look like they have named parameters. For example, the following would be a valid function call in Groovy:

f('param1': value1, other, 'param2': value2)

You can even mix what look like named parameters with regular parameters as in the above example. What Groovy does when it sees named parameters is that it gathers them all up in a single map and then passes that map to the function as the first argument. As such, the above call is exactly equivalent to the following:

f(['param1': value1, 'param2': value2], other)

Trailing Closures

In Groovy, it is common to see something that looks like a function call, or even bare function name, followed by a block of code in curly braces. In fact, this construct is used in virtually every Abuild.groovy file. This points to another special bit of Groovy syntax surrounding function calls. Specifically, if a function call is immediately followed by one or more closures, those closures are passed to the function at the end of its parameter list. Additionally, if a function is being called with no arguments prior to the trailing closure, the parentheses can be omitted. So the following blocks of code are exactly equivalent:

f({println 'hello'})
f() {
    println 'hello'
}
f {
    println 'hello'
}

In all three cases, the function f is being called with a single argument, and that argument is a closure that, when run, prints the string hello followed by a newline.

Closure-based Iteration

Iteration over lists and maps is so common that Groovy provides convenience methods for calling a closure on each element of a list or map. In Groovy, a closure with one parameter can access the single parameter anonymously with through the variable it. If there are multiple parameters (or zero parameters), they have to be named and followed by -> to separate them from the body of the closure.

If you have a list in a variable called items, the following code:

items.each { f(it) }

would call the function f for each argument of the list. If have a map in a variable called table, this code:

table.each { k, v -> f(k, v) }

would call the function f on each key and value in the map. All that's happening here is that Groovy is calling the each method of the list and map objects with a closure passed to it as the last argument, which should hopefully be clear now that you've seen the trailing closure feature.

Safe Dereference

How often have you found yourself writing code where you first check whether a variable is null and, only if it isn't null, access it? Groovy offers a shortcut for this. This code:

obj?.method()

is the same as

if (obj != null)
{
    obj.method()
}

but it's a lot easier to write!

These features are often combined. In fact, this is extremely common when using Groovy's AntBuilder, which abuild uses very heavily. So if you see something like this:

ant.javac('destdir': classesDir, 'classpath': classPath) {
    srcdirs.each { dir -> src('path' : dir) }
    compilerargs?.each { arg -> compilerarg('value' : arg) }
    includes?.each { include('name' : it) }
    excludes?.each { exclude('name' : it) }
}

you may be able to a little bit better of an idea of what's going on!

There's a lot more to Groovy than in this tiny crash course. You are encouraged to seek out Groovy documentation or get a good book on the language. But hopefully this should be enough to get you through the examples in this documentation.



[36] For limited documentation on the old framework, see Appendix K, The Deprecated XML-based Ant Backend.