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

Version Resolution is wrong in some cases when a dependency uses dependencyManagement in it's pom.xml #1812

Closed
ptriller opened this issue Apr 12, 2017 · 20 comments
Labels

Comments

@ptriller
Copy link

In some cases when a dependency is included transitively and a dependencyManagement section changes the version in another pom, then gradle resolves the resulting Version differently from Maven

Example:

Project A has a dependency to artifact D in version 2
Project B has a dependency to the Artifact from Project A and a dependencyManagement of Artifact D to version 1 (in it's pom.xml)
Project C has a dependency to the Artifact of Project B.

If you do this with maven, then Project C has a transitive dependency to D in version 1
If you do it with gradle then Project C has a transitive dependency to D in version 2

I expected the versions to be the same. In my eyes the Maven behavior makes more sense.

To reproduce see this simple test setup:

https://github.com/ptriller/gradle-bugreport

@ptriller ptriller changed the title Vefrsion Resolution is wrong when a dependency uses dependencyManagement in it's pom.xml Version Resolution is wrong in some cases when a dependency uses dependencyManagement in it's pom.xml Apr 12, 2017
@oehme oehme changed the title Version Resolution is wrong in some cases when a dependency uses dependencyManagement in it's pom.xml Treat imported BOMs as constraints on the importing module Sep 20, 2017
@oehme oehme added a:feature A new functionality and removed a:question labels Sep 20, 2017
@oehme
Copy link
Contributor

oehme commented Sep 20, 2017

This is a limitation in how we currently treat BOMs (we only apply them to the direct dependencies of a module). Instead, we should add the management section of the BOM as constraints on the module that imports that BOM.

@bigdaz
Copy link
Member

bigdaz commented Dec 21, 2017

There are 2 issues here:

  1. POM <dependencyManagement> elements do not apply transitively, they only apply to dependencies in the declaring module. We think this is the same behaviour as Maven, but we are considering making these apply transitively
  2. A dependency constraint cannot downgrade a dependency version for a transitive dependency.

We are working to improve things in this space, but our aim is not to perfectly replicate the Maven resolution behaviour.

@ljacomet ljacomet changed the title Treat imported BOMs as constraints on the importing module Treat imported BOMs as force constraints on the importing module Aug 21, 2018
@ljacomet ljacomet changed the title Treat imported BOMs as force constraints on the importing module Allow importing BOMs as force constraints Aug 21, 2018
@ljacomet
Copy link
Member

Edited title to reflect required change to current Gradle behavior with IMPROVED_POM_SUPPORT

@ljacomet ljacomet added this to the 5.0 RC1 milestone Aug 21, 2018
@ljacomet
Copy link
Member

ljacomet commented Sep 4, 2018

This feature will be part of the BOM support in Gradle 5.0 - see #4422 for details

@ljacomet
Copy link
Member

This will be available in Gradle 5.0 through enforcedPlatform, watch release notes.

@sofax
Copy link

sofax commented Jan 9, 2019

I don't see how this issue has been fixed with Gradle 5.x - it still exists.

I have a Gradle build script that lists a couple of Maven artifacts. The script has a Copy task that copies all the direct and transitive dependencies to the folder "deps".

The situation here is the same as in the original issue description, i.e. I get artifact D in version 2 instead of version 1.

@sofax
Copy link

sofax commented Jan 9, 2019

BTW, the original title of the issue as reported by @ptriller was "Version Resolution is wrong in some cases when a dependency uses dependencyManagement in it's pom.xml".

For reasons I don't know it has been modified (by @oehme, and then by @ljacomet ). The new title, "Allow importing BOMs as force constraints", has nothing to do with the actual issue. Misunderstanding?

@oehme
Copy link
Contributor

oehme commented Jan 9, 2019

You're right, this was misclassified.

However, the original issue is not correct either - Maven behaves exactly the same if you have a dependency chain like that. <dependencyManagement> on transitive dependencies in project B is ignored by project C. The attached example project doesn't show the project C case with Maven. If it did, you would see that it behaves the same as Gradle.

@oehme oehme changed the title Allow importing BOMs as force constraints Version Resolution is wrong in some cases when a dependency uses dependencyManagement in it's pom.xml Jan 9, 2019
@oehme oehme added a:bug and removed a:feature A new functionality labels Jan 9, 2019
@oehme oehme removed this from the 5.0 M1 milestone Jan 9, 2019
@sofax
Copy link

sofax commented Jan 9, 2019

You're right, this was misclassified.

However, the original issue is not correct either - Maven behaves exactly the same if you have a dependency chain like that. <dependencyManagement> on transitive dependencies in project B is ignored by project C. The attached example project doesn't show the project C case with Maven. If it did, you would see that it behaves the same as Gradle.

I haven't checked the attached project, but I have encountered this exact problem in our environment, where the setup is exactly as described by the ticket author. Gradle and Maven definitely behave differently here, and the Maven behavior is the correct one.

The concrete situation is:
We have a custom artifact (let's call it my-artifact) that uses HK2 v2.5.0-b63 (e.g. org.glassfish.hk2:hk2-locator:2.5.0-b63). HK2 needs jakarta.annotation-api-1.3.4.jar, which had not been released yet when HK2 v2.5.0-b63 was released. So we added the following section to the pom.xml of my-artifact:

   <dependencyManagement>
      <dependencies>
         <!-- Temporary workaround for https://github.com/eclipse-ee4j/glassfish-hk2/issues/432 -->
         <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>1.3.3</version>
         </dependency>
      </dependencies>
   </dependencyManagement>

Thus my-artifact depends on jakarta.annotation-api v1.3.3 (and hence can be built).

Next I created a new Maven artifact (let's call it deps) whose pom.xml lists my-artifact as a dependency. When I invoke mvn dependency:copy-dependencies on that new pom.xml, it copies jakarta.annotation-api-1.3.3.jar, as expected.

Finally I created a Gradle build script that corresponds to the pom.xml of the deps Maven artifact, i.e. it lists my-artifact as a dependency. I had to use a little workaround to be able to use the Copy task for the desired job:

configurations {
   depsToCheck.extendsFrom implementation
}

task copyDeps(type: Copy) {
   from configurations.depsToCheck
   into 'deps'
}

dependencies {
   implementation 'my-company.my-artifact:1.0.0'
}

Now, when I invoke ./gradlew copyDeps, it will copy jakarta.annotation-api-1.3.4.jar instead of jakarta.annotation-api-1.3.3.jar.

@oehme
Copy link
Contributor

oehme commented Jan 9, 2019

Can you create a full reproducible project, including the Maven and Gradle example, please?

@sofax
Copy link

sofax commented Jan 9, 2019

Can you create a full reproducible project, including the Maven and Gradle example, please?

Yes, but that will take a while.

@sofax
Copy link

sofax commented Jan 9, 2019

@oehme
Copy link
Contributor

oehme commented Jan 9, 2019

That's a different issue - The original reporter had no direct dependency on project B (jakarta.annotation-api), but only a dependencyManagement block.

In your case Gradle and Maven differ indeed and this is expected behavior, as Gradle does not use the "just use the first version you see" strategy that Maven uses, but instead considers all versions and does proper conflict resolution. Since one module in the graph requires 1.3.4 and the other 1.3.3, we pick 1.3.4 since it is likely compatible. You can adjust this using Configuration.resolutionStrategy.

We won't change our resolution engine to be exactly like Maven, because we want to support other systems as well.

@sofax
Copy link

sofax commented Jan 9, 2019

@oehme: Can you tell me how the resolution strategy can be configured to enforce the Maven behavior? The Maven pom.xml of demo-artifact explicitly ensures that the correct version of the dependency is used. This must not be (implicitly) overruled by a Gradle script that depends on demo-artifact.

@oehme
Copy link
Contributor

oehme commented Jan 9, 2019

The Maven pom.xml of demo-artifact explicitly ensures that the correct version of the dependency is used.

This is not how Gradle works. demo-artifact can't control the version of annotation-api. It only says which version it would like. But glassfish disagrees and wants a higher one, so Gradle chooses that, since it is more likely to work.

This must not be (implicitly) overruled by a Gradle script that depends on demo-artifact.

There is no overruling here, just two competing dependencies and Gradle chooses the one that it is more likely to work.

Can you tell me how the resolution strategy can be configured to enforce the Maven behavior?

You can't, because Maven's model inherently relies on the order of dependency declarations and just uses the first version it sees, which is a pretty broken strategy that we don't support. Instead Gradle considers all version declarations and conflict resolves them. If the resolution that Gradle picks is not what you'd like, you can use the resolutionStrategy or a force rule to fix it up.

@sofax
Copy link

sofax commented Jan 9, 2019

That won't work in our case. I'm afraid we can't use Gradle then. :(

We need a tool that downloads all the effective dependencies that are actually used by our base technology artifacts, not the ones that Gradle thinks work better (and which, in this case, is obviously the wrong decision).

Thanks for clarifying though.

@ljacomet
Copy link
Member

ljacomet commented Jan 9, 2019

If you need such a strong contract, have a look at defining and consuming a Maven BOM with the enforcedPlatform approach.
Note that Gradle 5.2 will also introduce the java-platform plugin to better support such bill of material definitions inside a Gradle project and give the ability to publish them as Maven BOM.

@sofax
Copy link

sofax commented Jan 9, 2019

Hmm ... I'm not sure how the use of enforcedPlatform can (generally) help me here. I assume it would have to be applied to all the dependencies (i.e. all our base technology artifacts) - how does Gradle deal with potential conflicts then?

But this whole discussion is beginning to make me wonder if there really is a "correct" way to handle this requirement. It would probably be more reasonable to deal with one base artifact at a time instead of processing the whole bunch in one script, as we can never know in which order the base artifacts will be referenced by a product.

@ljacomet
Copy link
Member

ljacomet commented Jan 9, 2019

Defining a BOM would help in having a centralized source for the versions of the dependencies to be used by your product / platform.
Based on that, you would use the Maven BOM in your Maven projects at the different levels, making sure no surprise happens if a new path to a dependency is introduced.
And since the addition of enforcedPlatform to Gradle, you have a way to consume a BOM in a way that is Maven compatible: the platform forces the versions instead of just being a source of recommendations.

@sofax
Copy link

sofax commented Jan 9, 2019

We do have a BOM. However, sometimes artifacts must override transitive dependencies introduced by the BOM.

Anyway, this is a problem I will have to solve. Thanks to both of you for your support! :) 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants