Hi all,
When I first used Gradle a year ago (or so), the first thing I missed (coming from Maven) was a centralized way of defining dependency versions for a (multi-module) project, similar to Maven’s <dependencyManagement>. I initially started doing like many and using variables and referencing them from the projects’ dependencies; e.g.
def fooVersion = '1.0'
ext.libs = [
someDependency: "com.example:some-dependency:$fooVersion",
otherDependency: dependencies.create("com.example:other-dependency:$fooVersion") {
exclude module: 'slf4j-simple'
},
yetAnotherDep: [ 'com.example:yet-another:2.0', 'com.example:some-optional-dep:3.0' ]
]
dependencies {
compile libs.someDependency
test libs.otherDependency
runtime libs.yetAnotherDep
}
For a few months, I’ve started using a new (to me) approach that has the advantage of applying to the buildscript too, and managing versions of transitive dependencies (and easy replacement of dependencies). I’d like to know what you think about it.
I create a file called dependency-management.gradle where I’ll put all my versions and rules (see below), and I apply it to all projects and their buildscripts with the following snippet in my root build.gradle:
buildscript {
apply from: "$rootDir/dependency-management.gradle", to: it
dependencies {
classpath 'net.ltgt.gradle:gradle-errorprone-plugin'
}
}
subprojects*.buildscript {
apply from: "$rootDir/dependency-management.gradle", to: it
}
allprojects {
apply from: "$rootDir/dependency-management.gradle"
}
Dependencies are then declared without version in my projects (even if they had a version, it would be overwritten), and that also applies to classpath dependencies in buildscript (as you can see above)
Here’s what my dependency-management.gradle looks like:
// Note: this script is applied both to Project and ScriptHandler.
// Beware then to only use properties common to both objects,
// specifically 'configurations', 'dependencies', and 'repositories'.
repositories {
mavenCentral()
}
dependencies.modules {
module('javax.ws.rs:jsr311-api') {
replacedBy('org.jboss.resteasy:jaxrs-api')
}
}
configurations.all {
exclude group: 'javax.ws.rs', module: 'jsr311-api' // conflicts with org.jboss.resteasy:jaxrs-api
resolutionStrategy {
force 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.6'
// All artifacts in a given groupId and whose artifactId has a given prefix: "foo.bar:baz-*" or "foo.bar:baz_*"
// (note: matches "foo.bar:baz" and "foo.bar:baz-quux")
def artifactPrefixInGroup = [
'com.google.errorprone:error_prone': '2.0.2',
]
// All artifacts in a given groupId: "foo.bar:*"
def allArtifactsInGroup = [
'com.google.guava': '18.0',
'org.slf4j': '1.7.12',
'commons-logging': [ group: 'org.slf4j', name: 'jcl-over-slf4j']
]
// All artifacts whose groupId has a given prefix: "foo.bar.*:*"
// (note: matches both "foo.bar:baz" and "foo.bar.baz:quux")
def allArtifactsInGroupPrefix = [
'com.google.inject': '4.0',
]
def selectReplacement
selectReplacement = { group, name ->
def selector = group + ":" + name
def replacement = artifactPrefixInGroup.findResult {
if (selector == it.key || selector.startsWith(it.key + "-") || selector.startsWith(it.key + "_")) return it.value
}
if (!replacement) {
replacement = allArtifactsInGroup[group]
if (!replacement) {
replacement = allArtifactsInGroupPrefix.findResult {
if (group == it.key || group.startsWith(it.key + ".")) return it.value
}
}
}
if (replacement instanceof Map) {
replacement = selectReplacement(replacement.group, replacement.name)
assert replacement : "Recursively selecting a replacement for ${group}:${name} returned null"
} else if (replacement instanceof String) {
// replacement is a version, keep the same 'group' and 'name'
replacement = [
group: group,
name: name,
version: replacement
]
}
return replacement
}
eachDependency { DependencyResolveDetails details ->
def replacement = selectReplacement(details.requested.group, details.requested.name)
if (replacement) {
details.useTarget(replacement)
}
}
}
}
See here how I ensure that:
-
net.ltgt.gradle:gradle-errorprone-pluginwill have version0.0.6 - all error prone dependencies (in group
com.google.errorpronewith nameerror_proneorerror_prone_annotationsfor example) will have the same version - all Guice dependencies (in groups
com.google.injectandcom.google.inject.extensions) will have the same version - similarly, all Slf4j dependencies will have the same version
- all dependencies on
commons-logging:<whatever>will be replaced with a dependency onorg.slf4,:jcl-over-slf4j(whose version is then –recursively– resolved to1.7.12)
I also used global exclude and replacement rules, all centralized in the same file.