Can I force the order of dependencies in my classpath with Gradle?
Asked Answered
P

3

13

A project runs on Google App Engine. The project has dependency that uses a class that can't be invoked on App Engine due to security constraints (it's not on the whitelist). My (very hacky) solution was to just copy a modified version of that class into my project (matching the original Class's name and package) that doesn't need the restricted class. This works on both dev and live, I assume because my source appears in the classpath before my external dependencies.

To make it a bit cleaner, I decided to put my modified version of that class into it's own project that can be packaged up in a jar and published for anyone else to use should they face this problem.

Here's my build.gradle:

// my jar that has 'fixed' version of Class.
compile files('path/to/my-hack-0.0.1.jar')

// dependency that includes class that won't run on appengine
compile 'org.elasticsearch:elasticsearch:1.4.4'

On my local dev server, this works fine, the code finds my hacked version of the class first at runtime. On live, for some unknown reason, the version in the elasticsearch dependency is loaded first.

I know having two versions of the same class in the classpath isn't ideal but I was hoping I could reliably force my version to be at the start of the classpath. Any ideas? Alternatively, is there a better way to solve this problem?

Penelopa answered 29/7, 2015 at 10:56 Comment(2)
It's not gradle that determines the class loading order, but the class loader itself. There usually are some rules that determine the class loading order within ClassLoader hierarchies, however if the class is loaded within the context of one class loader (afaik) the loading order is not defined, nor guaranteed to remain constant (unless it is a very special class loader that guarantees such a behavior).Furbish
Huzzah, thanks @DaniloTommasina! Of course, it's the appengine classloader I should have been investigating, not gradle. I found a solution, details below if you're interested...Penelopa
J
11

Not really sure if this is what people visiting this question were looking for, but this was what my problem and a solution that I reached at. Jar A: contains class XYZ Jar B: also contains class XYZ My Project needs Jar B on the classpath before Jar A to be able to get compiled.

Problem is Gradle sorts the dependencies based on alphabetical order post resolving them which meant Jar B will be coming after Jar A in the generated classpath leading to error while compiling.

Solution: Declare a custom configuration and patch the compileClasspath. This is how the relevant portion of build.gradle might look like.

configurations {
    priority
    sourceSets.main.compileClasspath = configurations.priority + sourceSets.main.compileClasspath
}

dependencies {
    priority 'org.blah:JarB:2.3'
    compile 'org.blah:JarA:2.4'
    ...
}
Jobina answered 23/12, 2017 at 14:40 Comment(2)
FWIW I did not find this to work for either compileClasspath or runtimeClasspath (ordering remained as if I'd left this configuration out; i.e. random).Tachistoscope
I had a similar issue, but while running tests. I used testRuntimeClasspath instead of compileClasspath and worked like a charm, thank you for sharing! Here is the actual commit if anyone's interested.Passed
P
3

It's the app engine classloader I should have been investigating, not gradle...

App Engine allows you to customise the class loader JAR ordering with a little bit of xml in your appengine-web.xml. In my case:

<class-loader-config>
    <priority-specifier filename="my-hack-0.0.1.jar"/>
</class-loader-config>

This places my-hack-0.0.1.jar as the first JAR file to be searched for classes, barring those in the directory war/WEB-INF/classes/.

...Thanks to a nudge in the right direction from @Danilo Tommasina :)

UPDATE 2020:

I just hit the same problem again and came across my own question... This time, live appengine was loading a different version of org.json than was being loaded in dev. Very frustrating and no amount of fiddling the build script would fix it. For future searchers, if you're getting this:

java.lang.NoSuchMethodError: org.json.JSONObject.keySet()Ljava/util/Set;

It's because it's loading an old org.json dependency from god-knows-where. I fixed it by adding this to my appengine-web.xml:

<class-loader-config>
    <priority-specifier filename="json-20180130.jar"/>
</class-loader-config>

You'll also need a matching dependency in build.gradle if you don't already have one:

compile 'org.json:json:20180130'
Penelopa answered 29/7, 2015 at 11:47 Comment(0)
F
2

According to gradle dependencies documentation, the order of dependencies defines the order in the classpath. So, we can simply put the libraries in the correct order in "dependencies".

But beware! here are two rules with higher priorities:

  • For a dynamic version, a 'higher' static version is preferred over a 'lower' version.
  • Modules declared by a module descriptor file (Ivy or POM file) are preferred over modules that have an artifact file only.
Flavorful answered 3/8, 2016 at 12:0 Comment(2)
Did this change? I can't see anything to that effect in the documentationMier
I don't think this applies to order of dependency declarations, but rather the order of repository declarations.Tachistoscope

© 2022 - 2024 — McMap. All rights reserved.