How should a build/launch tool best make use of modules?
Ron Pressler
ron at paralleluniverse.co
Tue Sep 29 15:14:08 UTC 2015
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 at v2
D
E
M —> L at v1 —> B
L at v2 —> B
When I build the application, the build tool detects the version conflict,
L at 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 at v2. This is done
today using shadowing, namely merging L at 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 at 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 at v1 in
another, and then A in yet another, but this doesn’t work either as A —>
L at v2, and the L at v1 would override that. Then I thought of putting M —>
L at 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 at 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 at 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
More information about the jigsaw-dev
mailing list