Gradle - a powerful build system

Tomasz Kaczanowski,
2009-12-09

Tags: [tools, groovy]

Introduction

What tool do you use to build your project and why? In general there are two main answers to this questions in the Java world:

I use Maven. Thanks to fantastic plugins I need 3 lines to achieve effects, that would require a lot of XML pseudo-coding, if I used Ant. Sometimes Maven is not flexible enough, but then I use AntRun plugin and that solves my problems.

-- Maven user

I use Ant, and I can do everything, because Ant offers me the flexibility I need. Maven gained popularity by dependency management, but I have Apache Ivy, which can do even more.

-- Ant user

It is not my intention to repeat the arguments, that Ant and Maven users have used so many times in blogs, articles and discussion forums. First of all, because there is nothing new to say in favor of Ant or Maven. Secondly (and this reason is more important to me) because the new competitor - Gradle - has joined the tournament. And Gradle is so powerful that will redefine the Java build tools scene - moving all Ant/Maven antagonism to the history of computer flame wars.

Before presenting Gradle, let us have a closer look at some of the shortcomings of both Ant and Maven:

it is hard to implement any algorithm in the build file; even simple if or for constructs are hard to achieve, and very unnatural to express,

it is hard to do tasks (even presumably simple ones), that were not foreseen by Ant/Maven developers, or are not available in form of tasks/plugins,

you need to know these tools very well to understand a complex build,

"build by convention" is not supported (Ant), or ties your hands because the configuration is hard (Maven),

the support for multi-module builds is limited, and rarely fits your project needs,

the boilerplate of XML is annoying, and makes build files much bigger and harder to read, than they should be.

To fully benefit from this article, you should install Gradle. Consult the installation instructions on Gradle's website. You will find all source code attached, so you can easily follow the examples. All the code presented in the article is compatible with Gradle 0.8 (the latest at the time of writing).

The aim of this article is to introduce Gradle. Gradle has comprehensive documentation and many examples that will show you all its secrets. This article is not meant to replace these sources of information but rather to whet your appetite.

Gradle short introduction

I'd like to present Gradle by showing some real-life examples of its usage. But before this, let me underline few main features of Gradle, so you know what to expect.

Gradle is a very flexible general purpose build tool, especially suitable for Java developers. The build scripts are written in Groovy based DSL, making it very easy to express algorithms. Gradle allows to create dependencies between tasks, and follows the "convention over configuration" paradigm (but the configuration is always possible and easy to achieve). It goes beyond what today's build tools can achieve, at the same time sticking to good “standards”, that are well established among Java developers, thus lowering the cost of switching.

Gradle is still under rapid development (but at the same time ready for production use) and gains a lot of interest from the Java community. The list of already implemented features is impressive (including very advanced support for multi-module projects), and so are the features planned for the oncoming releases (e.g. DSLs in other languages than Groovy - Scala, JRuby, etc.).

Your first build script

I promised to present Gradle by examples, so let us create some code. First, we will write a very simple web application. Then we will enhance it to present many features of Gradle - especially the dynamic nature of build scripts and Gradle's support for multi-module builds.

If you place this script in myWebApp main folder, and run it by executing gradle war a resulting myWebApp-0.1.war file will be created in build/libs folder. Copy it to any web container (e.g. Tomcat) and enjoy by visiting http://YourHostName/myWebApp-0.1.

Learning about your build

With Gradle you can easily gather many important information regarding your build. Execute gradle --tasks to learn about tasks (written by you and those offered by plugins). Execute gradle --properties to learn about properties and gradle --dependencies to learn about dependencies (try also gradle -? to learn about all command line switches).

The first things that you probably spotted about this file are:

Gradle build scripts can be very concise,

it uses DSL instead of XML,

it can "talk" to Maven repositories.

Yes, it is all true, but this is only a beginning. Now, let us take a closer look at Gradle by examining this build script.

DSL and convention over configuration

First thing you probably noticed, is that the build.gradle script is very concise. This is thanks to the two facts:

Just say it

Would you like to use War plugin in your build ? Would you like to set version to 0.1 ? Say it straight.

usePlugin 'war'
version = 0.1

Would you like to print a message if some condition occurred ? Say it straight.

if (someCondition) {
println "some message"
}

If you think about this two simple examples, you won't notice anything unusual here. "That's the normal way the things should work". Yes, true. But unfortunately, it is very hard to express such simple things using Ant or Maven build files. Gradle uses mix of Groovy and DSL that gives you definitely more expressive power than XML based solutions.

Gradle uses DSL which is much more expressive than XML (it is also easier to read and understand),

Gradle follows the "convention over configuration" paradigm, allowing to cut the length of build file to the minimum, if default values (directory layout, file naming conventions) are used.

For example, Gradle follows the well-established convention of project layout (set by Maven), and by default expects to find the source code and resources in the following folders:[1]

