NOTE 1: What is written here, is a really, really last resort. You should try other approaches first, and be really, really sure that they will not work for you.
NOTE 2: This functionality is present in Eclipse Luna since N20131219-2000.
It is very important in some Linux systems to be able to verify integrity of the installation because of security reasons. This goal was achieved by making the Eclipse installation read-only just after it was copied to the system, and launching it in a shared configuration mode by regular users. It did worked very well. However, problems were appearing as more and more Eclipse plugins/features was available via the Linux repositories. It was nearly impossible to install things into a read-only Eclipse, while keeping it's integrity checked. One solution to that problem was adopting dropins, but the problem with the dropins was that one wrong plugin could effectively prevent all dropins bundles from being loaded, giving almost no information about what went wrong. Although things got much better in Eclipse Kepler, the lack of dropins diagnosis and its "unpredictability" (it is predictable, it just doesn't help you if something goes wrong) is still a huge pain if you want to deploy large Eclipse stack divided into read-only components.
The alternative solution key requirements were:
- be able to load bundles from specified location, with no startup overhead (dropins resolution could take as much as 30 sec at Eclipse startup)
- always attempt to load all bundles (key point for diagnosis - OSGi will report wiring errors)
In order to get fragments working properly, you need to ensure that following requirements are met:
- Base Eclipse product installation is read-only (otherwise Eclipse will start correctly, but will be in inconsistent state, and attempts to install things using UI will likely break things).
- Base Eclipse product contains important bundles already started (declarative services). This requirement is met for the SDK by default.
- Eclipse needs to be configured explicitly to use fragments by passing -Dp2.fragments=<path to fragments>[,<path to fragments>] property to JVM.
- Fragments need to be read-only. If you miss that step, you will see Migration Installation wizard appearing again and again.
What is a fragment
Fragment is actually a p2 runnable repository with some additional files. A runnable repository is an undocumented* feature of p2. It can be created by calling repo2runnable application with -flagAsRunnable argument, f.e.:
./eclipse -application org.eclipse.equinox.p2.repository.repo2runnable -source org.eclipse.epp.mpc-519e70b90429c4fa2a14b35ab8bb6907519cb101/org.eclipse.epp.mpc.site/target/site -destination /tmp/p2/installed3 -flagAsRunnable
The runnable repository has already bundles and features in a runnable shape, and they will not be copied into Eclipse installation when installed (this is the reason why "-flagAsRunnable" is undocumented - results of using it are very surprising to the end user). Additionally, the fragment must contain two additional files: fragment.info and fragment.profile.
It is a file of the format of bundles.info. Each of lines should contain one bundle (not feature, not source bundle, but OSGi bundle to be loaded):
<bundle symbolic name>,<bundle version>,<path to bundle>,<start-level>,<auto-start>
The path to the bundle may be relative or absolute. The fragment.info is read by a simpleconfigurator at Eclipse startup, and bundles from it are loaded into OSGi.
This file is a set of units that need to be installed into P2 profile in order to be consistent with bundles loaded via fragment.info. This file contains also additional units like features, source bundles and all "meta" things that complete the installation. It should, however, not duplicate base configuration units (like a.jre and others).
<?xml version='1.0' encoding='UTF-8'?> <?profile version='1.0.0'?> <profile id='1387452745074-0.9476774676180175' timestamp='1387452745074'> <properties size='2'> <property name='org.eclipse.equinox.p2.installFolder' value='/home/kdaniel/junit-workspace/testRepo2RunnableFragmentsConfigured_1'/> <property name='org.eclipse.equinox.p2.cache' value='/home/kdaniel/junit-workspace/testRepo2RunnableFragmentsConfigured_1'/> </properties> <units size='2'> <unit id='helloworld' version='1.0.0'> <update id='helloworld' range='[0.0.0,1.0.0)' severity='0'/> <properties size='1'> <property name='org.eclipse.equinox.p2.name' value='Helloworld Plug-in'/> </properties> <provides size='3'> <provided namespace='org.eclipse.equinox.p2.iu' name='helloworld' version='1.0.0'/> <provided namespace='osgi.bundle' name='helloworld' version='1.0.0'/> <provided namespace='org.eclipse.equinox.p2.eclipse.type' name='bundle' version='1.0.0'/> </provides> <requires size='2'> <required namespace='osgi.bundle' name='org.eclipse.ui' range='0.0.0'/> <required namespace='osgi.bundle' name='org.eclipse.core.runtime' range='0.0.0'/> </requires> <artifacts size='1'> <artifact classifier='osgi.bundle' id='helloworld' version='1.0.0'/> </artifacts> <touchpoint id='org.eclipse.equinox.p2.osgi' version='1.0.0'/> <touchpointData size='1'> <instructions size='1'> <instruction key='manifest'> Bundle-Activator: helloworld.Activator Require-Bundle: org.eclipse.ui,org.eclipse.core.runtime Manifest-Version: 1.0 Bundle-RequiredExecutionEnvironment: J2SE-1.5 Bundle-Name: Hellowo rld Plug-in Bundle-Version: 1.0.0 Bundle-ManifestVersion: 2 Bundle-ActivationPolicy: lazy Bundle-SymbolicName: helloworld; singleton:=true </instruction> </instructions> </touchpointData> </unit> <unit id='helloworldfeature.feature.jar' version='1.0.0'> <update id='helloworldfeature.feature.jar' range='[0.0.0,1.0.0)' severity='0'/> <properties size='3'> <property name='org.eclipse.equinox.p2.name' value='Helloworldfeature Feature'/> <property name='org.eclipse.equinox.p2.description' value='[Enter Feature Description here.]'/> <property name='org.eclipse.equinox.p2.description.url' value='http://www.example.com/description'/> </properties> <provides size='3'> <provided namespace='org.eclipse.equinox.p2.iu' name='helloworldfeature.feature.jar' version='1.0.0'/> <provided namespace='org.eclipse.equinox.p2.eclipse.type' name='feature' version='1.0.0'/> <provided namespace='org.eclipse.update.feature' name='helloworldfeature' version='1.0.0'/> </provides> <filter> (org.eclipse.update.install.features=true) </filter> <artifacts size='1'> <artifact classifier='org.eclipse.update.feature' id='helloworldfeature' version='1.0.0'/> </artifacts> <touchpoint id='org.eclipse.equinox.p2.osgi' version='1.0.0'/> <touchpointData size='1'> <instructions size='1'> <instruction key='zipped'> true </instruction> </instructions> </touchpointData> <licenses size='1'> <license uri='http://www.example.com/license' url='http://www.example.com/license'> [Enter License Description here.] </license> </licenses> <copyright uri='http://www.example.com/copyright' url='http://www.example.com/copyright'> [Enter Copyright Description here.] </copyright> </unit> </units> <iusProperties size='2'/> </profile>
Actually generating a fragment
There is no easy way to do that. The actual *.info and *.profile generation is hidden behind P2 abstraction, and there is no elegant way of reusing P2 logic. There is just a dirty patch for the repo2runnable, and to use it to create a fragment it is necessary to call (once you have Eclipse built with that patch):
./eclipse -application org.eclipse.equinox.p2.repository.repo2runnable -createFragments -flagAsRunnable -source <path to source repo> -destination <path to destination>
How does it work
There are two important areas here:
- loading bundles at startup - no big logic here - just find all the *info files, remove duplicates for sanity, and load all the bundles.
- keeping the profile consistent - a bit more complicated
- detect when fragments and master profile are not up to date
- add runnable (fragment) repos to the p2
- create a new surrogate profile containing all the bundles from master profile and fragment profiles.
- on profile commit everything is nicely written down as one, consistent configuration. Bundles/Features are not copied over and over, because runnable repos prevent that.
Tips, Tricks, Limitations and various notes
- No Verification - things installed via fragments are not guarded by P2. It is entirely product packager responsibility to ensure that produced fragments will work, and that their dependencies are installed.
- Fragments can't handle custom touchpoints. Those should be handled by the external installer (f.e. installing JVM, configuring shortcuts, preferences, adding users to the system).
- Dependencies between fragments need to be handled by the external installer.
- The core, immutable installation should be marked as not launchable. See Bug 379102 for details.
- P2 director WILL NOT BE ABLE to install things that are in the fragments. This is a limiation of runnable repositories mechanism (so if you have a bunle named a_1.0.0.jar, installed via a fragment, you will not be able to install any a_1.0.0.jar using that installation director.
- It is good to verify that the fragment is really working by installing bundles in a classical way (with all repositories but the fragment one disabled).
- This should be considered to be work in progress. If you want to adopt it, be sure to add yourself to the list below to avoid unpleasant surprises.
- Fedora, Roland