The Kaptain on … stuff

13 Nov, 2010

Why do I Like Gradle?

Posted by: TheKaptain In: Development

Gradle, if you don’t already know it, is rapidly gaining traction as a strong leader in the next generation of build systems. It builds heavily upon excellent aspects of the Maven and Ant frameworks, yet is pitched as not suffering from the same “Frameworkitis“. And I’ve gotta say – the results are pretty spectacular. Among the major selling features, at least as I see it, are:

  • Groovy syntax and a very terse and descriptive dsl that makes build scripts easily comprehensible
  • flexibility of layout, configuration, organizing build logic – pretty much everything
  • incremental builds, based on an easily implementable pattern
  • convention over configuration paradigm, thank you very much Maven
  • clear separation of build configuration from execution
  • extensibility at every level

The most basic Java build

[groovy language=”true”]
apply plugin: ‘java’
[/groovy]
That’s it. One line of Groovy in a file called ‘build.gradle’ and you can build a Java project with a Maven-standardized project layout.

├── build.gradle
└── src
    ├── main
    │   ├── java
    │   └── resources
    └── test
        ├── java
        └── resources

Included with the Java plugin are tasks to compile, package, test and javadoc your code. You also get configuration objects to describe the artifacts that your build depends on and those that it produces. Of course, with this most basic setup these configurations don’t yet have anything in them, but in a complex build they’re very handy for isolating the responsibilities of each task. Because you can both configure the existing configurations and add custom ones yourself, it’s very easy to accommodate a project that follows a different structure, whether that is just differently named source directories or multiple directories that need specific processing. This is VERY handy for legacy builds.

Incremental builds

Gradle provides a very easy way to create tasks that are able to execute only if their declared input and/or output artifacts have changed. This makes it trivial to incorporate your custom build behaviour into an incremental build. As an example I’d like to expand upon something I read by Etienne Studer in this month’s JAXmag. It’s a great example of developing an incremental task. First here’s the task implementation almost verbatim from the article. I’ve updated it slightly to make the output more readable using FileUtils, and you can examine the file it spits out from the build/reports/size directory that will be automatically created when it executes.
[groovy language=”true”]
class Size extends DefaultTask
{
@InputFiles FileTree inputDir
@OutputFile File outputFile

@TaskAction
void generate()
{
def totalSize = inputDir.files.inject(0) { def size, File file -> size + file.size()
}
outputFile.text = FileUtils.byteCountToDisplaySize(totalSize)
}
}
[/groovy]
If you simply include this class definition in a build file, it will be automatically compiled and available for use elsewhere in the script. It could just as easily be defined in a separate file(local or remote), in a jar on the classpath or in the buildSrc directory of your Gradle project. This flexibility enables developing and evolving tasks in a very agile fashion, encouraging you to publish the results for reuse instead of re-implementing the logic in other projects.
In order to execute this task as part of a build, we first need to configure it. In this case, I’m configuring it to work on all declared configurations and all source, both code and associated resources. In order to do so, I’m using a couple of Gradle internal classes, FileTree and SourceSet, which actually sound pretty self-explanatory to me. The key part is the assignment of the ‘inputDir’ and ‘outputFile’ properties on the task.
[groovy language=”true”]
task size(type:Size){
def filetree = sourceSets.inject(new UnionFileTree()) { FileTree total, SourceSet sourceSet ->
total += sourceSet.allSource
total
}
inputDir = filetree
outputFile = file("$reportsDir/size/size.txt")
}
[/groovy]
Just to prove that it’s working incrementally, here’s the output from successive invocations. Note that ‘UP-TO-DATE’ in the output that indicates the task was skipped the second time around, because none of the inputs changed and the output file hasn’t been deleted.
[groovy language=”true”]
gradle-intro$ gradle size
:size

BUILD SUCCESSFUL

Total time: 2.963 secs

gradle-intro$ gradle size
:size UP-TO-DATE

BUILD SUCCESSFUL

Total time: 2.912 secs
[/groovy]
This task does actually have a concrete dependency on the Java plugin, since without that convention applied neither the ‘sourceSets’ or ‘reportsDir’ objects would be present. Here’s the complete version of the build file that’s been built so far, 28 lines including imports and spacing.
[groovy language=”true”]
import org.apache.commons.io.FileUtils
import org.gradle.api.internal.file.UnionFileTree
import org.gradle.api.tasks.SourceSet

apply plugin: ‘java’

task size(type:Size){
def filetree = sourceSets.inject(new UnionFileTree()) { FileTree total, SourceSet sourceSet ->
total += sourceSet.allSource
total
}
inputDir = filetree
outputFile = file("$reportsDir/size/size.txt")
}

class Size extends DefaultTask
{
@InputFiles FileTree inputDir
@OutputFile File outputFile

@TaskAction
void generate()
{
def totalSize = inputDir.files.inject(0) { def size, File file -> size + file.size()
}
outputFile.text = FileUtils.byteCountToDisplaySize(totalSize)
}
}
[/groovy]

Sharing your new Task