Another convention makes Gradle produce WAR file using projectName-version.war pattern (project folder is used as projectName if not specified otherwise). Also by default, Java 1.5 source compatibility is assumed.

Configuration is possible and easy

All of these conventions can be easily configured if required. For example if created artifacts (JAR and WAR files) should be created in build/artifacts instead of default build/libs, it is enough to set value of libsDirName variable to "artifacts":

libsDirName = "artifacts"

To change the name of the generated WAR or JAR file (by default it uses name of the project) set archivesBaseName property:

archivesBaseName = "i-dont-like-the-default-name"

Because our simple myWebApp follows the default layout and conventions, there is no need to set any of these values. This way, the build scripts is very short.

Core functionality enhanced by plugins

Gradle is based on plugins. Each plugin can enhance the core functionality offered by Gradle in three ways. For example, usage of Java plugin results in:

almost 20 new tasks available (e.g. clean, compileJava, test, jar),

dependencies between these tasks, which forces the reasonable execution order (e.g. test task depends on compileJava and compileJavaTests tasks),

a so called "convention object" is added to your project configuration, which results in many convention properties defined, for example:

In the first build script, War plugin is used, which has two important consequences:

it imports Java plugin (as War plugin depends on it), thus importing all its tasks and configurations,

it adds a war archive task, which does what war task is expected to do:

copies src/main/webapp to the root of archive,

copies compiled classes to WEB-INF/classes,

puts all required runtime dependencies into WEB-INF/lib,

creates archive in build/libs directory

Gradle right now offers 9 plugins (Java, Groovy, War, OSGi, Eclipse, Jetty, Maven, Project-reports, Code-quality) and if you miss some features, you can always write your own (which is very simple, but outside the scope of this article - please refer to the userguide).

Repositories and dependencies management

I have a good news for you - after you switch to Gradle, you'll be still able to use your current artifacts repository. Gradle uses Ivy underneath, and is compatible with any kind of repository you can think of - from "standard" Maven layouts, through old-school flat directory layouts (like libs folder in Ant builds), to basically any custom layout of repository.

As you can see in the first example, achieving simple things is very elegant when using Gradle. To make Gradle download artifacts from the central Maven repository, it is enough to add these lines to your build script:

repositories {
mavenCentral()
}

Declarations of dependencies are also much more concise than those used by Maven[2] or Ivy. The snippet presented below puts Commons Lang JAR on the compilation classpath:

dependencies {
compile "commons-lang:commons-lang:2.4"
}

Writing your own build logic

Ant and Gradle

Gradle provide excellent integration with Ant. It is trivial to import your Ant build.xml directly into a Gradle project. You can then use the targets of your Ant build, as if they were Gradle tasks. The other type of integration is achieved thanks to fantastic AntBuilder provided by Groovy. The ant property, which is available in every Gradle build script, allows to reuse a wealth of Ant tasks and types - from simple ones like javac, copy, jar to more sophisticated like selenese (for running Selenium tests) or findbugs (for execution of Findbugs static code analysis tool).

This all makes switching from Ant to Gradle rather painless... and I'm pretty sure you will never want to go back. :)

Till now we relied on functionality provided by Gradle. It is time to go beyond of what is prepared for us. While doing this, we will learn many interesting things about Gradle.

Calculating checksum with Ant

Let us write a task, that will calculate a checksum for each file in build/libs directory (this is a default directory where all created JAR and WAR files ends up). Because we strongly believe in code reuse, we will use Ant checksum task for this. Add the task presented below to the build.gradle file and execute gradle clean checksum from the command line.

There is plenty of things happening in this short code snippet. It presents usage of Groovy scripting, dependencies between tasks, and cooperation with Ant.

Firstly, a new checksum task is created. It depends on on assemble task provided by Java plugin (which creates JAR and WAR files). Then, all files from libsDir (this is one of many properties set by Java plugin, which by default points to build/libs directory) are stored in a list using Groovy. Next, ant.checksum method is invoked on each of these files, and the result is printed to the standard output.

Snapshots and releases

It is quite common in the Java world, that the distinction between snapshot and release version is reflected by the names of artifacts. The snapshots receive a "-SNAPSHOT" suffix, which makes them easily recognizable. Let us have a look at implementation of such naming schema in Gradle.

The fragment of build.gradle file presented below shows one very interesting features of Gradle. The execution of Gradle build scripts is divided into phases. First, a DAG (dependency acyclic graph) of tasks is created. Then, the appropriate tasks are executed. The simple example below shows, that it is possible to jump "in between" these two phases, and manipulate the graph of tasks. In the example below, this introspection of tasks graph is used to decide, if archive should receive and additional "-SNAPSHOT" suffix or not.

Execute this build script once with gradle clean assemble and then second time with gradle clean release, and compare the resulting JAR file in the build/libs dir.

Creation of custom test report

Imagine you use Gradle to execute TestNG integration tests for a OSGi based application. This application consists of more than 10 OSGi bundles, and each of them changes often (it is an early stage of development). You would like to have a report that would contain:

