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-plugin
will have version0.0.6
- all error prone dependencies (in group
com.google.errorprone
with nameerror_prone
orerror_prone_annotations
for example) will have the same version - all Guice dependencies (in groups
com.google.inject
andcom.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.