Performance Zone is brought to you in partnership with:

Creator of the Apache Tapestry web application framework and the Apache HiveMind dependency injection container. Howard has been an active member of the Java community since 1997. He specializes in all things Tapestry, including on-site Tapestry training and mentoring, but has lately been spreading out into fun new areas including functional programming (with Clojure), and NodeJS. Howard is a DZone MVB and is not an employee of DZone and has posted 79 posts at DZone. You can read more from them at their website. View Full User Profile

Gradle: Overruling Third-Party Dependencies

02.21.2014
| 7775 views |
  • submit to reddit

We all hate dependency hell, and on any Java project of any size, you'll hit it eventually. My project, for Aviso, is a large Clojure code base, with tons of dependencies on third-party libraries, many of which are Java libraries with their own third-party dependencies. Despite using Gradle as our build tool, we don't get a free pass, we sometimes end up with conflicts.

It often plays out like this: module A as a dependency on library L, which has a transitive dependency on library Q. That's OK, module A has a consistent class path when it builds.

Meanwhile, module B has a dependency on library M, which has a transitive dependency on library Q ... but a different version. That's OK, module B also has a consistent class path when it builds.

However, inside IntelliJ, you see both version of library Q in the "External Libraries" folder of the Project explorer. That's unfortunate and can cause confusion when navigating your code.

Worse yet, in the final application, combining modules A and B, you will be executing one module with a different version of library Q than your tests. That alone makes me a touch nervous.

Fortunately, Gradle provides a quite reasonable way of dealing with this. The hard way would be to just turn off all transitive dependencies. But I consider that throwing out the baby with the bathwater.

Instead, we can selectively override transitive dependency, consistently across all modules. And we can do this in a single place, in our top-levelbuild.gradle:

def versionOverrides = [
    "asm:asm": "3.3.1",
    "bultitude:bultitude": "0.1.7",
    "commons-codec:commons-codec": "1.7",
    "commons-io:commons-io": "2.4",
    "io.aviso:pretty": "0.1.9-SNAPSHOT",
    "joda-time:joda-time": "2.1",
    "org.clojure:core.incubator": "0.1.1",
    "org.clojure:tools.macro": "0.1.1",
    "org.clojure:tools.namespace": "0.1.1",
    "org.codehaus.groovy:groovy-all": "1.8.6",
    "org.jsoup:jsoup": "1.7.1",
    "org.yaml:snakeyaml": "1.12",
    "slingshot:slingshot": "0.10.3"
]
 
subprojects {
 
    configurations.all {
 
        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
 
            def overrideVersion = versionOverrides[details.requested.group + ":" + details.requested.name]
 
            if (overrideVersion != null && details.requested.version != overrideVersion) {
                logger.info "Overriding dependency ${details.requested.group}:${details.requested.name} version ${details.requested.version} --> $overrideVersion"
                details.useVersion overrideVersion
            }
        }
    }
}

This one small change affects every child project; we have a single place to maintain and resolve these version conflicts and don't have to chase down which module (among the 37 currently in our overall project) is the culprit for introducing a conflict. When we see a conflict, we add a new mapping to versionOverrides and we are done.

This is a huge example of how powerful Gradle's Groovy DSL is; because the build script is also executable code, there's room to put logic in place that simply can't be defined declaratively.

Our change hooks into the dependency resolution logic associated with each Gradle configuration (a configuration is essentially a way of declaring the class path for compiling, testing, or executing Java code).

Gradle kindly exposes a step inside the overall process of analyzing the dependencies; this code hooks into this step. It sees the requesteddependency, and if it's in the override map, forces the version number to a specific value. In fact, this mechanism is powerful enough to replacedependencies, but that's beyond our immediate needs.

This is one of the reasons I use Gradle in preference to Maven: Gradle has the tools to cleanly and easily address my specific problems and particular edge-cases.


Published at DZone with permission of Howard Lewis Ship, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Zsolt Kúti replied on Thu, 2014/02/27 - 3:38am

Good example for the flexibility of gradle. Thanks for this nice piece of information.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.