information on the build environment (Java version, OS version etc.),

list of bundles (with versions),

results of tests.

It is possible to put all this report functionality into the build script, but then it would grow significantly. Instead, you can easily create your own custom task and then configure it and execute from the build script. The build.gradle would look (roughly) like this:

These two snippets of code show you few things. First of all, combined power of Groovy and AntBuilder makes it trivial to create text files and perform all kind of operations on them (copy, zip, delete etc.). Second, it is easy to create reusable tasks, and configure them - here, task ReportTask can take four parameters, which are supplied from the build script (this also helps to keep the real logic out of the build script). Third, groups of dependencies in Gradle can be processed in many ways - here you see a simple example of printing them.

One build file or many build files ?

The important thing is, that you can achieve same effects regardless of which approach you use. For example, if you want to add dependency on Commons Lang to core project, you could add this snippet of code to build.gradle placed in the root of the project:

or put this lines into the build.gradle file placed in core directory:

dependencies {
compile "commons-lang:commons-lang:2.4"
}

As you can see, it is really just a matter of taste.

Multi-module builds

Our application needs another user interface - this time in Swing. It seems natural at this point to divide the project into three parts - which will also give us a splendid opportunity to have a closer look at how Gradle supports multi-module builds:

core project contains business logic. It is written in Java and uses Commons Lang library. Both UI projects makes use of the core project classes.

swing project creates UI for desktop. It is written in Groovy.

web project creates UI for the web browsers. It is written in Java and uses JSP. The resulting WAR archive must contain all required libraries - not only core JAR, but also transitive dependencies of core project (that is Commons Lang library).

Multi-module project layout

Gradle offers a lot of flexibility when it comes to the layout of mutli-project builds. For the sake of this article, I will use a hierarchical layout (content of src directories not shown):

In the main directory there are two subfolders - one for core project, and the other one called ui. The ui subdirectory is a container for all possible UI implementations. Right now it contains swing and web subdirectories.

As you can see there is only one build.gradle file. You might wonder if it is a good idea to keep all the build logic in one place. If you don't like it, Gradle allows you to have separate build.gradle file for each subproject.[3]

settings.gradle

First of all, let us have a look at the new new file - settings.gradle - that we haven't met yet. This file describes the layout of a multi-module build[4]. As you can see below, it tells Gradle where the subprojects are located:

include "core", "ui:swing", "ui:web"

build.gradle

Before we see the build script, let us write down what it should do. Here are the tasks for the full build of this multi-module project:

Even if it is the first time, you looked at multi-module build.gradle file, you should have no problems with understanding it. First it uses subprojects property to set common settings for all subprojects (core, swing and web). This way all of them will:

use Java plugin,

have group property set to org.gradle.sample and version to 1.0,

look for and download dependencies from Maven central repository and local repo directory,

store created archives in repo directory.

After the common settings for all the subprojects are set, each of them is configured independently. A dependency on Commons Lang library is added to core project. Both UI projects are configured to depend on the core project. Then, project web is enhanced with War plugin. Finally project swing is set to use Groovy plugin, and a dependency on Groovy (required by this plugin) is also set.

Full and partial builds

For a multi-module build, there is always a question of "what part of it would you like to build?". So, would you like to build both UIs, or maybe only one of them ?

To build all projects execute gradle build from the root directory. You will notice, that all projects were build, and three artifacts were created: core-0.1.jar, swing-0.1.jar and web-0.1.war.

If you want to build only one of the UI projects, the buildNeeded[5] task is what you are looking for. For example, if you execute gradle buildNeeded from ui/swing directory, you will notice, that only core project and swing project were built resulting in the creation of two archives: core-0.1.jar and swing-0.1.jar.

The moral is, that partial builds are possible in Gradle (and even advisable, as they significantly shorten build time).

Conclusions

...ah, another silver bullet ? Another tool that promises to solve all your build-related problems ? Well, Gradle doesn't promise it all. But it strives at

making the impossible possible, the possible easy, and the easy elegant

-- Moshé Feldenkrais

And surely, even now, before 1.0 release, it offers plenty of interesting and useful features. I have presented only some of them - a tip of a (still growing) iceberg. :)

I'd like to encourage you to put some effort into knowing Gradle better. At first you might feel uneasy (especially if you are experienced Maven user) with the flexibility and freedom that it offers. I can tell you from my experience, that even after few month with Gradle, I feel like I haven't made the full mental switch, I'm am still surprised by the impact of Gradle features on my build scripts. Just give yourself some time, and you will be delighted with the power that Gradle offers. The switching cost is not so bad, as many solutions that you are accustomed to (e.g. project layout, Ant tasks, Maven repos) are still there, after you switch to Gradle.

Gradle comes with an impressive userguide, and a lot of examples that will help you to begin (see also CookBook). You can also count on the members of the community (check the mailing lists and the wiki). And if you need professional support, you might be interested in the services offered by Gradle Inc.