Written on
Using Gradle in Practice
One thing on my to-do list for a long time has been to switch GContracts [0] from Maven to Gradle. This blog post is about the basic concepts I learned from the migration and how parts of the resultingbuild.gradle
look like.
The Origin
GContracts is basically a Java library which provides Groovy AST transformations to annotate Groovy classes with pre-, post-conditions and class-invariants. So far, the build-process has been realized with Maven 2, thus with apom.xml
.
The overall build-process is pretty straight-forward:
- compile the java classes with a dependency to the current Groovy version
- compile the groovy classes with the same dependency (all tests are done in Groovy)
- execute the test-suite
- generate the javadoc files from the given source code
- packaging
- package the compiled class files to a separate Jar file
- package the source files to a separate Jar file
- package the javadoc files to a separate Jar file
- sign all Jar files
- upload all jar files to the OSS Sonatype Nexus staging repository
pom.xml
for GContracts build process can be found at github, but it mainly consists of plugins realizing the use-cases cited above.
Starting with a simple build.gradle
Gradle's equivalent to Maven's pom.xml
is a file called build.gradle
. But differently from Maven, it uses its own internal Groovy DSL called the Gradle Build Language [1].
Before I am going to start with the basic terminology it is important to notice that Gradle itself has been built by applying domain-driven design principles, leading to an important consequence: you'll find class representations of the terminology I use in this blog article in the Gradle API. This is a direct consequence of using a ubiquitous language (a language structured around the domain model and used by all team members to connect all the activities of the team with the software), a main principle of DDD.
A first-class citizen is the project. Each build.gradle
file is directly linked to a project. If GContracts would consist of multiple sub-projects (core-module, spring-module, etc.) each sub-project would be assigned to a separate build.gradle
file. For the sake of simplicity, let us ignore multi-project builds for this article (more about it can be found in the Gradle documentation [2]). As I've said previously, when taking a look at Gradle's API javadoc you'll find the Project
class. It is exactly this Project
class which will be instantiated by Gradle when starting a build process - after initialization build.gradle
is used to configure the Project
object instance.

