Something fishy with transitive dependencies


(kevin.loveland) #1

I’ve been struggling with this for a few hours and it has just got me stumped how transitive dependency resolution is working in Gradle.

I am in the process of converting a project from Maven to Gradle. The end result is to generate a .war with the same versions as the Maven build. Simple enough, right?

Here is the problem that has me stumped. I need to remove ‘org.apache.geronimo.specs:geronimo-javamail_1.4_spec:1.7.1’ from the runtime configuration. However, I have two cxf libraries which declare it as a dependency and the seem to exclusions fail.

So my dependencies declared like this:

dependencies {
 compile(group: 'org.apache.cxf', name: 'cxf-rt-core', version: '2.6.10')
}

The runtime dependency tree looks like this:

runtime - Runtime classpath for source set 'main'.
+--- org.apache.cxf:cxf-rt-core:2.6.10
|
  +--- org.apache.cxf:cxf-api:2.6.10
|
  |
  +--- org.apache.geronimo.specs:geronimo-javamail_1.4_spec:1.7.1
|
  |
  \--- wsdl4j:wsdl4j:1.6.3
|
  +--- org.apache.ws.xmlschema:xmlschema-core:2.0.3
|
  \--- org.apache.geronimo.specs:geronimo-javamail_1.4_spec:1.7.1

When I declare an exclusion in my dependencies:

dependencies {
 compile(group: 'org.apache.cxf', name: 'cxf-rt-core', version: '2.6.10') {
  exclude(module: 'geronimo-javamail_1.4_spec')
 }
}

All is good:

runtime - Runtime classpath for source set 'main'.
+--- org.apache.cxf:cxf-rt-core:2.6.10
|
  +--- org.apache.cxf:cxf-api:2.6.10
|
  |
  \--- wsdl4j:wsdl4j:1.6.3
|
  \--- org.apache.ws.xmlschema:xmlschema-core:2.0.3

I don’t run into problems until I add an additional cxf dependency.

dependencies {
 compile(group: 'org.apache.cxf', name: 'cxf-rt-core', version: '2.6.10') {
  exclude(module: 'geronimo-javamail_1.4_spec')
 }
 compile(group: 'org.apache.cxf', name: 'cxf-rt-frontend-jaxws', version: '2.6.10')
}

The dependency tree now looks like this:

runtime - Runtime classpath for source set 'main'.
+--- org.apache.cxf:cxf-rt-core:2.6.10
|
  +--- org.apache.cxf:cxf-api:2.6.10
|
  |
  +--- org.apache.geronimo.specs:geronimo-javamail_1.4_spec:1.7.1
|
  |
  \--- wsdl4j:wsdl4j:1.6.3
|
  +--- org.apache.ws.xmlschema:xmlschema-core:2.0.3
|
  \--- org.apache.geronimo.specs:geronimo-javamail_1.4_spec:1.7.1
+--- org.apache.cxf:cxf-rt-frontend-jaxws:2.6.10
|
  +--- org.apache.cxf:cxf-api:2.6.10 (*)
|
  +--- org.apache.cxf:cxf-rt-core:2.6.10 (*)
|
  +--- org.apache.cxf:cxf-rt-bindings-soap:2.6.10

I’m confused by the report. Since my exclusion is on the ‘cxf-rt-core’, it seems like the report is incorrect or at least somewhat deceiving in helping me track down my problem. However, I just want to solve my issue, so I’ll ignore the where the report identifies the dependency on ‘geronimo’.

Since both cxf-rt-core’ in ‘cxf-rt-frontend-jaws’ declare a dependency on 'cxf-api, it is unclear whether the dependency on ‘geronimo’ is coming from ‘cxf-rt-core’ or ‘cxf-rt-frontend-jaws’. So as a test, I intentionally exclude ‘cxf-api’ from both dependencies.

dependencies {
 compile(group: 'org.apache.cxf', name: 'cxf-rt-core', version: '2.6.10') {
  exclude(module: 'geronimo-javamail_1.4_spec')
  exclude(module: 'cxf-api')
 }
 compile(group: 'org.apache.cxf', name: 'cxf-rt-frontend-jaxws', version: '2.6.10') {
  exclude(module: 'cxf-api')
        }
}

The dependency tree now looks like this:

runtime - Runtime classpath for source set 'main'.
+--- org.apache.cxf:cxf-rt-core:2.6.10
|
  +--- org.apache.ws.xmlschema:xmlschema-core:2.0.3
|
  \--- org.apache.geronimo.specs:geronimo-javamail_1.4_spec:1.7.1
+--- org.apache.cxf:cxf-rt-frontend-jaxws:2.6.10
|
  +--- org.apache.cxf:cxf-rt-core:2.6.10 (*)
|
  +--- org.apache.cxf:cxf-rt-bindings-soap:2.6.10

I’m not sure what I’m missing here, but this seems fishy to me. I would expect the dependency for ‘cxf-rt-core’ explicitly declared in my dependencies to take precedence over the transitive dependency ‘cxf-rt-core’ declared by ‘cxf-rt-frontend-jaxws’.

Just to make sure that there was not yet another dependency somewhere in ‘cxf-rt-frontend-jaxws’, I excluded the ‘cxf-rt-core’.

compile(group: 'org.apache.cxf', name: 'cxf-rt-core', version: '2.6.10') {
  exclude(module: 'geronimo-javamail_1.4_spec')
  exclude(module: 'cxf-api')
 }
 compile(group: 'org.apache.cxf', name: 'cxf-rt-frontend-jaxws', version: '2.6.10') {
  exclude(module: 'cxf-api')
  exclude(module: 'cxf-rt-core')
 }

The dependency tree now looks like what I would expect:

runtime - Runtime classpath for source set 'main'.
+--- org.apache.cxf:cxf-rt-core:2.6.10
|
  \--- org.apache.ws.xmlschema:xmlschema-core:2.0.3
+--- org.apache.cxf:cxf-rt-frontend-jaxws:2.6.10
|
  +--- org.apache.cxf:cxf-rt-bindings-soap:2.6.10

Here is the question: How the reconciliation of attributes resolved when the dependency with identical version numbers occurs multiple times in the dependency tree? Do I need to insure that each dependency only occurs once within the dependency tree if I declare any additional attributes on a dependency? Which dependency is actually used? The first one declared? The last one declared? The nearest dependency?


(Peter Niederwieser) #2

As far as I know, a per-dependency exclude only affects the subtree of that direct dependency. In most cases, it’s more useful to exclude a dependency from a whole configuration (‘configurations.compile { exclude module: ‘geronimo-javamail_1.4_spec’ }’), or even from all configurations (‘configurations.all { exclude module: ‘geronimo-javamail_1.4_spec’ }’).


(kevin.loveland) #3

Conceptually I understand that Gradle recommends you declare exclusions on a per-configuration basis. In fact, the Gradle documentation in 50.4.7. Excluding transitive dependencies states:

Should you exclude per-dependency or per-configuration? It turns out that in majority of cases you want to use the per-configuration exclusion.

However, dependencies exclusions are transitive and configuration exclusions are not. In other words, if the right answer is to declare the exclusion in the configuration, I can only do those in the final consumer of a project rather than the project itself where I think it belongs.


(Peter Niederwieser) #4

However, dependencies exclusions are transitive and configuration exclusions are not.

How did you come to this conclusion? Both should be transitive.


(kevin.loveland) #5

You are right. Please excuse the error.

I was observing the dependency tree of the end state war project and a core library project. What I failed to realize was a very similarly-named, intermediate project between the two. The intermediate project was also including some cxf dependencies and therefore pulling in the geronimo dependencies.

I ran further tests and confirmed your statement. The configuration is in fact transitive.

THANKS!