Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.
See all our reports

How to Add Custom Install Actions to Your Next Eclipse IDE Plug-in

The Eclipse IDE, according to some recent numbers on IDE usage, sits in nearly 2/3 of developer workstations. The ecosystem for Eclipse plug-ins, as you can imagine, is quite vast: about 1600 plug-in solutions are hosted on the Eclipse Marketplace, and have been downloaded nearly 6 million times collectively.

You Eclipse platform developers out there know, however, that the process for bringing your bad@$$ new plug-in to the machines of Eclipse users most often uses good ol’ “Install New Software”. But what if you wanted to customize specific actions in response to business or development needs to run during installation (such as “Ping me when install is successful/unsuccessful), upgrade (i.e. “How many users haven’t upgraded yet?”) or uninstallation (e.g. “Send Email Reminder: don’t cry when uninstalls happen.”), or other useful actions.

So although installing plug-ins into Eclipse is usually achieved by calling upon the powers of the Install New Software Wizard, there are also other ways. Users can go to the Eclipse Marketplace or put the plug-ins in the dropins folder. Regardless, all of these methods make use of Equinox p2, the underlying provisioning system for Eclipse-based applications, which seems full of secrets.

Install New Software

It became my interest to find out whether this Wizard could only cast the menial spells everyone knows (such as “Install Feature”, “Spectral Blade”, “Improved Spectral Blade”), or whether one could make it call forth more powerful magic. I wanted to find out if it is possible to run custom actions during installation, upgrading and uninstallation of plug-ins. As I wrote above, doing this might be valuable for business reasons (such as knowing how many people install your software), or to configure/unconfigure the system for your plug-in, or perform clean-up during uninstalls. It turns out that you can indeed run any code during both installs and uninstalls!

<shameless plug> JRebel for Eclipse has been downloaded over 70,000 times, and Eclipse RCP devs can cut way down on build/deploy times and really speed up their plug-in development using it.</shameless plug>

p2.inf and two-phased provisioning

I thought this not possible at first, but after doing numerous searches I finally found the right combination of keywords and discovered some hints as to how to achieve this. P2 is not well documented at all, so I found Ian Bull‘s blog post that explained most of what I wanted to know. However, it wasn’t easy to get everything working and also I found that maybe there is a simpler way to do it than he described.

A plug-in can give P2 some “advice” in the somewhat documented p2.inf file. This advice can tell P2 to run actions during the provisioning process. An extension point called “org.eclipse.equinox.p2.engine.actions” then lets you implement custom actions that can be invoked.

However, P2 cannot run code from the same plug-in that is being installed. Custom actions have to be provided in a separate plug-in, which needs to be installed before the main plug-in. What P2 can do for you is perform the installation in two phases without having the user perform two separate actions. If there is a meta-requirement from the main plug-in to another one, it will (1) install the plug-in with the custom actions (2) install the main plug-in, and run the custom actions synchronously during its installation.

Defining custom install actions

I’ll skip over how to create plug-ins and features in this post, since you should know how to do that if you are in the plug-ins business. You can also just clone or download the example project I used from this GitHub repository.

To describe the meta-requirement relation between two plug-ins, a p2.inf file should be created in the META-INF folder of both of them (next to MANIFEST.MF). The two plug-ins don’t need to have a normal dependency relation, only the meta-requirement described by p2.inf. However, I found it to be ok to place both plug-ins in the same feature, as to not expose this implementation detail to users who will be selecting features to install from an update site. Even if they are part of the same feature, P2 will install the meta-requirement first.

We’ll call the custom actions plug-in “org.zeroturnaround.example.p2.spells” (for they will be cast by the Wizard, after all). The plug-in will depend on “org.eclipse.equinox.p2.engine” and “org.eclipse.equinox.p2.metadata”, and the custom install actions can be provided by extending the extension point “org.eclipse.equinox.p2.engine.actions” in plugin.xml:

   <extension point="org.eclipse.equinox.p2.engine.actions">
      <action
            class="org.zeroturnaround.example.p2.spells.OnInstallAction"
            name="onInstall"
            touchpointType="org.eclipse.equinox.p2.osgi"
            version="1.0.0">
      </action>
   </extension>
   <extension point="org.eclipse.equinox.p2.engine.actions">
      <action
            class="org.zeroturnaround.example.p2.spells.OnUninstallAction"
            name="onUninstall"
            touchpointType="org.eclipse.equinox.p2.osgi"
            version="1.0.0">
      </action>
   </extension>

The touchpoint type should be “org.eclipse.equinox.p2.osgi”. It has something to do with the fact that there are “Eclipse touchpoints” and “native touchpoints”, and the ones we will create are not the native kind. I suspect a touchpoint is the point where the Wizard’s magic touches the real world, and I wouldn’t want to stick my nose in that if I can avoid it.

This p2.inf file in the spells plug-in seems not to be strictly necessary, but it seemed to help me get this working, or at least understand things better: the spells plug-in “provides” capabilities, and the main plug-in “requires” them. I guess P2 can also infer the “provides” side of the relation from the extension defined in plugin.xml, but it is a bit more clear if we provide it explicitly in the p2.inf file:

