Over the last couple of weeks I had to get up to speed on gradle. We got inspired to try out Gradle at work following an great talk on Groovy for SysAdmins by Dan Woods at the Groovy & Grails eXchange 2013, during which Groovy and Gradle were used as a mechanism for building and deploying VMs.
First off, I seem to love Gradle! The build.gradle files are so slimline and readable. The challenges for the last couple of weeks were:
- How to manage releases in a similar way to maven
- How to include common elements in our builds (parent pom functionality
Gradle Release plugin
We decided to use the townsfolk gradle-release plugin as it seems to do pretty much what Maven release has been doing for us: check for snapshot dependencies, check version control is up to date, set version, build, tag, set next snapshot and commit.
To use the plugin, the dependency has to be made available to the build.gradle file itself. Standard dependencies are only available to the project being built. This is where the buildscript block comes in – dependencies declared here can be used in the groovy script itself.
buildscript{
repositories {
mavenCentral()
maven { url "https://oss.sonatype.org/content/groups/public"}
}
dependencies {
classpath 'com.github.townsfolk:gradle-release:1.2'
}
}
The one thing that the plugin does not do is upload artifacts to a maven repository, but this is easy as Gradle allows you to declare dependencies between tasks. We add a dependency on the uploadArchives task, which comes from the maven plugin:
createReleaseTag.dependsOn uploadArchives
A couple of gotchas here. When using this with a multi-module project, you can only apply the plugin to the parent project, and all modules have to have the same version. We found initially that only the parent project was getting deployed to artifactory. The task dependency must be modified as follows for multi-module projects:
createReleaseTag.dependsOn allprojects.uploadArchives
Extracting common gradle elements to use across projects
The parent pom we use for maven projects has common plugin configurations, standard dependencies and so on. We decided to try and create a similar “parent” gradle file, to use across all our projects. The apply from syntax can be used to configure the project with an external script:
apply from: 'https://artifactory.url/../gradle-common-1.0.gradle'
Notice that the apply from is coming from a maven repository. We used the release plugin described above to release a common gradle file as a maven artifact, so this can managed following all our standard release practices too!
The common gradle file can contain all of the gradle build language, including applying other scripts. One thing it cannot contain is a buildscript block. To include common configuration in a buildscript block, you must create a second common file, containing a project.buildscript block:
project.buildscript {
repositories {
mavenCentral()
maven { url "https://oss.sonatype.org/content/groups/public"}
}
dependencies {
classpath 'com.github.townsfolk:gradle-release:1.2'
}
}
Within the project, apply this script within the project buildscript block:
buildscript {
apply from: 'https://artifactory.url/../gradle-common-buildscript-1.0.gradle'
}
This seems like doubling up to me, but is how it appears to work. It means we have two common gradle files to apply separately.
One final gotcha I found when moving the release plugin configuration into our common gradle files – when I tried to configure the task dependency in the external script, I got this error when building multi-module projects:
Could not find property 'uploadArchives' on project ':submodule'.
This seems to be because the parent is trying to configure the dependency on the subproject uploadArchives task, but the subproject does not yet have that task. It must be to do with the order in which gradle applies external scripts? Anyway, the solution was to delay evaluation of the dependency (those clever gradle folk think of everything!). Putting the dependency in a closure does this (Found this at #3 here 12-new-things-i-learned-from-a-three-day-gradle-training. So in the main common gradle file I have these lines, which apply the release plugin to the parent project only and create a lazy dependency on the uploadArchives task for all modules.
if (!project.parent) {
apply plugin: 'release'
project.createReleaseTag.dependsOn { allprojects.uploadArchives }
}
And that’s it, infrastructure in place!
(Credit for setting a lot of this up goes to JCDubs but I was so excited when I started working with it I had to write up what we learned on the way. I’m looking forward to lots more XML free building…)