Can Gradle help solve jar hell in any way?

Java 8 is here.

Let's say that there is an old version of widget libray with the coordinates of Maven widgetmakers:widget:1.0.4 , which has a class defined in it as follows:

 public class Widget { private String meow; // constructor, getters, setters, etc. } 

Years pass. The compilers of this widget library decide that a widget should never meow , but rather should be a bark . So, the new release is made with the coordinates of Maven widgetmakers:widget:2.0.0 and the widget looks like this:

 public class Widget { private Bark bark; // constructor, getters, setters, etc. } 

So now I'm going to create my application, myapp . And, wanting to use the latest stable versions of all my dependencies, I declare my dependencies like this (inside build.gradle ):

 dependencies { compile ( ,'org.slf4j:slf4j-api:1.7.20' ,'org.slf4j:slf4j-simple:1.7.20' ,'bupo:fizzbuzz:3.7.14' ,'commons-cli:commons-cli:1.2' ,'widgetmakers:widget:2.0.0' ) } 

Now let's say that this (fictional) fizzbuzz always depended on version 1.x of the widget library, where the widget was meow .

So now I point out 2 versions of widget in my compilation paths class:

  • widgetmakers:widget:1.0.4 , which is inserted by the fizzbuzz library as a dependency on it; and
  • widgetmakers:widget:2.0.0 , which I refer directly to

Thus, it is obvious that depending on which version of the widget first loaded into the class, we will either have Widget#meow or Widget#bark .

Does Gradle provide any means to help me here? Can I use multiple versions of the same class and configure the fizzbuzz classes to use the old version of widget , and my classes use the new version? If not, the only solutions I can think of are the following:

  • Perhaps I can do some kind of shading and / or push based on the game, where maybe I will use all my dependencies as packages under myapp/bin , and then give them different version prefixes. Admittedly, I do not see a clear solution here, but I'm sure something is possible (but still hacker / nasty). Or...
  • Inspect the entire dependency graph and just make sure that all of my transitive dependencies do not conflict with each other. In this case, for me, this means either sending a transfer request for fizzbuzz to update it to the latest version of widget , or, unfortunately, overriding myapp to use an older version of widget .

But Gradle (so far) has been magical to me. So I ask: is there any Gradle magic that can help me here?

+5
source share
4 answers

I don’t know the specifics of Gradle, since I am a Maven person, but in any case this is more general. You basically have two options (and both are hacks):

  • The magic of ClassLoader. Somehow you need to convince your build system to download two versions of the library (good luck with this), and then at run time load the classes that use the old version with ClassLoader, which has the old version. I did it, but it's a pain. (Tools like OSGI can take away some of this pain.)
  • Shading packages. Re-package library A, which uses the old version of library B, so that B is actually inside A, but with the package prefix B. This is common practice, for example. Spring ships its own version of asm . On the Maven side, the maven-shade-plugin does this, probably the equivalent of Gradle. Or you can use ProGuard , an 800-pound gorilla manipulation jar.
+5
source

Gradle will tweak the classpath with your dependencies; it does not provide its own runtime to encapsulate dependencies and its transitive dependencies. The version that is active at runtime will comply with the rules for loading classes, which, I believe, are the first bank in the class order to contain the class. OSGI provides runtimes that can handle such situations, as well as a future system of modules.

EDIT: BjΓΆrn is right in trying to resolve conflicts in different versions; it will compile the classpath based on its strategies, so the order in which you put your dependencies in a file does not matter. However, you still get only one class for each class, it will not solve the OP problem

+2
source

If you have different versions of the library with different equal coordinates, the Gradles conflict resolution mechanism comes into play.

The default permission strategy is to use the latest requested version of the library. You will not get multiple versions of the same library in the dependency graph.

If you really need different versions of the same library at runtime, you will either have to do some ClassLoader magic, which is definitely possible, or do some shading for one of the libraries, or both.

In terms of conflict resolution, Gradle has a built-in latest strategy, which by default, and the crash strategy fails if different versions are in the dependency graph, and you must explicitly resolve version conflicts in your build files.

0
source

Worse, when the same class appears in several jars. This is more insidious - look at the metrics from Codahale and Dropwizard with incompatible versions of the same class in two jars. gradle classpath-hell plugin can detect this horror.

0
source

All Articles