Last week, we ran into troubles when deploying our system via Equinox P2: All of a sudden, our DSLs simply didn’t work after the installation of the system into a new Eclipse – the DSL projects didn’t request the Xtext nature, and Eclipse didn’t open the appropriate editor without displaying any error message. Big showstopper!
A look in the log revealed the following:
org.eclipse.e4.core.di.InjectionException: java.lang.LinkageError: loader constraint violation: when resolving overridden method "org.eclipse.xtext.xbase.ui.contentassist.XbaseProposalProvider.getProposalFactory(Ljava/lang/String;Lorg/eclipse/xtext/ui/editor/contentassist/ContentAssistContext;)Lcom/google/common/base/Function;" the class loader (instance of org/eclipse/osgi/internal/loader/EquinoxClassLoader) of the current class, org/eclipse/xtext/xbase/ui/contentassist/XbaseProposalProvider, and its superclass loader (instance of org/eclipse/osgi/internal/loader/EquinoxClassLoader), have different Class objects for the type com/google/common/base/Function used in the signature ...
So the root of the problem were different versions of Google Guava (that contains com.google.common.base.Function). Of course, OSGi supports running multiple versions of bundles at once – which we do in our system since Sirius uses Guava 15 and Mylyn uses Guava 18. No problem so far!
The problem arose because Xtext is open to a wide range of Guava versions. Unfortunately, the Xtext bundles were not wired to one Guava version consistently, but randomly to one of the two (depending on the order of resolution). This introduced incompatibilities between Xtext bundles, which prevented the system from working.
Searching for a way
Now what can be done?
One possible solution for this would be employing the OSGi “uses”-directive (good explanation here): This directive in the bundle headers of multi-bundle projects can ensure that the class space is consistent. The problem with this solution was that we would have had to fork Xtext in order to add the “uses”-directives. Since we are not (yet) ready and willing to do that, we reported a bug with Xtext. The good news is that the helpful guys over there reduced the likelihood of inconsistent bundle wiring by removing some reexports. The bad news: There is no waterproof solution at hand that will work in any case.
Maybe – just maybe – upgrading to Xtext 2.8 might have helped. With our complex system, this would have taken two weeks and forced the same migration on our customer. No good!
Breakthrough: Hacking OSGi
So back to the planning table: What we want is to make sure that all Xtext bundles end up with the same version of Guava. So if we could influence the OSGi resolving process, we’d be fine! But to do that, the code that influences the resolving process needs to be loaded first – a chicken and egg problem: Who makes sure that the code that determines bundle resolution order is the first to be fired up?
A closer look at the OSGi specification showed us that there is a mechanism in place already: The Resolver Hook Service makes it possible to influence the resover’s decisions by writing a system extension to the OSGi framework.
After some research about the details of the Resolver Hook Service, we came up with a system bundle fragment that is called whenever a bundle wants its dependencies resolved. This fragment is given a list of possible candidates for the bundle wiring. Now we can kick out the older versions if a dependency can be resolved by more than one version of a bundle, so only the newest one remains. And voilà: All of Xtext ended up with Guava 18.
Deploying the solution
We still were faced with one minor problem: OSGi needs to be made aware of the system extension fragment at startup. Locally, this is no problem: One can either add “osgi.framework.extensions=…” to the $ECLIPSE_HOME/configuration/config.ini, to the vm section in the $ECLIPSE_HOME/eclipse.ini or pass it as an argument to the VM (‑Dosgi.framework.extensions=…).
But how to do this automatically during a P2 installation? Well, as Dennis Hübner put it:
p2.inf is your friend
Using a p2.inf located next to the feature.xml of the feature containing the bundle fragment, it is easy to update the $ECLIPSE_HOME/configuration/config.ini during the installation process. Yayy, it works!
The code we came up with is available under the EPL. It can be found in our lunifera-runtime repo at Github (development branch, relevant folders: org.lunifera.runtime.systemextension and org.lunifera.runtime.feature.resolverhooks).