September 28, 2017
Fast lane to Gradle syntax for Java developers

1. Intro

We are using Gradle as build system for our BCD-UI framework. Even after having created the first working build scripts I wondered sometimes about Gradle’s syntax. Especially how a task definition’s syntax works, while it was easy to use, stayed unclear.
When looking at Gradle as a Java developer, you probably stumble over the syntax. It looks like a nice balance between declarative and imperative, but learning it in addition to Gradle is one more hurdle. Not everybody wants to become a Groovy champion first to start with Gradle.
Here we try to help you, limiting Groovy knowledge as far as possible to read and write your first Gradle scripts.

Attention: We do neither explain Gradle here, nor Groovy. This is all about helping understanding Gradle’s syntax by explaining parts of Groovy’s syntax.

Since a Gradle script basically a Groovy script, you may wonder, why the title is not “Groovy syntax for Java developers”. There are two reasons:

  1. Gradle DSL does only use a part of Groovy syntax

  2. Gradle does extend Groovy syntax a bit

There are already some very good Groovy syntax introductions for Java developers, for example Groovy Tutorial for Java Developers from Tim Roes.

Our goal

Let’s have a look at this typical Gradle fragment:

task myTask( type: Copy ) {
  from 'srcDirA'
  into 'destDir'
  includeEmptyDirs = false
  doLast {
    println "Copy finished"
  }
}
When I saw this, I had several questions, among them
  1. What is myTask syntactically. Looks like a call to a never declared function

  2. What are the outer curl braces for, some kind of class definition?

  3. When do I need to have use a plain value, an equal sign plus value and when something with curl brackets?

Seems to define a Gradle task named myTask, copying files from 2 sourceDirs to destDir and print ‘copy finished’ at the end.
While you may have an idea what this does, the syntax may not be clear, which hurts in more complex cases or when trying to create even an easy script yourself.

2. Some Groovy syntax

You can Groovy code online with Groovy Web Console here.
Don’t be surprised if it sometimes switches from ‘Output’ to ‘Result’ in the area below the text entry. Later you’ll understand why (see implicit return).

2.1. DSLs

Groovy is, as Java, a language that is compiled into Java Virtual Machine bytecode and executed in a Java VM. It allows to write code more compact compared to Java, which some see critical in general, but which comes nicely when it comes to DSL Domain Specific Languages.
A DSL is not purpose agnostic, like Java or C, being general purpose languages, instead the keywords of each DSL rather belong to a specific domain. In the case of Gradle, the domain is build scripts.

2.2. Some Groovy syntax

We only touch a few aspects of Groovy syntax here.
First, Groovy is almost a superset of Java, i.e. every Java code is also Groovy code you can mix-in Java code lines seamlessly and talk to the other code’s objects. But it has extensions which make it easy to create DSLs and declaration-like syntax.

                                          (1)
def str = 'I am a string'                 (2) (3) (4)
// Math.sqrt(str)                         (5)
println str                               (6) (7)
def msg = "$str is ${str.length()} long"  (8)
println Math.sqrt( str.length() )         (9)
str = 200                                 (10)
println str.class.name                    (11)
1 No need for an enclosing class and static void main, Groovy creates that for you in the background. Start the script with code.
2 Groovy knows type inference. Each variables has a typed a given time, as in Java, but the compiler can infer types from the context. In most cases you can just use the generic def
3 Normal strings have single quotes '' in Groovy
4 You can omit the semicolon ; at the end of statements in most cases, if you do one statement per line
5 Groovy knows, txt is a java.lang.String, so compiler and IDE can complain here. Note that it is detected a compile time and not only during runtime as it would be in a scripting language.
6 println() is a shortcut to System.out.println().
7 You can omit the parentheses () around parameters in method calls, if it is not a nested call and has at least one argument
8 GStrings strings are strings in double quotes "", offering interpolation and more
9 You can mix Java and Groovy statements seamlessly
10 Groovy variables defined with def (and not an explicit type name) can change there type over time
11 Each class attribute can be accessed in a property-like manner, Groovy translates this into a call to the getter behind the scenes

While there is much much more about Groovy syntax, see link above, this is enough for our purpose of understanding above Gradle task definition.

2.3. Closures

Groovy supports closures. Closures are functions which can be passed around like objects. If you know Java 8 lambdas or Javascript closures, you have an idea.

To define a closure, just write the body of it surrounded by curl brackets:

// A Groovy script in java-near syntax
import groovy.lang.*;

Closure<Integer> myClosure = {
  int i = 4;
  return 4 * 2;
};
println( myClosure() ); // -> '8'

This code was as close to Java as possible. let’s have a look at the more compact style we learned above

//                                       (1)
def myClosure = {                        (2)
  def i = 4
  4 * 2                                  (3)
}
println myClosure()  // -> '8'           (5)
1 No need for import. Groovy imports more than Java per default
2 Use = { …​ } to define the closure
3 You can omit the return key word, the return value will be the value of the last expression of the method or closure
4 We could omit the most outer () but not the inner one. An remember, the second reason why we could not omit the inner one is because that call had not parameters

Parameters

To define parameters of closures, list the parameters’ names followed by , same syntax as for Java lambdas:

def myClosure = { myVal1, myVal2 ->
  myVal1 * myVal2 * 2
}
println myClosure(2,4) // -> '16'

Named parameters

Groovy also allows for named parameters. Declare one argument, provide named parameters and access them via name:

def myClosure = { args ->   args.val2 * args.val1 * 2 }
println myClosure( val1: 2, val2: 4 ) // '16'

2.4. Closures as parameters

Closures can be passed around and provided as arguments

