Build Eclipse plugin using Maven2

Note: This manual is already outdated - the eclipse plugin itself can pack eclipse bundles as Maven 2 dependencies, and gained support to build the bundles itself.

This howto describes how to build a full-blown eclipse plugin using Maven2. The howto strives to keep the plugin able to be executed and debugged directly in the Eclipse IDE while using Maven2 repository for dependencies.

Before we drop it on, you have to decide a few things:

  • You should really consider moving the project to Maven2 suggested file structure, otherwise some Maven2 plugins will not work for you. For example, the site generator plugin ignores the output directory and places everything into target directory. Moreover, if you try to package the plugin after the site is generated, the site is included in result jar file. Site itself should be placed in src/site - the change report plugin will always look there for its changes.xml descriptor file.
  • Whether your plugin will be distributed as a single jar (this feature was added in Eclipse 3.1.0) or as a zipped directory (this is better when the plugin will include configuration files that the user may need to modify)
  • Where the dependencies will be placed (probably the lib/ directory)
  • Which eclipse plugins will the plugin depend on. Consult the eclipse plugin repository for the list of the eclipse bundles (eclipse plugins have groupId equal to eclipse.bundles).
  • If you want to build a RCP application then add the eclipse.bundles:org.eclipse.rcp_base to the dependencies. There are several platform-specific resources you need to depend on:
    • eclipse.bundles:org.eclipse.deltapack.launchers.platform - provides a launcher for given platform (launcher.exe for win32, etc).
    • eclipse.bundles:org.eclipse.swt.platform - SWT native libraries for given platform.

    platform is one of:

    • carbon.macosx.ppc
    • carbon.macosx.x86
    • gtk.linux.ia64
    • gtk.linux.ppc
    • gtk.linux.x86
    • gtk.linux.x86_64
    • gtk.solaris.sparc
    • motif.aix.ppc
    • motif.hpux.ia64_32
    • motif.hpux.PA_RISC
    • motif.linux.x86
    • motif.solaris.sparc
    • photon.qnx.x86
    • win32.win32.x86

There already is an Eclipse bundle project built with Maven2. You may download the project EuroMath2 (you have to grab the 1.4.x version - this tutorial relates to 1.4.0-alpha version, later versions may have fixed some issues stated in this text). This tutorial is not perfect, some things could be done in a better way, but I'm simply too lazy and I wanted to show bad paths aswell :-)

All EM2's bundles were created to be compatible with Eclipse 3.2 and OSGi - I don't care for Equinox or whatever that is. This tutorial merely describes the build system of EuroMath2 so you're on your own when building Eclipse bundles with Maven2.

Okay, let's get started. Download sources distribution, go to sk.uniba.euromath.pom directory, issue

mvn install

and grab the RCP application at sk.uniba.euromath.rcp's target directory. In this howto I'll describe how I managed to build EuroMath2 - please grab and modify those poms any way you see fit for your application.

EM2 structure

EM2 consists of the following Maven2 projects (artifact ids are listed):

  • sk.uniba.euromath.pom - the main project, parent of all other projects. It itself extends a bundle-pom plugin that defines some neat things - eclipse bundle versions, configures eclipse plugin etc.
  • sk.uniba.euromath - an Eclipse bundle project, the base bundle containing all basic functionality. Exports all of its libraries in use for dependent bundles.
  • sk.uniba.euromath.fop - Eclipse bundle, extends points in sk.uniba.euromath, depends on sk.uniba.euromath
  • sk.uniba.euromath.mathml - Eclipse bundle, extends points in sk.uniba.euromath, depends on sk.uniba.euromath
  • sk.uniba.euromath.docbook - Eclipse bundle, extends points in sk.uniba.euromath, depends on sk.uniba.euromath
  • sk.uniba.euromath.xhtml - Eclipse bundle, extends points in sk.uniba.euromath, depends on sk.uniba.euromath
  • sk.uniba.euromath.rcp - builds the RCP distribution from all five bundles.

Creating M2 descriptors

We'll start with sk.uniba.euromath.pom's pom.xml. It is pretty straightforward, except that the bundle plugin must extend the sk.baka.eclipse:plugin-parent-pom POM (located here) - it defines versions of bundles that comes with Eclipse distribution (we are going to use bundles from Eclipse 3.2).

  <parent>
    <groupId>sk.baka.eclipse</groupId>
    <artifactId>plugin-parent-pom</artifactId>
    <version>3.2</version>
  </parent>

Let's continue with sk.uniba.euromath's pom.xml. It extends sk.uniba.euromath.pom of course. Please note that Eclipse bundle dependencies are defined like regular dependencies. This of course causes some problems with assembly plugin and eclipse plugin - more on this later.

    <!-- eclipse plugins dependency -->
    <dependency>
      <groupId>eclipse.bundles</groupId>
      <artifactId>org.eclipse.core.runtime</artifactId>
    </dependency>
    <dependency>
      <groupId>eclipse.bundles</groupId>
      <artifactId>org.eclipse.equinox.common</artifactId>
    </dependency>

sk.uniba.euromath.fop's pom.xml demonstrates a remedy to assembly plugin problem - it simply declares some dependencies as provided. While this will work OK with commons-lang dependency (it is provided by the sk.baka.eclipse bundle after all), it causes Eclipse bundles to be absent when RCP assembly includes all runtime dependencies.

Preparing eclipse descriptors

Note: I was planning to develop maven-eclipse-plugin to complement standard maven-eclipse-plugin with the functionality required to develop Eclipse bundles, but I don't have time for such work anymore. So please consider it as a starting point for developing your own m2 plugin or the like.

First we have to configure the workspace to add the M2_REPO classpath variable. Execute:

mvn -Declipse.workspace=<path-to-eclipse-workspace> eclipse:add-maven-repo

Now .classpath and .project files must be generated. Just execute:

mvn eclipse:eclipse

Note that the plugin nature is already present in the .project file - this is defined in the plugin-parent-pom maven2 plugin.

Dependent eclipse plugins are registered in the plugin classpath, like common libraries - this is because maven-eclipse-plugin does not recognize between a regular library and a plugin dependency. We have to move those dependencies into the META-INF/MANIFEST.MF file, to register them as plugin dependencies. Delete all Eclipse-Bundles dependencies from your .classpath file and move them to the META-INF/MANIFEST.MF.

Now the .classpath file contains 'var' referencies directly to the Maven2 repository. While this isn't bad for a regular Eclipse project, it breaks some things in Eclipse-bundle project (for example you cannot export classes from such dependencies). Moreover, we need to mimic runtime bundle environment. So, we'll first create symlinks to libraries, and place them into the lib/ directory. Look into sk.uniba.euromath/.project for details:

        <linkedResources>
                <link>
                        <name>lib/msv-20050913.jar</name>
                        <type>1</type>
                        <locationURI>M2_REPO/msv/msv/20050913/msv-20050913.jar</locationURI>
                </link>
                <link>
                        <name>lib/src/msv-20050913-sources.jar</name>
                        <type>1</type>
                        <locationURI>M2_REPO/msv/msv/20050913/msv-20050913-sources.jar</locationURI>
                </link>
                <link>
                        <name>lib/stax-api-1.0.jar</name>
                        <type>1</type>
                        <locationURI>M2_REPO/stax/stax-api/1.0/stax-api-1.0.jar</locationURI>
                </link>
                <link>
                        <name>lib/src/stax-api-1.0-sources.jar</name>
                        <type>1</type>
                        <locationURI>M2_REPO/stax/stax-api/1.0/stax-api-1.0-sources.jar</locationURI>
                </link>
     ...

Note the 'sources' links. We cannot use any variables when attaching sources to the 'lib' .classpath dependencies, thus we cannot link sources directly with Maven2 repository. We will use symlinks once more, to bring library sources into our project to the lib/src/ directory.

We have to modify .classpath sources to reference these linked libraries:

   ...
        <classpathentry exported="true" sourcepath="lib/src/commons-lang-2.1-sources.jar" kind="lib" path="lib/commons-lang-2.1.jar"/>
        <classpathentry exported="true" sourcepath="/sk.baka.xml.gene/src/main/java" kind="lib" path="lib/gene-0.3.jar"/>
        <classpathentry exported="true" kind="lib" path="lib/isorelax-20030108.jar"/>
   ...