The simplest way to share build logic is to ‘apply’ it. This simple shorthand covers everything from plugins to local files to remotely hosted resources. Here’s how easy it is to incorporate this particular task implementation and configuration into a build using a relative file path.
[groovy language=”true”]
apply from : ‘../gradle-intro/build.gradle’
[/groovy]

And because only a simple http connection is required to share it to a broader base, here’s how you can reference a copy on this site. Sorry about the .txt extension, but I didn’t feel like editing php to allow a new filetype today 🙂

[groovy language=”language=:”]
apply from : ‘http://www.kellyrob99.com/blog/wp-content/uploads/downloads/2010/11/gradleSizeTask.txt’
[/groovy]

Some other reading

If you’re still not sold on Gradle, here’s some articles I’ve seen recently that at the very least will give you a better perspective.
Hibernate – Gradle why?
Maven VS Gradle VS Ant
Maven to Gradle: Part 1, Part 2, Part 3
A comparison of build script length
Ant/Gradle/Maven comparison
DZone article
OpenMRS Mailing list on Maven VS Gradle

10 Responses to "Why do I Like Gradle?"

1 | Tweets that mention Why do I like gradle? | The Kaptain on ... stuff -- Topsy.com

November 13th, 2010 at 5:58 pm

Avatar

[…] This post was mentioned on Twitter by Paul King, kellyrob99. kellyrob99 said: New blog post: Why do I Like Gradle? http://www.kellyrob99.com/blog/2010/11/13/why-do-i-like-gradle/ […]

2 | David

November 15th, 2010 at 1:58 pm

Avatar

Why can’t we just have exactly this with a JAVA as standard instead of some poxy buzz language which time will sweep under the carpet.

With appropriate libraries java becomes pretty terse, and we wouldn’t have to learn a new language. Personally, I think the groovy syntax is messy. Me and my IDE want JAVA!

3 | TheKaptain

November 15th, 2010 at 3:30 pm

Avatar

Gotta say, the thought of trying to do raw build scripts in pure Java makes me want to reach for XML(shudder). I like the fact that when necessary you CAN code in a Gradle script, but for the vast majority of cases I just want to be able to configure behaviour and depend on the framework for the actual execution.

4 | Blog bookmarks 11/16/2010 « My Diigo bookmarks

November 15th, 2010 at 7:30 pm

Avatar

[…] Why do I like gradle? | The Kaptain on … stuff […]

5 | David

November 16th, 2010 at 10:49 am

Avatar

I don’t really understand how putting together a build script in java should be any more painful than putting together any other kind of program is.

An extra advantage is you get to debug your builds using your IDE in exactly the way you do your normal development.

The simplest build would just extend a base class overriding getName() or something trivial like that, and would default to a maven project directory structure with matching phase behaviours. To add a step before the integration-test phase, you’d just override the integrationTestPhase() method, insert your code (using your normal java libraries if desired) and then call super.integrationTestPhase().

Sounds like a pleasure to me.

6 | Tomek

November 16th, 2010 at 12:15 pm

Avatar

>Me and my IDE want Java!
@David, use IDE that supports Groovy. There is at least one.

7 | David

November 17th, 2010 at 10:15 am

Avatar

My IDE does support groovy. I think you are missing the point.

8 | Peter

December 4th, 2010 at 11:23 am

Avatar

@David Take a look into this build tool:

http://code.google.com/p/h2database/source/browse/trunk/h2/src/tools/org/h2/build/Build.java

written by the creator of h2 db.

> “My IDE does support groovy”

why not code java? groovy is a super set of it IMHO

BTW: more build systems:
http://karussell.wordpress.com/2009/09/29/evolution-of-build-systems/

9 | Andrew B.

December 26th, 2010 at 7:26 am

Avatar

Thanks for your thoughful post. I’ve wanted to find a reason to love Gradle. After reading your article, though, I remain unconvinced.

What I see in Gradle is the ability to script builds. For this benefit, I have to learn a new DSL. And, I if I want the terseness you tout, I have to use the Maven project layout.

There are two important costs: almost no one knows Gradle (a significant limitation in both commerical and OSS projects), and the use of a non-Java, non XML syntax to build Java projects.

If I were to take the plunge into a build language with those limitations, I’d have to also consider Apache Buildr and Gant. And I don’t see that Gradle brings anything compellingly better than either of those solutions.

10 | TheKaptain

December 26th, 2010 at 10:26 am

Avatar

Thanks for the comments Andrew. I have limited experience with Buildr and Gant so I really couldn’t make a fair comparison at this point.
The real benefit of the DSL, as I see it, is that you’re bypassing the ‘clunkiness’ of XML for describing your build and using a language specifically purposed for the task at hand. The cost of learning that DSL can be mitigated greatly once you realize that all you’re really doing is calling methods on the underlying Gradle classes being configured. For instance, when you configure a block like this:

jar {
baseName = 'myJar'
}

it’s equivalent to this:

org.gradle.api.tasks.bundling.Jar jar = new org.gradle.api.tasks.bundling.Jar();
jar.setBaseName("myJar");

Perhaps it’s my love of Groovy(or my hatred of xml) that makes the DSL appealing to me, but in terms of expressiveness and terseness I still feel that it brings a lot to the table.

Comment Form