Table of Contents
Abuild.groovy
File
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.
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.
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.
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.
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, ...]
.
<<
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.
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)
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.
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.
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.