provides.0.namespace=org.zeroturnaround.example.spells
provides.0.name=onInstall
provides.0.version=1.0.0
provides.1.namespace=org.zeroturnaround.example.spells
provides.1.name=onUninstall
provides.1.version=1.0.0

Our main plug-in is mostly left blank for the purposes of this example, we’ll call it “org.zeroturnaround.example.p2.main”. It has to add the meta-requirements on the spells plugin in p2.inf, and also list the instructions for calling the custom actions during installation and uninstallation:

metaRequirements.0.namespace=org.zeroturnaround.example.p2.spells
metaRequirements.0.name=onInstall
metaRequirements.1.namespace=org.zeroturnaround.example.p2.spells
metaRequirements.1.name=onUninstall

instructions.install=org.zeroturnaround.example.p2.spells.onInstall();
instructions.uninstall=org.zeroturnaround.example.p2.spells.onUninstall();

Ian’s example used org.eclipse.equinox.p2.iu as the namespace here, and then used fully-qualified action names, but I didn’t completely understand why so I used the plug-in’s id for the namespace. We leave the version of the metaRequirements open, but you could also specify it e.g. metaRequirements.0.range=[1,2) to require any 1.x version. Once we have this in place, we can run any code during installs by implementing the OnInstallAction class the extension referred to.

Let’s implement some custom actions!

As a mundane example, let us perform the following actions during installation:

  • Upon first install, add a note about the version of the plug-in being installed into a txt file
  • Upon upgrade, add a note about which version was upgraded to which
  • Upon uninstall, delete the txt file

Some of this, like deleting the file, could also be performed by actions already provided by the “org.eclipse.equinox.p2.touchpoint.natives” plug-in. These are described in Eclipse Help. It provides actions like chmod, mkdir and so on. However, we will only use custom actions in this example.

The implementation for our custom actions can be found in the repository. It will save the txt file in ${user.home}\org.zeroturnaround.example.p2.txt. In this post, let’s only look at one snippet from OnInstallAction:

  @Override
  public IStatus execute(Map<String, Object> parameters) {
    IInstallableUnit iu = (IInstallableUnit) parameters.get("iu");
    IInstallableUnit upgradeFrom = null;
    Object operand = parameters.get("operand");
    try {
      if (operand instanceof InstallableUnitOperand)
        upgradeFrom = ((InstallableUnitOperand) operand).first();
    }
    catch (Throwable e) {
      // Ignore class not found in case InstallableUnitOperand is missing
    }
    if (upgradeFrom != null)
      performUpgrade(iu, upgradeFrom);
    else
      performNewInstall(iu);
    return Status.OK_STATUS;
  }

Recognizing upgrades

This makes use of P2 internal code (InstallableUnitOperand) to distinguish between new installs and upgrades. Of course, ideally we shouldn’t use non-API code, but I didn’t discover any other way of doing this. By checking whether the “operand” given to our action is an InstallableUnitOperand and contains references to two versions of our installable unit, we can take a pretty good guess that the installation or uninstallation being done at the moment is part of an upgrade process:

  • A new install has first=null, second=<new version>
  • An upgrade has first=<old version>, second=<new version>
  • An uninstall has first=<old version>, second=null

There are other phases of provisioning procedures that we could tie our actions to, such as “configure” and “unconfigure”, but I didn’t investigate why they would be useful (configure runs after install and unconfigure before uninstall).

Building and testing the example project

Now, we should also wrap a feature around our plug-ins and create a P2 repository for them, so that they can be installed by the Wizard. The GitHub repository includes Maven pom.xml files to build it the plug-ins, feature and update site with Maven & Tycho. If all goes well, the complete update site will be located in /org.zeroturnaround.example.p2.updatesite/target/site after running mvn package:

