Gradle is proposed as a next-generation build solution. Gradle combines the flexibility of Groovy builds with a powerful domain specific language (DSL) that configures a rich set of classes. This allows to easily develop a non-standard build for any project, according to its needs, or to fall back to a more traditional “convention over configuration” approach also fully supported by the tool. This article presents what may ultimately be Groovy’s killer app.
Author: Kenneth A. Kousen
This article is based on Making Java Groovy, to be published in Spring 2013. This eBook is available through the Manning Early Access Program (MEAP). Download the eBook instantly from manning.com. All print book purchases include free digital formats (PDF, ePub and Kindle). Visit the book’s page for more information based on Making Java Groovy. This content is being reproduced here by permission from Manning Publications.
As with virtually all Groovy projects of any significant size, Gradle is written in both Java and Groovy. Gradle is essentially a Domain Specific Language (DSL) for builds. Gradle doesn’t come with an installer. Instead, you just download a zip file, set the GRADLE_HOME environment variable to wherever you unzip it, add the %GRADLE_HOME%\bin directory to your path, and you’re ready to go. In fact, you don’t even need to install Groovy first, because Gradle comes with its own version of Groovy.
Including Groovy in another project
One of the dirty little secrets of Groovy is that the various versions are not binary compatible. Code compiled with one version doesn’t necessarily work with any other.
This means that projects in the Groovy ecosystem have a choice. They can either be compiled with different versions of Groovy and make the Groovy version part of their own version, or they can include a particular version of Groovy.
The Spock framework takes the former approach. Spock versions are like 0.6-groovy-1.8, meaning Spock version 0.6 compiled with Groovy version 1.8. Grails and Gradle take the other approach. Grails 1.3.8, for example, includes a copy of Groovy 1.7.8. Grails 2.0.3 includes Groovy 1.8.4.
To see the Groovy version included in your Gradle distribution, run the “gradle –v” command. For Gradle 1.0-rc-3, the included Groovy version is 1.8.6.
When you run that command, Gradle also reports the included versions of Ant and Ivy, as well as the JVM and OS versions.
Gradle builds range from extremely simple to quite powerful. I’ll start with the simplest possible example and build from there.
Basic Gradle builds
Gradle is a plug-in based architecture. Most Gradle tutorials start by defining what a task is and showing how to call one. Rather than do that here, let me show you a minimal build file and go from there.
Here is the smallest possible Gradle build for a Java project called MinimalGradle, in a file called build.gradle.
apply plugin:'java'
The apply syntax indicates that the build is using the Java plugin. When you run the build command using this file, Gradle executes tasks in several stages, as shown:
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test
:check
:build
BUILD SUCCESSFUL
Each word after the colon is a Gradle task. Gradle constructs a Directed Acyclic Graph (DAG) out of the specified tasks, paying attention to their dependencies, and then executes them in order. This minimal project has no source code, so the compile tasks are up to date without running at all. In fact, the only task that does anything is the jar task, which creates a file called MinimalGradle.jar (which contains only a manifest) and stores it in the build/libs directory.
The next example is called HelloGradle and adds Java code, including a JUnit test case. It’s still a “Hello, World” example, but it does introduce some essential concepts.
Listing 1 A build.gradle file for a Hello, World Java application with testing
apply plugin:'java'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.9'
}
The terms repositories and dependencies are part of the Gradle DSL. Any required libraries are listed in the dependencies block. There are several legal forms for listing dependencies. The one used here is as a string separated by colons. In Maven parlance, the part before the first colon is the group id, the middle section is the artifact id, and the last section is the version number. Using Maven syntax is not an accident, as shown in the repositories section. Many different types of repositories can be used, but here the standard Maven central repository is declared.
Since nothing is said about the source code, it is assumed to be in the standard Maven structure. That means any classes are under src/main/java and tests reside in src/test/java. This is easy enough to change, but Maven structure is the default.
Executing the build runs the same series of tasks, but, this time, any tests are executed and a JUnit report in HTML form is produced in build/reports/tests/index.html.
Moving next to a mixed Java/Groovy project, consider the GroovyGradle project. The same Java classes are included, but now an additional test is added called GroovyGreetingTests, which is a subclass of GroovyTestCase. Sticking with the Maven conventions, the directories src/main/groovy and src/test/groovy have been added to the system.
The new build.gradle file is shown in listing 2.
Listing 2 A build.gradle file for a mixed Java/Groovy project
apply plugin:'groovy'
repositories {
mavenCentral()
}
dependencies {
groovy 'org.codehaus.groovy:groovy-all:1.8.6'
testCompile 'junit:junit:4.9'
}
There are two differences to note. First, the Java plugin has been replaced by the Groovy plugin, which includes the former inside it. Second, Groovy has to be declared as a dependency, as shown in the dependencies block.
The new plugin adds a couple of tasks to the build, as shown here.
:compileJava
:compileGroovy UP-TO-DATE
:processResources UP-TO-DATE
:classes
:jar
:assemble
:compileTestJava
:compileTestGroovy
:processTestResources UP-TO-DATE
:testClasses
:test
:check
:build
BUILD SUCCESSFUL
Both the compileGroovy and compileTestGroovy tasks are new, but everything else proceeds normally.
Interesting configurations
Gradle builds are used throughout this book. I’ll bring up lots of different options when discussing specific examples in context, but here I can show a few interesting ideas.
Custom source sets
Separating Groovy source code from Java source code is rather artificial. What if you wanted to use the same source folder for both, as an Eclipse project might do? Here is an easy customized project layout to do so.
sourceSets {
main {
java { srcDirs = [] }
groovy { srcDir 'src' }
}
test {
java { srcDirs = [] }
groovy { srcDir 'src' }
}
}
Source sets are collections of source code in a Gradle build. Here, by assigning the srcDirs property of both the src/main/java and src/test/java folders to an empty list, the Java compiler won’t run at all. Instead, the Groovy compiler is used for all classes in the src directory, which will presumably hold both Java and Groovy classes.
Copying jars
Another interesting task is to make a local copy of the dependent libraries. The following task does that:
task collectJars(type: Copy) {
into "$buildDir/output/lib"
from configurations.testRuntime
}
The collectJars task is a kind of Copy task—one of the built-in task types in Gradle. Running collectJars copies the jar files in the runtime classpath into the output/lib folder in the build directory. Spock uses this task to make a complete distribution.
Inputs and outputs
Another neat capability of Gradle is that it can skip tasks that aren’t necessary. It does this by creating hashes of files and directories and checking whether or not they have changed.
Ant integration
One of the nice features of Gradle is that it includes an instance of groovy.ant.AntBuilder as part of the build. That means anything that can be done with Ant can be handled inside a Gradle build. That has a couple of consequences. First, if you already have an Ant build file, you can invoke its tasks inside a Gradle build. You can even make the Gradle tasks dependent on the Ant tasks.
Consider this example, from the Gradle samples. The Ant build file is build.xml, and contains a single task called “hello”.
<project>
<target name="hello">
<echo>Hello, from Ant</echo>
</target>
</project>
The Gradle build is in the file build.gradle.
ant.importBuild 'build.xml'
task intro(dependsOn: hello) << {
println 'Hello, from Gradle'
}
The “intro” task depends on the “hello” task from the Ant build, which is imported using the ant variable (an instance of AntBuilder). Running “gradle intro” executes both tasks.
:hello
[ant:echo] Hello, from Ant
:intro
Hello, from Gradle
BUILD SUCCESSFUL
The Wrapper task
Finally, a client can execute a Gradle build even if they don’t have Gradle installed. Gradle comes with a special Wrapper task, which has a version property.
task wrapper(type: Wrapper) {
gradleVersion = '0.9'
}
Running this task generates scripts for both Windows and UNIX along with a minimal Gradle jar distribution. The wrapper tasks are called gradlew (or gradlew.bat). When executed, the wrappers first download and install a local copy of Gradle and then execute the build.
Summary
In this article, I presented the Gradle build tool. This chapter included a basic discussion of Gradle, and mentioned several more advanced capabilities.