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