A sustainable pattern with Gradle
This is a highly configurable pattern that I’ve applied to Gradle. I describe how I scale Gradle, avoid duplicated Gradle code and do feature toggling.
I previously blogged about A sustainable pattern with Jenkins. This is that same pattern, but applied to Gradle. The basic idea is that you have:
- Default configuration. Something that works for most projects.
- Given configuration. Something that can optionally be supplied by the project, to turn on/off features.
Everything here is available in this repository:
https://github.com/tomasbjerre/gradle-scripts
This is an ongoing project. When you read this it may have changed, so check the code for latest details. But the general idea, described here, will not change.
The problem
I can divide my projects into 3 different types:
- Java projects, typically a library.
- Violation, Java projects but with some small adjustments that I use in my violations-lib and its tools.
- Gradle plugins.
The Gradle plugins should be published to Maven Central and Gradle Plugin Portal. The Java projects should be published to Maven Central.
All projects should optionally:
- Be signed with PGP.
- Relocate, shadow, dependencies into a fat jar.
- Produce a changelog
- Have Spotbugs, static code analysis, configured and optionally fail build based on violations found.
- Be configured to publish artifacts to Maven Central (along with all POM requirements Central demands). Or any other Nexus server.
These features should be implemented so that I:
- Don’t need to duplicate Gradle code in all my projects.
- Can toggle features.
The solution
I create a Gradle script (main.gradle) that I package into a JAR. I release that JAR to Maven Central. I let the projects add that JAR to classpath, and apply the main.gradle from within that JAR.
The main.gradle has a defaultConfig that is basically a map with a bunch of configuration options.
The build.gradle in a project, can supply a buildConfig, it may look something like this:
apply plugin: 'java'
buildscript {
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
classpath 'se.bjurr.gradle:gradle-scripts:2.+'
}
}
project.ext.buildConfig = [
// Your can supply a given config here, a subset of defaultConfig.
]
apply from: project.buildscript.classLoader.getResource('main.gradle').toURI()dependencies {
// Dependencies of the project here just like you would in any other project
}
The main.gradle will merge its defaultConfig with given buildConfig creating an effectiveConfig that is used throughout the main.gradle script.
My entire defaultConfig is available in the code. Here is a reduced version:
def defaultConfig = [
repoType: "DEFAULT",
staticCodeAnalysis: [
maxViolations: 9999,
],
publishing: [
mavenRepositoryUrl: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/',
nexusCloseAndRelease: true,
sign: true,
relocate: [],
],
gradlePlugin: [
tags: []
],
changelog: [
enabled: true,
],
]
This means I can get the default behavior in my project if I just do:
project.ext.buildConfig = []
apply from: project.buildscript.classLoader.getResource('main.gradle').toURI()
I can also change it, to match my Violation needs, if I do:
project.ext.buildConfig = [
repoType: "VIOLATIONS",
publishing: [
relocate: [
'com.google',
'com.jakewharton'
]
]
]
apply from: project.buildscript.classLoader.getResource('main.gradle').toURI()
Or change it, to match my Gradle plugin needs, if I do:
project.ext.buildConfig = [
repoType: "GRADLE",
gradlePlugin: [
tags: ['violation', 'static code analysis', 'Checkstyle']
],
]
apply from: project.buildscript.classLoader.getResource('main.gradle').toURI()
Closure
Pull requests are very much welcome in the repo:
https://github.com/tomasbjerre/gradle-scripts
My intention is that not only I should be able to use this script.