Now you can export all these libraries from your bundle in MANIFEST.MF file by specifying packages

Let's look into sk.uniba.euromath.fop/pom.xml once more. It depends on sk.uniba.euromath plugin, which is marked 'provided'. sk.uniba.euromath plugin depends on commons-lang which should also be marked 'provided' - why did we then introduced that commons-lang dependency in sk.uniba.euromath.fop/pom.xml? It is because sk.uniba.euromath.fop depends on gene-fop library (marked 'compile') which in turn depends on commons-lang, which is thus also marked 'compile', regardless of the sk.uniba.euromath dependency.

Assemblying bundles and RCP

Finally we'll describe all assemblies. Each project (except sk.uniba.euromath.pom) uses assembly to build its bundle. Let's look on sk.uniba.euromath/src/main/assembly/bin.xml. First thing: we need to copy regular dependencies to lib/ directory and omit the eclipse.bundles dependencies completely. Assembly plugin does not allow to use wildcards like eclipse.bundles:* (sigh), so we need to exclude all eclipse.bundles manually. This can be remedied by declaring these bundles as 'provided' instead of 'compile' in pom.xml in the dependency section, but you'd have to manually include all these dependencies later in the sk.uniba.euromath.rcp plugin.

This assembly produces a zip file. I could simply produce a jar file but the maven-assembly-plugin refuses to use our META-INF/MANIFEST.MF file and uses one generated by itself. Note that the assembly generates the zip file without the 'bin' classifier. I had to remove the classifier because Eclipse uses its own bundle name mappings (see sk.uniba.euromath.rcp/src/main/assembly/rcp.xml):

<outputFileNameMapping>${artifactId}_1.4.0.jar</outputFileNameMapping>

The problem is that the classifier is always added - even when you specify this filename mapping. Thus RCP would include bundles such as sk.uniba.euromath.fop_1.4.0-bin.jar.

General rules

Okay, let's divide that mess into steps:

  • First, create parent pom, poms for each eclipse bundle and one pom that'll build RCP application.
  • Use eclipse:eclipse to generate .classpath and .project (tip: be sure to set up the maven-eclipse-plugin to download sources of libraries aswell)
  • Delete all eclipse.bundles bundles from .classpath and add them into MANIFEST.MF
  • For all relevant libraries (all transitive dependencies of your bundle EXCEPT transitive dependencies of other bundles you depend on) create symlink from M2_REPO/repository/blah/blah into lib/, create symlink from -sources.jar into lib/src/
  • Register these symlinks into 'Java Build Path' of your project and put them into MANIFEST.MF
  • Don't use eclipse:eclipse on this project anymore or you'll need to repeat the steps above :)
  • Mark all eclipse.bundles dependencies as 'provided' - this will save you a few lines in assembly descriptor but you'll need to specify these dependencies once more in the sk.uniba.euromath.rcp RCP builder project.
  • For each Eclipse bundle project write an assembly descriptor that produces a zip file with the correct plugin structure. Struggle a bit with 'provided' classifier / excludes/includes assembly rules :)
  • Write the RCP project that assemblies a RCP application.
  • Hunt down all ClassNotFound/NoClassDef errors.
  • Relax, have a beer.

Tips

Ah, the dreaded ClassNotFoundException / NoClassDefFoundError. They can meet you anytime when developing Eclipse bundles and they are often thrown for no apparent reason. So:

  • be sure to check that all libraries are correctly referenced from MANIFEST.MF and they are all readable - check your unix permissions.
  • Check everything. If Eclipse says it cannot find class foo.bar.Baz, placed in lib/a.jar, check if lib/b.jar containing bundle activator is readable and the activator class name is correct (yeah, this happened to me - I had incorrect unix permissions on b.jar once, because of a bug in Assembly - when using files rules in assembly, the assembly plugin will place those files with zero rights into archive)
  • Clean all projects
  • Contact nearest shaman
  • Go out for a beer

If you use Subclipse use the newest version (1.1.4) when dealing with symlinks in Eclipse 3.2.