C:\Users\Erkki\workspace\org.zeroturnaround.example.p2>mvn package
...
[INFO] --- tycho-packaging-plugin:0.18.1:update-site-packaging (default-update-site-packaging) @ org.zeroturnaround.example.p2.updatesite ---
[INFO] Building zip: C:\Users\Erkki\workspace\org.zeroturnaround.example.p2\org.zeroturnaround.example.p2.updatesite\target\site.zip
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] The Well-Kept Secrets of P2 ....................... SUCCESS [0.003s]
[INFO] Custom Install Actions ............................ SUCCESS [1.214s]
[INFO] Main Plug-in ...................................... SUCCESS [0.045s]
[INFO] Main Feature ...................................... SUCCESS [0.090s]
[INFO] Update Site ....................................... SUCCESS [2.266s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Note that I wasn’t able to get the two-phase install process working when launching a self-hosted Eclipse instance from another Eclipse (launched as an Eclipse Application, with Software Installation enabled). So to test out these custom actions in an Eclipse setup that doesn’t already have your custom spells plug-in installed, you should launch a second Eclipse normally through its executable.

Now let’s get to testing:

  1. Install “Main Feature” from the update site. The file org.zeroturnaround.example.p2.txt should appear in your home folder and have this content (timestamps omitted):

    New Install: org.zeroturnaround.example.p2.main 1.0.0

  2. Build a new version of the plug-in (you may also want to bump the version to 1.0.1).
  3. Restart Eclipse (or reload the update-site from Preferences -> Install/Update -> Available Software Sites).
  4. Update “Main Feature” from the update-site. The txt file should now contain:

    New Install: org.zeroturnaround.example.p2.main 1.0.0
    Upgrade: org.zeroturnaround.example.p2.main 1.0.0 -> 1.0.1

  5. Uninstall the “Main Feature” (Help -> About Eclipse -> Installation Details). The txt file should be deleted.

Getting all this working took me a while, even though it doesn’t seem that hard. I think my main problem was that I was testing this in a self-hosted Eclipse. It all started working much better when I launched the second Eclipse normally.

Complications with upgrades

If you need an upgrade to run with a new version of an install action, this scheme where both plug-ins are in the same feature does not work very well. To make P2 install the new custom actions plug-in, you should bump the version number of the actions (and the plug-in) and change the meta-requirement in the main plug-in to specify that exact version as the lower bound. Otherwise, P2 will not see the need to install the new version of the custom actions plug-in before the main one, and will use the action from the old version.

However, if you make it require the new version, it cannot be installed alongside the old version because the plug-in is a singleton (since it provides extensions in plugin.xml) and only one version of it can be installed. The old one will not be automatically uninstalled because it’s a required part of the currently installed feature. This is the dependency conflict I ran into:

The actions required to successfully install the requested software are incompatible with the software to install. 
  Cannot complete the install because of a conflicting dependency.
    Software being installed: org.eclipse.equinox.p2.engine.actions.root.epp.package.jee 1.0.0.1377705468398
    Software currently installed: Main Feature 1.0.0.201308281556 (org.zeroturnaround.example.p2.feature.feature.group 1.0.0.201308281556)
    Only one of the following can be installed at once: 
      Custom Install Actions 1.0.0.201308281556 (org.zeroturnaround.example.p2.spells 1.0.0.201308281556)
      Custom Install Actions 1.0.1.201308281604 (org.zeroturnaround.example.p2.spells 1.0.1.201308281604)
    Cannot satisfy dependency:
      From: org.eclipse.equinox.p2.engine.actions.root.epp.package.jee 1.0.0.1377705468398
      To: org.zeroturnaround.example.p2.spells onInstall 1.0.1
    Cannot satisfy dependency:
      From: Main Feature 1.0.0.201308281556 (org.zeroturnaround.example.p2.feature.feature.group 1.0.0.201308281556)
      To: org.zeroturnaround.example.p2.spells [1.0.0.201308281556]

This can be worked around by wrapping the custom actions plug-in in a feature that is optionally included in the main feature. Then it can be removed when this conflict occurs to be replaced with the new version. If it’s only included in the main feature, it will also not show up for users on the update site, but will show up in the Eclipse Installation Details dialog, under the Main Feature. I implemented this scheme in the included_feature branch of the GitHub repository. To test this upgrade scenario, you have to change the version number from 1.0.0 to a greater version in quite a few places: spells plug-in’s MANIFEST.MF, pom.xml, p2.inf, plugin.xml; spells feature’s feature.xml, pom.xml; main plug-in’s p2.inf

Final thoughts

The complexity of getting this all to work smoothly made me think that there could be easier ways to run actions on installs, upgrades and uninstalls, without using the P2 touchpoints. Of course, in that case the actions wouldn’t run exactly as part of the install/uninstall process, but only approximately at the right time:

  • When your plug-in starts up, check to see if a breadcrumb has been stored. Then write the current version into the breadcrumb.
  • If the breadcrumb didn’t exist, we had a new install.
  • If the breadcrumb existed, but was of an older version, we had an upgrade.
  • Register an listener on the P2 provisioning event bus, and react to events that say your plug-in is being uninstalled.

This would be much more hackish way to do it, but also doesn’t run into the same dependency conflicts as the meta-requirements. You could also store the breadcrumb at different scopes so an “upgrade” event could also happen per user, not per specific Eclipse instance. Perhaps the most powerful advantage would be that you could actually run code from your main plug-in in the actions, without reorganizing the code into separate plug-ins.

I’m thinking that a hack like this could even be exposed through a nice API by a third-party plug-in that you can include in your product–let me know what you think by leaving comments below or pinging me @t4ffer.

  • Kent

    Good stuff. However, after playing with this for several days, I concluded the trick no longer works. At least not in the latest kapler as of today. Eclipse does detect the custom action and downloaded it first, it then attempts to install the actual update but fails for not finding the action.

    If you quit the application then restart and re-install the update, it will be successful because the custom action is now loaded in OSGI. Of course, there will be no prompt to install the custom action plugin again since it was done in the last round.

    It seems what is missing is that Eclipse does not load the custom action plugin into OSGI runtime after download and deploy it.

    I tried this with my own code and the example provided, plus some examples from other sites. The results are the same.

  • Diana

    Do you remember seeing some MissingAction error when trying to update your bundles ? I see that the extension point that is exported using plugin.xml is not loaded when doing the update.