Hi. The module system leaves the task of version selection, version conflict resolution and other version-related issues to build tools, but as one of the maintainers of a launch tool, Capsule <http://www.capsule.io/> — which is similar to a build tool in many respects -- I can’t quite see how this should be done, and would appreciate some guidance.
The problem arises with version conflicts of a transitive dependency on a library that has breaking changes between versions. This is a common occurrence. One notable example is ASM. Suppose I have an application model, A, with the following dependencies: A —> B C —> L@v2 D E M —> L@v1 —> B L@v2 —> B When I build the application, the build tool detects the version conflict, L@v1/v2, and emits an error/warning. At this point the developer tells the build tool to find a solution such that library M will resolve the dependency on L at v1, but all other modules will use L@v2. This is done today using shadowing, namely merging L@v1 into the same JAR file as M, and transforming all classes both in M and L so that L’s packages are renamed. This solution is obviously brittle. For example, it may not work when reflection is involved, and in any event, it is excessive. There is no reason why the same solution (shadowing) would not work with Jigsaw modules, but it seems like Jigsaw could offer a better way, except I see two problems that I’d like to ask for advice on: 1. I don’t see a way other than the module path to inform the JVM of the module graph configuration (such as passing the name of a class that will create the configuration etc.). A workaround may be to inject an agent that will be listed first on the command line, have its premain method never return, and let it parse the rest of the command line, loading the main module through a custom layer. This may sound like a terrible hack, but it’s the kind of things tools do, and it might still be less brittle and hack then shadowing. My question is, is there a better way? 2. Suppose there was a way to create a configuration more complex than a single module path. How would I construct the one I described above? My first though was to have one layer consisting of all modules, and then a child-layer with M —> L@v1. But this doesn’t work as A —> M, and you can’t resolve a Configuration that’s missing a dependency. My next guess was to put all dependencies except M in one layer, then M —> L@v1 in another, and then A in yet another, but this doesn’t work either as A —> L@v2, and the L@v1 would override that. Then I thought of putting M —> L@v1 —> B in the first layer, and then everything else in another, overriding L, but this isn’t quite right as it would result in two versions of B. The only solution, then, is putting M —> L@v1 —> B in the first layer, and then everything else, taking care to override L but not B. This can work, but it may be quite annoying to compute, especially given that my intentions are very simple — resolve everything as you would except resolve L to L@v1 when accessed through M. Shouldn’t there perhaps be a way to modify the graph selectively before final resolution (or some other form of fine-grained control over the graph’s construction)? So, the module system leaves the task of version selection and conflict resolution to build tools — and rightly so — but I don’t see it simplifying the tools’ job or helping them avoid terrible hacks, even though it seems like it could. What am I missing? Is there some simple solution I’ve overlooked? Ron