A build tool named Gradle – Part 2
In my previous post of this serie I showed you the meaning of build tools in the software development process. By now you should know, what build tools are good for and what they are doing. In this second part I want to dwell on a specific build tool named Gradle. I will show you the usage and the the pros and cons of this tool. And I’ll give you a preview on part 3, in which I will compare Gradle with the popluar build tool Apache Ant.
Introduction to Gradle
As already mentioned, Gradle is a build tool provided by Gradleware Inc. It is open source and under the Apache license 2.0. It was developed under the management of Mr. Hans Dockter and version 1.0 was released on 12th of June 2012. Gradle has its own syntax – the Gradle DSL (domain specific language) – which is based on Groovy. Groovy in turn is Java-based. Thus the Gradle DSL is easy readable for people who are familiar with Java. But why should you even use Gradle instead of widespread tools such as Ant or Maven? Well, let’s see what the developers of Gradle have to say:
“Gradle combines the power and flexibility of Ant with the dependency management and conventions of Maven into a more effective way to build” – Gradleware Inc.
Sounds nice, doesn’t it? But I think you should build your own opinion whether it is useful to build your software with Gradle or not. To facilitate your decision, I want to give you an introduction into the usage of some common Gradle elements.
Getting started
To use Gradle, you need a installation of JDK 1.5 or higher. You can either install Gradle directly on your machine, or you use the ‘Eclipse Integration Gradle’ as described here. You can check whether Gradle is installed correctly by typing gradle -v in your console. Now you can create your build script. Therefore, just place an empty file named ‘build.gradle’ in the root directory of your project.
What are projects, what are tasks?
Projects – A project is a logical unit for an independent piece of software. Each project has exactly one build process, but Gradle allows you to define a parent-child relationship between projects. Let’s say you have a Client-Server-Software. Now you define 3 projects – yourSoftwareName(as parent project), clientModule(as child) and serverModule(as child). You can define these relationships in a file named settings.gradle. This file must be placed in the root directory of your parent project. So your folder structure could look like the alongside chart (only build relevant folders and files are shown in the chart – of course there are other folder like src, bin, build, etc.).
1 |
include 'clientModule', 'serverModule' |
Now you can place your common and shared resources in your parent project and access them from your child projects. Furthermore you’re able to teach your build process what it has to build – the whole software or only clientModule/serverModule.
Tasks – A task is a logical unit of work within a project. Most of your build script takes place inside tasks. The usage of tasks is very simple. You can either use predefined task types such as copy, exec, compile etc.,write your own task types or use a task without any type. Task types are templates that give your tasks attributes and methods. You can use tasks without a type for very specific operations which are not predefined in Gradle. If you want to use these specific operations more often, I would recommend to write your own task template. This is an example for a simple copy operation:
1 2 3 4 5 6 7 8 |
//definition of a predefined task with the type: Copy task myCopyTask(type: Copy) { from "${sourceFolder}" into "${destinationFolder}" exclude "*Test*.java" } //you can now call the task via console with: gradle myCopyTask |
By using predefined tasks you only have to set the task type and the attributes respectivly the methods of the task type (in the example above: from, into, exclude). You can lookup these elements in the Gradle DSL documentation (in the section task types).
Usage of Plugins
Plugins are very powerful extensions that add several tasks to your build process. I will show an example by using the Java plugin.
1 |
apply plugin: 'java' |
With this single line of code, Gradle activates the java extension for that gradle script. Thus, some java specific tasks are added to your build process. Now you have some executable tasks such as compileJava, compileTestJava, test, jar etc. The attributes of these tasks are filled with default values, so you should overwrite them with your specific values. Now you only have to configure and execute these tasks. Let us try to compile some Java source code. To do that we only need to tell Gradle where to find the source code. The following example shows how that could be achieved.
Definition and usage of source sets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
sourceSets { main { java { srcDirs = 'main/src' compileClasspath = configurations.compileCommon output.classesDir = 'build/classes' } } test { java { srcDirs = 'test/src' compileClasspath = configurations.compileTestCommon output.classesDir = 'test/classes' } } } |
With this code, we defined two source sets, one named ‘main’ and the other named ‘test’. We also defined the directory with the sources, a classpath and an output directory. The tasks provided by the Java plugin (compileJava and compileTestJava) are automatically using those source set definitions. Executing these tasks would compile the the source classes from the corresponding source folder to the corresponding output folder. Furthermore you can use these source sets in other operations as an object. Here are some examples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//iterating source sets sourceSets.each { it -> println it.output.classesDir } //global configuration for all source sets //adds a classpath entry to all source sets sourceSets.all { compileClasspath = files('build/classes/common') } //usage of the output directory of a source set as input directory for a test task task myJUnitTest(type: Test){ testClassesDir = sourceSets.test.output.classesDir } |
I’m a big fan of these source sets, because you can use them in different ways and because of possibility to link them directly you’ll get a consistent build process.
Dependency Management
As already mentioned in the first part, Gradle has an embedded dependency management tool. Apache Ivy is an approved dependency management tool. It is used by Ant, Maven and – of course – Gradle. Gradle even has embedded Ivy directly into their DSL.
First of all, we need a repository where Ivy can download the needed dependencies. We can either commit a URL or we just use the Maven Central Repository. After defining a repository, we can create a configuration. Configurations are a possibility to group our dependencies – so we can group our dependencies into a buildConfiguration (needed dependencies at build time) and a runtimeConfiguration (needed dependencies at run time). Finally, we can define the external libraries and assign them to a configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//declaration of the repository repositories { mavenCentral() //usage of the official maven central repository (predefined in gradle) } //declaration of the configurations configurations { myBuildConfiguration myRuntimeConfiguration } //declaration of the external libraries (assigned to the configurations) dependencies { myBuildConfiguration ( 'com.sun.xml.bind:jaxb-xjc:2.2.1', 'commons-cli:commons-cli:1.2' ) myRuntimeConfiguration ( 'org.hibernate:hibernate-core:4.1.7.Final' ) } |
If we now use our defined configurations in in a task – let’s say as classpath – Gradle will search the repository for the relevant packages and download them to an internal folder. The advantage of this native dependency management is, that you’re able to maintain your dependencies at one central point in one file. As explained before, Ivy is directly embedded in Gradle – there is no need to install it, therefore you can use it out of the box.
Build Lifecycle
Gradle provides a high-level build lifecycle, which means that specific internal functions will be executed in a specific order. Basically the build lifecycle consists of three phases:
1. Phase: Initialization:
This phase is only relevant for multi-projects (with a a parent-child relationship). It evaluates the settings.gradle file and determines which projects take part in the build process.
2. Phase: Configuration:
The configuration phase creates a so-called DAG (directed acyclic graph). This graph defines the execution order of the tasks inside the build script(s). You are able to manipulate the execution order with the methods dependsOn(otherTask) and mustRunAfter(otherTask), of which more in Part 3. Furthermore this phase checks the syntax and evaluates the project objects (for example whether a path is reachable) that these objects aren’t inside the doFirst{ } / doLast { } blocks (see example below).
3. Phase: Execution:
Within this phase Gradle executes the tasks in the order that was defined by the DAG. With the doFirst { } / doLast { } blocks, you can control what should happen immediately before / after the execution. Thus, you can split up the execution phase into three more phases:
- doFirst{} block
- actually execution (like copy, compile, package etc.)
- doLast{} block
Example build lifecycle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
println "I am outside of a task" //in configuration phase because it is outside of a task task myCopy(type: Copy){ from ('main/src'){ // exclude '**/*.java' //configuration of the copy task. evaluation at configuration phase and } //execution at execution phase into 'build' // doFirst{ // println "Starting to copy something..." //evaluation and execution at execution phase. but execution before the copy task because of the doFirst{ } block } // doLast{ // println "Finished copy..." //evaluation and execution at execution phase. but execution after the copy task because of the doLast{ } block } // } //output of: gradle myCopy // //I am outside of a task //Starting to copy something... //[here the copy operation takes place] //Finished copy... |
If you want to learn more about the build lifecycle, have a look at the User Guide Chapter 55: The Build Lifecycle
Interim conclusion
I hope I could give you an basic understanding of this new build tool. Personally, I am a big proponent of Gradle. In my opinion the statement of Gradleware, that it combines the flexibility of Ant and the conventions of Maven, is true. The first steps into this tool seem to be very hard, but with some exercise and experience it gives you some great advantages.
What is coming next in Part 3?
In the last part of this series I’ll compare Gradle with Apache Ant. I will reveal some use cases and how to solve them with Ant and Gradle. Additionally I will show you how to use the Ant-Wrapper and explain why you need less code in Gradle than in Ant. Finally I will evaluate the pro and cons and give you my personal conclusion.
Read the first part of this post: A build tool named Gradle – Part 1
Read the third part of this post: A build tool named Gradle – Part 3
-René
Recent posts






Comment article