Hi @dsilvasc,
This question actually mixes different problems, and it’s a great opportunity for me to introduce some recent changes in the dependency management engine (available in 4.7) that we’re working on. Most of them are not publicized yet because we need to gather feedback, so we’d appreciate if you could try it out.
Capabilities
The first problem you have is that you have several implementations of JSR-305 on the dependency graph. This is a very common problem, and a painful one. Loggers and their plethora of bindings is another one. For this, we separate conceptually things in 2 categories:
- realizing that you have a problem
- fixing the problem
For 1., we introduced the concept of “capability”. com.google.code.findbugs:jsr305
, net.jcip:jcip-annotations
, com.github.stephenc.jcip:jcip-annotations
and com.google.code.findbugs:annotations
are all things that provide the same capability (jsr305). This is something that should be declared, and, ideally, published by the authors of those libraries. Because we live in an unperfect world, and because Gradle metadata is not yet ready for prime time, the horrible truth is that they can’t explain this. Worry no more, we’re going to fix this. Gradle now provides the ability to modify published metadata on the consumer side. We call this metadata rules. Those rules can be used to “patch” external module metadata with knowledge which wasn’t available at publication time. So the idea here will be to declare a capability on all those modules.
A capability is similar to a module. It has coordinates (group, artifact, version) and by default, any module provides a capability corresponding to its GAV (so, the module com:foo:1.0
provides the capability com:foo:1.0
). Capabilities participate in conflict resolution. So, what we want to say is that com.google.code.findbugs:jsr305
, net.jcip:jcip-annotations
, com.github.stephenc.jcip:jcip-annotations
and com.google.code.findbugs:annotations
all provide the jsr305 capability. Obviously, there’s no such thing yet, so we’re going to declare an arbitrary one: jsr:jsr305:1.0
. We can do this using metadata rules:
dependencies {
components {
['com.google.code.findbugs:jsr305', 'net.jcip:jcip-annotations', 'com.github.stephenc.jcip:jcip-annotations', 'com.google.code.findbugs:annotations'].each { provider ->
withModule(provider) { details ->
allVariants {
withCapabilities {
addCapability('jsr', 'jsr305', '1.0')
}
}
}
}
}
}
Now if you run your build again, you will notice that Gradle fails as soon as it finds 2 of those modules on the classpath. Take this sample build script:
plugins {
id 'java-library'
}
repositories {
jcenter()
}
dependencies {
components {
['com.google.code.findbugs:jsr305', 'net.jcip:jcip-annotations', 'com.github.stephenc.jcip:jcip-annotations', 'com.google.code.findbugs:annotations'].each { provider ->
withModule(provider) { details ->
allVariants {
withCapabilities {
addCapability('jsr', 'jsr305', '1.0')
}
}
}
}
}
implementation "com.google.code.findbugs:jsr305:2.0.1"
implementation "net.jcip:jcip-annotations:1.0"
}
And let’s run gradle dependencies
. The build will now fail with an error saying that both libraries provide the same capability and that you have to choose.
So at this point, you might wonder why we did this, because in the end Gradle fails, and it’s not nice. Well, we did this precisely for that. Imagine that you didn’t have to write the rules above, because all of the providers live in the Gradle world, which publishes the capabilities. Then, as soon as you add a dependency that introduces a conflict, we find it!. So you can take this as a pre-emptive failure. The nice thing is that the error message tells you what happens.
Ok so now, we need to fix the problem. There are several ways to do it. The first one is the one you have done. I think it’s a good solution, and we also do this internally at Gradle. But another option is to rely on capabilities again. By default, capabilities are upgraded to the latest version. So if one of the providers has a higher version, it’s going to be selected. So you could change the rule to put 1.1
on com.google.code.findbugs:jsr305
, and leave 1.0
for the others. This way your preferred implementation is going to be selected automatically by Gradle! (Soon the build scan will also tell you that it was selected because it provides a higher version of the capability).
Another option would be to change the metadata of all components to remove the dependencies on the other implementations. The same dependencies.components.all { ... }
API can be used to do this, but let’s move on to the next problem.
Strict constraints
You are saying:
I also need to make sure that certain dependencies or groups of dependencies use some exact versions
Again, this is a requirement that we want you to be able to express. For this, we have richer version constraints. Your build script would typically say:
dependencies {
implementation("io.grpc:grpc-core") {
version {
strictly "1.11.0"
}
because "whatever reason why it doesn't work on other versions"
}
}
If you write this, you’re explaining to Gradle that if it ever finds, at any point in time, a transitive dependency which disagrees with this 1.11.0
, then the build is going to fail. Again, this is just pre-emptive, meaning that you declare your requirements, but as soon as the build fails, you have to fix it.
For that, you have used force
, but a better solution would be to use, now the component metadata rules again. Because you know that your project only works with 1.11.0
and that you thoughtfully want to ignore whatever version other components want (you take the responsibility), you can “fix” the metadata of those components:
dependencies {
components.all {
allVariants {
withDependencies { deps ->
deps.each { dep ->
if (dep.group == 'io.grpc' && dep.name =='grpc-core') {
dep.version {
prefer "1.11.0"
}
dep.because "We only work with 1.11.0"
}
}
}
}
}
}
Note that component metadata rules work independently of the configuration. So if you need a different behavior depending on the configuration you resolve, they are not the right answer. If that’s the case, let us know.