def times_2 = { valA -> valA * 2 } (1)
def times_4 = { valA -> valA * 4 }
def show = { valA, cl ->
  println cl( valA )              (2)
}
show 2, times_2  // -> '4'
show 2, times_4  // -> '8' (3)
1 This closure just returns the argument times 2
2 Here we take the first parameter, call the provided closure with it and print the result
3 Here we call execAndPrint with the value 2 and the closure times4 as arguments

If the closure is the last parameter, you may append it after the call.

def show = { valA, cl -> println cl( valA ) }
show( 2,  { valA -> valA * 8 } )  // -> '16'       (1)
show( 2 ) { valA -> valA * 8 }    // -> '16'       (2)
1 Closure parameter provided as second parameter
2 Closure parameter provided behind the the call, outside of the parenthesis

2.5. The closures’ delegate

But why aren’t groovy closures not just Java lambdas? There are 2 things, Groovy closures can do, which Java lambdas can’t, which are important for us here

  1. Groovy closures have access to local non-constant variables from the context they were defined in

  2. Groovy closures have a user changeable delegate object, which is used for looking up local methods and attributes

Let’s look at the following closure

def config = {
  def srcDir = 'fromDir'
  from srcDir
  includeEmptyDirs = true
}

While srcDir is a simple local variable, where do from, into and includeEmptyDirs come from?

Let’s create the following class

class MyClass {

  // Some attributes
  def fromDirs = []             (1)
  def includeEmptyDirs = false

  // A method
  void from( fromDir ) {        (2)
    fromDirs.add( fromDir )
  }

  // A method allowing to configre us with a closure
  void configure( closure ) {        (3)
    closure.delegate = this
    closure.resolveStrategy = Closure.DELEGATE_FIRST
    closure()
  }
}
1 Two instance attributes
2 A method, appending its argument to fromDirs
3 A method, getting a closure, changing the closure’s delegate to the instance and executing the closure

With this we can do

def myClass = new MyClass()
myClass.configure( config )

println myClass.fromDirs         // -> 'fromDir'
println myClass.includeEmptyDirs // -> true

3. Our own implementation

I am sure this gives you an idea, how configuration closure work in Gradle. To match the syntax even closer, we add a task() call. Here is the complete example:

// The class we want to configure
class MyClass {                                    (1)
  def fromDirs = []
  def includeEmptyDirs = false
  void from( fromDir ) {
    fromDirs.add( fromDir )
  }
  void configure( closure ) {
    closure.delegate = this
    closure.resolveStrategy = Closure.DELEGATE_FIRST
    closure()
  }
}

// A generic task factory
def taskCreate = { name, clazz, configClosure ->   (2)
  def t = clazz.newInstance()                      (3)
  t.configure( configClosure )                     (4)
  binding.setProperty( name, t )                   (5)

}


//------------------------------
// One step closer to Gradle
taskCreate ( 'aTask', MyClass ) {                  (6)
  from 'aDir'
  includeEmptyDirs = true
}

// aTast is now a configured instance of MyClass
assert aTask.fromDirs == ['aDir']
assert aTask.includeEmptyDirs == true
1 Our class, accepting the configure closure
2 A factory function, getting a task name, type and configure closure
3 Create a class of the given type, will be MyClass
4 Apply the configure closure on the new task
5 “Publish” the new task by its name with some Groovy binding magic
6 Create the class in Gradle like syntax.
Note how we wrote the last parameter, i.e. the configure closure, behind that parenthesis.

Is this how Gradle does it? In principle yes, but in case of Gradle all this is executed in the scope of a project object, see below.

4. Check what we learned

Gradle sets up an instance of class Project for each gradle.build file. The content of your build script is then executed in the scope of this instance and all calls and property access are calls to method’s and properties of the project.

Let’s look at the sample above again (with small modifications).

task ('myTask', type: Copy ) {     (1)
  from 'srcDir'                    (2)
  into 'destDir'
  doLast {                         (3)
    println "Copy finished"
  }
}
1 task is a call to a method defined in Gradle’s Project class.
It gets 3 parameters, the name of the new task, a set of named parameters and a closure (task configuration closure), starting here.
Gradle does create an instance of Copy with the given values
It then applies the closure spanning from the first to the last line to the new instance.
2 from is a call to a method of Copy with th string ‘source’ as its parameter
3 doLast is al call to a method as well with a closure as the parameter

Th difference between this and the sample above is, that myTask in the first sample is not provided as a string but rather looks like a keyword.

task myTask ( type: Copy ) { ... }

That is in fact something, which the Gradle team introduced as yet an extension to Groovy syntax by interpreting the AST.

Let’s try another example

task myTask( type: Copy ) {        (1)
  from ( 'srcDir' ) {              (2)
    exclude '**/*.properties'
  }
  into 'destDir'
  includeEmptyDirs = false         (3)
}
1 Call the project’s task() method, provide the name of the task, a named parameter type providing a class we want to derive from and a configuration closure to immediately setup our new task instance
2 Here we call the from() method providing 2 parameters, ‘srcDir’ and a closure.
Within the closure we call exclude(), providing one parameter. exclude() is executed in the context of the from specifier.
3 This assigns false to the project’s property includeEmptyDirs. As groovy is transparently using a setter, many internal things could go on here.

Of course all the magic is nested here and exclude() is a method on an instance of some CopySpec class, which is created inside of from(). I guess you can imagine by now, how that works.

Post Views: 5,286

One response to “Gradle syntax for Java developers”

  1. Johny Jackson says:

    When someone writes an post he/she maintains the thought of a user in his/her brain that how a
    user can understand it. So that’s why this piece of writing is amazing.
    Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *