Ability to also *replace* artifacts
boris-petrov opened this issue · 7 comments
Thanks for the new release!
I've been having this issue since the beginning of my using of the plugin and I'm really not sure what's the best way to tackle it.
I have different configurations which have different dependencies. In some of them, for example, I have a transitive dependency on bcprov-jdk15on
; in other configurations I have a transitive dependency on bcprov-jdk18on
; and in third ones I have transitive dependencies on both bcprov-jdk15on
and bcprov-jdk18on
. I'm using the io.fuchs.gradle.classpath-collision-detector
(as you suggested in your last video :) ) which complains about a conflict because I've got both bcprov-jdk15on
and bcprov-jdk18on
(in different configurations - the two ones in the same configuration are handled correctly by java-ecosystem-capabilities
). What I've been doing is as follows:
dependencySubstitution.all { dependency ->
def requested = dependency.requested
if (requested instanceof ModuleComponentSelector) {
if (requested.group == 'org.bouncycastle' && requested.module.endsWith('-jdk15on')) {
dependency.useTarget('org.bouncycastle:' + requested.module.replace('jdk15on', 'jdk18on') + ':1.72')
}
}
}
The question is if it's somehow possible for this to be done automatically by the plugin or there could be some option for it?
Thanks for all the suggestions for new rules @boris-petrov.
It sounds to me like you are combining multiple configurations in your setup by adding the results of the resolution.
For example, if you do something like this configurations.conf1 + configurations.conf2
, Gradle will individually resolve conf1
and conf2
and only in the end put all Jars together. Then it won't detect conflicts between these individual sets.
What you should do instead is combining the configurations to one and do only one resolution. Then this plugin should detect the conflict for you and, to stick with the example, select only one bcprov
.
Instead of doing something like:
jars = configurations.conf1 + configurations.conf2
You do:
configurations.create('combinedConf') {
extendsFrom(configurations.conf1)
extendsFrom(configurations.conf2)
}
jars = configurations.combinedConf
@jjohannes thanks for the support! I see what you mean... actually I believe (one of) my problem is in my configuration of the detectCollisions
plugin. I have a few different separate configurations - one for production
, one for integration-tests
. I've added both to the configurations
of detectCollisions
- which I guess is wrong (as they have conflicting dependencies as you see). Perhaps I should have two different tasks - one to detect collisions in production
and the other for the tests. Am I correct?
But what I requested initially I believe still makes sense for a number of reasons:
-
Consistency of used artifacts between different configurations. Say my example of
production
and tests. If one hasjdk15on
via some transitive dependency and the other has bothjdk15on
andjdk18on
(which would resolve tojdk18on
thanks to your plugin) that would be inconsistent and could lead to different runtime behavior. -
Take the
javax
vsjakarta
madness for example. Say I have aweb
sourceset which depends on some Jakarta stuff. And I have atest
sourceset which doesn't depend onweb
(because it only tests the backend stuff) - it might have only Javax dependencies in it. Which means that different resulting JARs (the one for production and the one for testing) will be in different worlds. Similar to #6. -
Similarity with the logging-capabilities plugin. I believe there, when you specify say
enforceLogback
, it does exactly what I suggest here - it replaces all "unwanted" artifacts with the "correct"/"better" alternative. That's for logging but the same could work for other things too.
Note that I'm absolutely not sure I'm right to think these things. I'm just thinking out loud. If you think what I say doesn't make sense, please let me know and we can close the issue. :) Thanks again for the hard work!
Regarding detectCollisions plugin: Yes, if you want to check multiple classpaths in one project, you need to register additional DetectCollisionsTask
s. E.g.:
tasks.register("detectTestCollisions", DetectCollisionsTask) {
configurations.from(project.configurations.testRuntimeClasspath)
}
Regarding the dependency substitution: Yes, you are right here as well. It is a (kind of unsolved) problem that you don't get the desired result if there is no conflict. Hence, you easily get into the situation where the conflict appears on your runtime classpath, but not on some of your compile classpaths. I think it would be great if there could be a feature in Gradle similar to "Consistent Resolution", which somehow automatically solves this. With consistent resolution you can, if you set it up like here, make sure that all versions (and with that all version conflicts) appear on all classpaths.
For this plugin, I imagined initially that it only provide metadata - i.e. that everything the plugin does is adding information that could also be published as part of the metadata. But yes, the information we have could possibly also be used to create substitution rules to get the desired consistency – similar as in the logging-capabilities plugin.
Right now, this plugin already does more than just providing metadata as it also provides resolution strategies (see also comment here #36 (comment)). Maybe we could think about providing three plugins, so that we have one "pure metadata rules" plugin and offer plugins for the resolution strategies and for substitution rules in addition:
org.gradlex:java-ecosystem-capabilities
Just the metadata rules (no more resolution strategies)org.gradlex:java-ecosystem-capabilities-resolution-strategies
Adds the default resolution strategies (if desired)org.gradlex:java-ecosystem-capabilities-subtitution-rules
Adds substitution rules to ensure consistency (this would be new, have to figure out if this is somehow "automatically" doable based on the information we already have in the plugin)
Thank you being open-minded about this and considering it!
I agree with everything you said and would like to see very much these three plugins come to life! :) I'm wondering something though... why would anyone not want the default resolution strategies
? Why would anyone want only the metadata rules? What's the use-case for that?
There are cases where there is no clear default for every context. Especially when alternatives overlap, but are not exactly the same. Just taking the highest version (or selecting by some other general criteria) might not be the right for every context. And then a problematic conflict can go unnoticed. Which is an argument for adding all resolution strategies yourself in your build for your dependency graph(s). So that your are aware of all conflicts which you handle explicitly. (That's also a reason why Gradle itself does not have a default, like select highest version, built-in for capability conflicts.)
There are many examples already:
org.bouncycastle:bcpkix-jdk14
ororg.bouncycastle:bcpkix-jdk15on
?javax.mail
orcom.sun.mail:javax.mail
?- ...
So I guess the best would be to provide default rules for the "obvious" ones and not provide rules for the unknown ones. I definitely think that providing defaults is great because in most cases people won't have to think - just apply the plugin and continue with your life (that's answering your question in #36).