Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to also *replace* artifacts #35

Open
boris-petrov opened this issue Jan 30, 2023 · 6 comments
Open

Ability to also *replace* artifacts #35

boris-petrov opened this issue Jan 30, 2023 · 6 comments
Labels
a:enhancement New feature or request

Comments

@boris-petrov
Copy link
Contributor

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?

@jjohannes
Copy link
Member

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 jjohannes added the a:question Further information is requested label Feb 9, 2023
@boris-petrov
Copy link
Contributor Author

boris-petrov commented Feb 9, 2023

@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:

  1. Consistency of used artifacts between different configurations. Say my example of production and tests. If one has jdk15on via some transitive dependency and the other has both jdk15on and jdk18on (which would resolve to jdk18on thanks to your plugin) that would be inconsistent and could lead to different runtime behavior.

  2. Take the javax vs jakarta madness for example. Say I have a web sourceset which depends on some Jakarta stuff. And I have a test sourceset which doesn't depend on web (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 Add rule that prevents all dependencies to javax... and early jakarta.... modules to be upgraded to a Jakarta version with the new package #6.

  3. 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!

@jjohannes
Copy link
Member

Regarding detectCollisions plugin: Yes, if you want to check multiple classpaths in one project, you need to register additional DetectCollisionsTasks. 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)

@jjohannes jjohannes added a:enhancement New feature or request and removed a:question Further information is requested labels Feb 21, 2023
@boris-petrov
Copy link
Contributor Author

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?

@jjohannes
Copy link
Member

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 or org.bouncycastle:bcpkix-jdk15on?
  • javax.mail or com.sun.mail:javax.mail?
  • ...

@boris-petrov
Copy link
Contributor Author

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants