How can I specify an explicit dependency of a plugin that will track the version of a transitive dependency?


(Ryan Martin) #1

We’ve recently run into some issues in a Java Gradle project with conflicting netty.io versions. Our project uses gRPC (grpc.io) which brings in a transitive dependency on several netty.io libraries via io.grpc:grpc-netty. We also need io.netty:netty-codec-haproxy, which is not included by grpc-netty. Our recent upgrade to grpc-netty:1.0.3 broke our build, because it updated to 4.1.6.Final of netty.io’s core, but we still had netty-codec-haproxy on 4.1.4.Final.

Dynamic versioning won’t work here, because we don’t need the latest version; we need the one that matches whatever grpc-netty uses. Is there a way to find the version being included via transitive dependency of, say, io.netty:netty-common, and apply that version to netty-codec-haproxy?

ext {
grpcVersion = ‘1.0.3’
}

dependencies {
// Server implementation for gRPC (with native openssl)
compile “io.grpc:grpc-netty:$grpcVersion”,
“io.netty:netty-tcnative-boringssl-static:1.1.33.Fork19:${osdetector.classifier}”

// HAProxy protocol support
compile group: 'io.netty', name: 'netty-codec-haproxy', version: '4.1.4.Final'

(Chris Doré) #2

As a quick fix, if you know the required version, you can use ResolutionStrategy.force.


(Ryan Martin) #3

Thanks, Chris, but that’s not really superior to just including the extra dependency explicitly. I still have to either examine the incoming POM for the new grpc-netty, or else let the gradle build run & see what the transitive dependency resolver brings back, and then manually copy that over to the netty-code-haproxy dependency version. I’m looking for something that reduces the number of separate human interventions necessary to maintain this build each time we update our gRPC version. It’s not just convenience - if someone forgets this step, the build won’t necessarily break in an obvious fashion, but it can become unstable in production under load (as has happened to us.) A way to make this a DRY change would prevent any recurrence of that error.


(Chris Doré) #4

Force doesn’t solve the issue you are trying to avoid, but it does have a significantly different behaviour than just declaring an extra dependency. Declaring an extra dependency will not help in the case where some dependency in the graph happens to request a higher version of netty-code-haproxy than you want. In that case, Gradle will pick the highest requested version (as you’ve observed). If however you use force on netty-code-haproxy, it doesn’t matter what versions are in the resolved dependency graph, your specified version will be used.


(Chris Doré) #5

I took a stab at forcing a dependency based on what is in the requested dependency tree. I think this might be something you could work from.

In my example I want to force the version of hamcrest-core used by junit, regardless of other dependencies on hamcrest-core. With just junit 1.9 and hamcrest-core 1.2 defined as dependencies, we get hamcrest-core 1.2 instead of 1.1:

$ ./gradlew dependencies
myConfig
+--- junit:junit:4.9
|    \--- org.hamcrest:hamcrest-core:1.1 -> 1.2
\--- org.hamcrest:hamcrest-core:1.2

The code:

task wrapper(type: Wrapper) {
    gradleVersion = '3.3'
}
repositories {
    jcenter()
}
configurations {
    myConfig
}
dependencies {
    myConfig 'junit:junit:4.9'
    myConfig 'org.hamcrest:hamcrest-core:1.2'
}
configurations.myConfig.incoming.beforeResolve {
    // create a new configuration with the same dependencies as the original configuration
    // this allows us to resolve the dependency graph independent of the original configuration
    def conf = configurations.detachedConfiguration( *configurations.myConfig.allDependencies.toArray() )

    // find junit's requested hamcrest-core version
    def hccVersion = null
    conf.incoming.resolutionResult.allComponents.each {
        if( it.moduleVersion.group == 'junit' && it.moduleVersion.name == 'junit' ) {
            it.dependencies.each { dep ->
                if( dep.requested in ModuleComponentSelector ) {
                    if( dep.requested.group == 'org.hamcrest' && dep.requested.module == 'hamcrest-core' ) {
                        hccVersion = dep.requested
                    }
                }
            }
        }
    }

    // force hamcrest-core version if we found one
    if( hccVersion ) {
        configurations.myConfig.resolutionStrategy.force "${hccVersion.group}:${hccVersion.module}:${hccVersion.version}"
    }
}

The results:

$ ./gradlew dependencies
myConfig
+--- junit:junit:4.9
|    \--- org.hamcrest:hamcrest-core:1.1
\--- org.hamcrest:hamcrest-core:1.2 -> 1.1

Hope this helps, best of luck :slight_smile:


How to 'synchronize' versions of external module dependencies