Project
instance with DSL statements in the build.gradle
file. What we need to do first of all is to set some properties (although all of them would be optional and assigned to default values):
group = "org.gcontracts"
version = "1.1.3"
description = "GContracts provides closure annotations to support design by contract for Groovy classes."
targetCompatibility = "1.5"
sourceCompatibility = "1.5"
Project
object instance defined, like project.group = ...
. Since build.gradle
syntax is a Groovy DSL, this can be done by altering the delegate - the object which receives method and property calls. In this case, every property we've specified will be delegated to the actual Project
instance (if it is not a build script property/method, but let us ignore those details by now).
Gradle is based on a modularized structure, with each module being a plugin. Out-of-the-box Gradle comes with a bunch of officially supported plugins. In order to compile and package the Java source files we need to add the Gradle Java plugin [3]. Applying a plugin to a build configuration file is done by calling the Project
class' apply
method
apply plugin: 'java'
gradle tasks
on the command-line we could see the tasks being imported by the Java plugin. As GContracts internally uses Groovy as the only dependency (the Java code executing the AST transformations needs to know the Groovy abstractions), an external dependency needs to be defined in our build.gradle
file to let GContracts compile correctly:
dependencies {
compile group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.6'
}
repositories {
mavenCentral()
}
Setting Up the Test Environment
Since GContracts test cases are entirely written in Groovy, we'll also need to apply the Groovy plugin [4]. The plugin does not work out-of-the-box, it needs to know the exact Groovy version it should use. This can be done by specifying another dependency:
dependencies {
compile group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.6'
groovy group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.6'
}
groovy
.
Gradle introduces the concept of configurations. Multiple configurations can be added to the current project, each configuration will contain a certain distinct set of dependencies. When we applied the Groovy plugin, it added a groovy
configuration to the current project. The groovy
configuration does not default to a certain Groovy version, that is the reason we need to explicitly define one.
The Groovy plugin automatically applies the Java plugin, so we can remove this line. In addition, we can remove the first reference to Groovy for the compile configuration since the groovy
configuration extends the compile
configuration.
The next step is to add another dependency for executing JUnit test cases:
dependencies {
groovy group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.6'
testCompile group: 'junit', name: 'junit', version: '3.8.1'
}
build.gradle
we can already compile Java and Groovy source-code, create javadocs, test and package GContracts - only be specifying the plugins, a repository, dependencies and very little meta-data.
apply plugin: 'groovy'
group = "org.gcontracts"
version = "1.1.3"
// libs
junit = "junit:junit:3.8.1"
description = "GContracts provides closure annotations to support design by contract (tm) for Groovy"
targetCompatibility = "1.5"
sourceCompatibility = "1.5"
// specifies all Maven repositories for resolving external depedencies
repositories {
mavenCentral()
}
// external dependencies
dependencies {
groovy group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.6'
testCompile junit
}
gradle tasks
we can have a look at all tasks this build script offers for execution:
gradle tasks
:tasks
------------------------------------------------------------
Root Project - GContracts provides closure annotations to support design by contract (tm) for Groovy
------------------------------------------------------------
Build tasks
-----------
assemble - Assembles all Jar, War, Zip, and Tar archives.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles the main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles the test classes.
Documentation tasks
-------------------
groovydoc - Generates the groovydoc for the main source code.
javadoc - Generates the javadoc for the main source code.
Help tasks
----------
dependencies - Displays the dependencies of root project 'gcontracts'.
help - Displays a help message
projects - Displays the sub-projects of root project 'gcontracts'.
properties - Displays the properties of root project 'gcontracts'.
tasks - Displays the tasks in root project 'gcontracts'.
Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.
Rules
-----
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.
Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.
Pattern: clean<TaskName>: Cleans the output files of a task.
To see all tasks and more detail, run with --all.
BUILD SUCCESSFUL
Total time: 4.212 secs
It gets trickier
The next build tasks are to configure signing and uploading the archives to the OSS Sonatype Nexus staging repository. Signing is done via GPG and luckily there is a third party plugin for that use-case: the GPG Gradle plugin [5]. To use the GPG plugin we need to add a separate build script dependency:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'de.huxhorn.gradle:de.huxhorn.gradle.pgp-plugin:0.0.3'
}
}
apply plugin: de.huxhorn.gradle.pgp.PgpPlugin
PgpPlugin
looks for the uploadArchives
task (which will be part of the Gradle Maven plugin which is used for uploading the archive) and adds signing of all the affected artifacts before the deployment is executed. In order to let the plugin sign correctly, we need to specify some additional meta-data:
pgp {
secretKeyRingFile = new File("${System.properties['user.home']}/.gnupg/secring.gpg")
keyId = '740A1840'
password = 'youShouldNotDoThis'
}
gradle.properties
file, located in the .gradle
directory - this would be a better place for keyId
and password
.
The last requirement is uploading the signed artifacts to the OSS Sonatype Nexus staging repository. Therefore, we need to apply the Maven Gradle plugin [6] and specify additional data which will be part of the generated pom.xml
.
apply plugin: 'maven'
// ...
uploadArchives {
repositories.mavenDeployer {
configuration = configurations.archives
repository(url: "http://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: nexusUsername, password: nexusPassword)
}
pom.project {
name 'GContracts - Contract-Oriented Programming for Groovy'
packaging 'jar'
description 'GContracts provides annotations to implement interface contracts aka design by contract in Groovy classes.'
url 'http://github.com/andresteingress/gcontracts'
inceptionYear '2010'
scm {
url 'scm:git:file:///Users/andre/Development/gcontracts'
connection 'scm:git:file:///Users/andre/Development/gcontracts'
}
licenses {
license {
name 'BSD License'
comments """
/**
Copyright (c) 2010, gcontracts@me.com
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
following conditions are met:
1.) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
disclaimer.
2.) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with the distribution.
3.) Neither the name of Andre Steingress nor the names of its contributors may be used to endorse or
promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/
"""
distribution 'repo'
}
}
developers {
developer {
id 'andre'
name 'Andre Steingress'
email 'me@andresteingress.com'
url 'http://www.andresteingress.com'
// see: http://www.mail-archive.com/user@gradle.codehaus.org/msg05368.html
// organization 'Andre Steingress'
// organizationUrl 'http://www.andresteingress.com'
roles{
role 'Developer'
}
timezone '+1'
}
}
}
}
gradle uploadArchives
now compiles, tests, packages, signs and deploys the GContracts jar to the staging repository.
Our last step is to deploy two more signed Jar files: sources and javadocs. This can be easily done by extending the archives
configuration with two custom tasks:
// custom tasks for creating source/javadoc jars
task sourcesJar(type: Jar, dependsOn:classes) {
classifier = 'sources'
from sourceSets.main.allSource
}
task javadocJar(type: Jar, dependsOn:javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
// add javadoc/source jar tasks as artifacts
artifacts {
archives sourcesJar
archives javadocJar
}
Jar
. A task type definition specifies custom properties and behavior which can be inherited for custom tasks. Finally, both Task
object references are added to the artifacts of the current project.
Executing gradle uploadArchives
now leads to the following result:
:compileJava UP-TO-DATE
:compileGroovy UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:javadoc
:javadocJar
:sourcesJar
:uploadArchives
Uploading: org/gcontracts/gcontracts/1.1.3/gcontracts-1.1.3.jar to repository remote at http://oss.sonatype.org/service/local/staging/deploy/maven2/
Transferring 35K from remote
Uploaded 35K
Uploading: org/gcontracts/gcontracts/1.1.3/gcontracts-1.1.3.pom.asc to repository remote at http://oss.sonatype.org/service/local/staging/deploy/maven2/
Transferring 1K from remote
Uploaded 1K
Uploading: org/gcontracts/gcontracts/1.1.3/gcontracts-1.1.3-sources.jar to repository remote at http://oss.sonatype.org/service/local/staging/deploy/maven2/
Transferring 42K from remote
Uploaded 42K
Uploading: org/gcontracts/gcontracts/1.1.3/gcontracts-1.1.3.jar.asc to repository remote at http://oss.sonatype.org/service/local/staging/deploy/maven2/
Transferring 1K from remote
Uploaded 1K
Uploading: org/gcontracts/gcontracts/1.1.3/gcontracts-1.1.3-javadoc.jar to repository remote at http://oss.sonatype.org/service/local/staging/deploy/maven2/
Transferring 101K from remote
Uploaded 101K
Uploading: org/gcontracts/gcontracts/1.1.3/gcontracts-1.1.3-javadoc.jar.asc to repository remote at http://oss.sonatype.org/service/local/staging/deploy/maven2/
Transferring 1K from remote
Uploaded 1K
Uploading: org/gcontracts/gcontracts/1.1.3/gcontracts-1.1.3-sources.jar.asc to repository remote at http://oss.sonatype.org/service/local/staging/deploy/maven2/
Transferring 1K from remote
Uploaded 1K
BUILD SUCCESSFUL