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
apply plugin: 'java'
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.
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)
}
}
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.
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")
}
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.
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
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.
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)
}
}
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.
apply from : '../gradle-intro/build.gradle'
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
apply from : 'http://www.kellyrob99.com/blog/wp-content/uploads/downloads/2010/11/gradleSizeTask.txt'
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







[...] 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/ [...]
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!
[Reply]
November 15th, 2010 at 3:30 pm
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.
[Reply]
[...] Why do I like gradle? | The Kaptain on … stuff [...]
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.
[Reply]
>Me and my IDE want Java!
@David, use IDE that supports Groovy. There is at least one.
[Reply]
My IDE does support groovy. I think you are missing the point.
[Reply]
@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/
[Reply]
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.
[Reply]
December 26th, 2010 at 10:26 am
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.
[Reply]