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

Building Eclipse plug-ins with Maven 3 and Tycho

Automatically building Eclipse plug-ins has been sort of difficult for quite a while.

Running the build manually from PDE works, but it’s pretty much a black box and you can’t always get what you want from that. It can sign plug-ins when choosing Export… -> Deployable plug-ins…, but it can’t do this when building a whole update site. If you are used to Maven, Ant or another command line build tool, then things like this are truly annoying.

There are, of course, some tools provided by the Eclipse Foundation for headless building of plug-ins, but they don’t seem easy to set up or convenient to use. Tycho for Maven 3 aims to change that, making it possible to build OSGi bundles / Eclipse plug-ins in an environment familiar to most developers and with minimal additional configuration.

Background

Tycho still doesn’t have an 1.0 release, but seems pretty stable, if lacking a few features. It doesn’t yet have a lot of updated or detailed documentation, but I found help in blog posts about building with Tycho. Mattias Holmqvist’s posts, for example, are recommended reading.

The information I found was sometimes outdated, though, or didn’t go into the things that we needed to make a headless build for JRebel for Eclipse, which has had over 800 downloads in February alone and continues to climb the charts on the Eclipse Marketplace. I figured that since others may have similar needs to ours at ZeroTurnaround, it would be cool to share this experience with everyone (*note: this post assumes some experience with Maven and PDE).

First, I’ll describe what we needed from Tycho. JRebel for Eclipse consists of 6 Eclipse plug-ins, some of which depend on each other. One plug-in provides general JRebel/Eclipse integration, another embeds JRebel itself, and others provide debugger, WTP and RAD integration. We needed to be able to:

  • use existing meta-data (manifest-first model), so that we can still use PDE for development
  • build the whole set of plug-ins at once when doing a new JRebel release
  • sign all our plug-ins and features
  • build single plug-ins separately to release bug fixes quickly

It was actually quite easy to get the first three requirements met by a Tycho build, but it turned out the fourth one was harder to achieve (more on that later).

At first I just added a pom.xml file to each plug-in and feature, specifying the artifact name, version and packaging type for each. Tycho can even generate a basic pom.xml for you. A parent pom.xml was added to define some common Maven plug-in settings. I also created a new update site project, which only has the site.xml that says which versions of what features to add to the site.

Sample project: Achievements for Eclipse

To make things easier to demonstrate, let’s create a sample project that mimics the setup I used. And to make it more fun (or ridiculous, take your pick), the sample project will add a couple of achievements to Eclipse, similar to Steam, Xbox or Playstation achievements. I got the idea for that from this post. The project structure will be like this:

  • achievements — root/parent project
    • achievements-ui — core and UI code together (unlike most eclipse.org projects)
    • achievements-ui.feature — feature containing the above plug-in
    • achievements-general — adds some general achievements (other plug-ins might add language specific ones)
    • achievements-general.feature — feature containing the above plug-in
    • update-site — update site containing all of the above

Since we want to see how Tycho can build existing plug-ins, let’s create the sample projects as PDE plug-in projects first, and later add Maven metadata.

The core/UI plug-in will keep track of achievements gotten (per workspace) and will display pop-ups for them when they are first achieved. We will cheat a bit and reuse the pop-up notification UI from JRebel for Eclipse, although it isn’t exactly a public API.

To keep things simple, each Achievement is defined by three String fields: a unique ID, a title and a textual description. The general achievements plug-in will contribute two achievements: The Cloner (Copy-paste more than 50 lines) and Manual Man (Turn off Build Automatically). You can find the complete zipped source code for the projects (without pom.xml-s) here:

achievements-without-poms.zip.

You can import them into Eclipse by doing Import… -> Existing Projects into Workspace. At the end of the article, you will find the link to source code including pom.xml-s.

And here are some examples of the plug-ins in action. First, we turn off Build Automatically:

The achievement pop-up should open immediately:

We can also open the Achievements view with Ctrl + 3 to see all the Achievements we’ve gotten so far:

Creating the Maven/Tycho metadata

After the projects have been implemented as Eclipse plug-ins, with the features and update site created for them, we will create a parent project which simply contains a pom.xml. The parent POM defines dependencies on the Tycho Maven plug-ins and also has some set-up for code signing, but we will skip the actual signing for this post. Homework: generate a Java keystore containing a private key and certificate for signing, then sign the code by running Maven with the “sign” profile: mvn -Psign -Dkeystore=... -Dstorepass=... -Dalias=...

There are three ways of resolving dependencies in Tycho:

  1. Simply specify the tycho.targetPlatform property, pointing it at an Eclipse installation.
  2. Let Tycho use the P2 resolver, which resolves plug-ins from repositories defined in the POM.
  3. Use an Eclipse Target Definition in a separate artifact, but this doesn’t seem to be fully supported yet.

We will use the second approach, the reasoning for that will be explained later. The P2 resolver needs some repositories to download plug-in dependencies from, so we’ll add the Eclipse Helios and ZeroTurnaround update sites as repositories. The latter is needed because of the dependency on JRebel’s pop-up notification UI.

The pom.xml for the parent project looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.zeroturnaround.achievements</groupId>
  <artifactId>org.zeroturnaround.achievements.parent</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>Achievements for Eclipse</name>
  <properties>
    <tycho.version>0.10.0</tycho.version>
  </properties>
  <modules>
    <module>achievements-ui</module>
    <module>achievements-ui.feature</module>
    <module>achievements-general</module>
    <module>achievements-general.feature</module>
    <module>update-site</module>
  </modules>
  <repositories>
    <repository>
      <id>eclipse-helios</id>
      <layout>p2</layout>
      <url>http://download.eclipse.org/releases/helios</url>
    </repository>
    <repository>
      <id>zeroturnaround</id>
      <layout>p2</layout>
      <url>http://www.zeroturnaround.com/update-site</url>
    </repository>
  </repositories>
  <profiles>
    <profile>
      <id>sign</id>
      <!-- To sign plug-ins and features, run: mvn -Psign -Dkeystore=<path>
        -Dstorepass=*** -Dalias=<keyalias> clean install -->
      <build>
        <pluginManagement>
          <plugins>
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-jar-plugin</artifactId>
              <version>2.3.1</version>
              <executions>
                <execution>
                  <goals>
                    <goal>sign</goal>
                  </goals>
                </execution>
              </executions>
              <configuration>
                <verify>true</verify>
                <jarPath>${project.build.directory}/${project.build.finalName}.jar</jarPath>
              </configuration>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
    </profile>
  </profiles>
  <build>
    <plugins>
      <plugin>
        <groupId>org.sonatype.tycho</groupId>
        <artifactId>tycho-maven-plugin</artifactId>
        <version>${tycho.version}</version>
        <extensions>true</extensions>
      </plugin>
      <plugin>
        <groupId>org.sonatype.tycho</groupId>
        <artifactId>target-platform-configuration</artifactId>
        <version>${tycho.version}</version>
        <configuration>
          <resolver>p2</resolver>
        </configuration>
      </plugin>
    </plugins>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-jar-plugin</artifactId>
          <version>2.3.1</version>
        </plugin>
        <plugin>
          <groupId>org.codehaus.tycho</groupId>
          <artifactId>maven-osgi-packaging-plugin</artifactId>
          <version>${tycho.version}</version>
          <executions>
            <execution>
              <id>timestamp</id>
              <phase>validate</phase>
              <goals>
                <goal>timestamp</goal>
              </goals>
            </execution>
          </executions>
          <!-- for some reason configuration won't work here, have to define
            in each module -->
          <configuration>
            <archive>
              <addMavenDescriptor>false</addMavenDescriptor>
            </archive>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

Next, we let Tycho generate the initial POM-s for plug-ins, features and the update site. cd to each plug-in/feature/update-site directory and run

mvn org.sonatype.tycho:maven-tycho-plugin:generate-poms -DgroupId=org.zeroturnaround.achievements
-Dtycho.targetPlatform=/path/to/eclipse

The initial generated pom will simply include artifactId, version, packaging type etc. detected from the PDE metadata. We will add the parent POM reference and some build settings needed for signing and packaging.

If we don’t want the Maven metadata to be included in the plug-ins, we have to add the <addMavenDescriptor>false</addMavenDescriptor> bit to each plug-in and feature. Simply adding it to the parent POM didn’t work with Tycho 0.10. The maven-jar-plugin reference is needed to apply the code signing configuration defined in the parent POM.

Plug-in POM-s look like this:

<?xml version="1.0" encoding="UTF-8"?>
<project
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
  xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.zeroturnaround.achievements</groupId>
    <artifactId>org.zeroturnaround.achievements.parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
  </parent>
  <groupId>org.zeroturnaround.achievements</groupId>
  <artifactId>org.zeroturnaround.achievements.ui</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>eclipse-plugin</packaging>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.sonatype.tycho</groupId>
        <artifactId>maven-osgi-packaging-plugin</artifactId>
        <version>${tycho.version}</version>
        <configuration>
          <archive>
            <addMavenDescriptor>false</addMavenDescriptor>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

And feature POM-s look pretty much the same, except the packaging type is eclipse-feature instead of eclipse-plugin.

<?xml version="1.0" encoding="UTF-8"?>
<project
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
  xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.zeroturnaround.achievements</groupId>
    <artifactId>org.zeroturnaround.achievements.parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
  </parent>
  <groupId>org.zeroturnaround.achievements</groupId>
  <artifactId>org.zeroturnaround.achievements.ui.feature</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>eclipse-feature</packaging>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.sonatype.tycho</groupId>
        <artifactId>maven-osgi-packaging-plugin</artifactId>
        <version>${tycho.version}</version>
        <configuration>
          <archive>
            <addMavenDescriptor>false</addMavenDescriptor>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

The update site POM can stay pretty basic, I only added the reference to the parent POM and changed the artifactId and version.

<?xml version="1.0" encoding="UTF-8"?>
<project
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
  xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.zeroturnaround.achievements</groupId>
    <artifactId>org.zeroturnaround.achievements.parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
  </parent>
  <groupId>org.zeroturnaround.achievements</groupId>
  <artifactId>org.zeroturnaround.achievements.update.site</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>eclipse-update-site</packaging>
</project>

Now, once you have all the pom.xml-s created, you should already be able to successfully build everything by running

mvn clean install

The first time you build, Tycho will download the necessary dependencies from the update sites specified as repositories in the parent POM (Eclipse Helios and ZeroTurnaround update sites). The fully built update site will be generated in update-site/target/site.

Final thoughts

See, that wasn’t hard at all, right?

We can now easily do a full build of the whole update site, including signing the plug-ins and features – and it didn’t require too much configuration.

But what if we’ve already released the plug-ins, and now want to release an update for the achievements-general module to add a new achievement, while keeping the existing build of the achievements-ui module? If we don’t unnecessarily build the other modules, we won’t make users update features that were not changed*.

If you use tycho.targetPlatform to specify the Eclipse installation to build against, Tycho resolves dependencies only from that target platform; however, this doesn’t work if we want to build a single plug-in and not the whole project, and that is why we chose to use the P2 resolver. It resolves plug-ins from the local repository as well.

So, if we want to build achievements-general again, we need to have achievements-ui already “installed” in the local Maven repository (and we’ll build in this order: achievements-general, achievements-general.feature and update-site). If you delete org/zeroturnaround/achievements from your local repository and try to build only the achievements-general module, you will likely get this error:

org.eclipse.equinox.p2.core.ProvisionException: No solution found because the problem is unsatisfiable.

This means P2 couldn’t resolve the dependency on achievements-ui. But what if we don’t have the exact build we released in the local repository any more?

This may be somewhat hackish, but I ended up adding the published update site as a repository in the parent POM and deleting all the project artifacts from the local repository before building a single plug-in. This way, Tycho will download the exact released versions of other plug-ins when it re-builds the update site. It’s a bit circular, so if someone has a better solution, I’d be happy to hear it (keep in mind that Tycho is replacing .qualifier/-SNAPSHOT in version strings each time it builds).

Here is the zipped source code for the example project:


* the reason we didn’t want to build all plug-ins every time for JRebel is that if our Embedded JRebel for Eclipse plug-in is updated, that will change the location of jrebel.jar in your local filesystem, and any launch/server configurations that don’t use ${jrebel_args} will keep pointing to the old version. In a future update, we should update those configurations automatically where possible, but we currently don’t, so we are just better off not releasing any updates to that plug-in if it didn’t actually change.

  • Hendrik Eeckhaut

    “for some reason configuration won’t work here, have to define in each module”

    I think this is because you made a mistake in your parent pom file. You use org.codehaus.tycho as groupID. This should be org.sonatype.tycho instead.

  • Erkki Lindpere

    Oh god, thanks for cathing that. That bit gave me headaches and turns out it was a simple mistyped identifier :|

  • http://www.pessinlaw.com/miami-personal-injury-lawyer/ Miami Personal Injury Lawyer

    Tycho for Maven 3 aims to change that, making it possible to build OSGi bundles / Eclipse plug-ins in an environment familiar to most

  • Yogesh Jadhav

    I am having trouble buidling eclipse plugin using CLASSPATH VARIABLE in Java build path (M2_REPO). I added a log4j library using the M2_REPO variable and tried compiling the project using tycho-maven-plugin. But I am getting compilation error and it seems maven-osgi-compiler-plugin is not referring to the M2_REPO classpath variable. I am stuck here and not able to proceed, can you please tell me where I am going wrong?

  • Krzysztof Ciesielski

    I imported maven projects to eclipse but there are compilation errors.
    Class: AchievementsTracker
    Unresolved import: org.zeroturnaround.eclipse.notifications.NotificationManager

    I cannot find this class in attached projects and in maven dependencies. Did I miss something?

  • Erkki Lindpere

    As I mentioned in the post “We will cheat a bit and reuse the pop-up notification UI from JRebel for Eclipse, although it isn’t exactly a public API.”

    The projects have dependencies on the “JRebel Eclipse Integration” plug-in and use that to show pop-up notifications.

    Also, if you imported them as M2Eclipse projects, I’ve found that it doesn’t work well with Eclipse plug-in projects.

  • Rstudner

    This (as well as all uses of tycho) fail for me, unless I do -Dtycho.targetPlatform=/home/me/myeclipseinstall with this error:

    me@rogerlinux:~/svn/new-studio$ mvn clean verify
    [INFO] Scanning for projects…
    [WARNING] No explicit target runtime environment configuration. Build is platform dependent.
    [WARNING] No explicit target runtime environment configuration. Build is platform dependent.
    [WARNING] No explicit target runtime environment configuration. Build is platform dependent.
    [WARNING] No explicit target runtime environment configuration. Build is platform dependent.
    [INFO] Resolving target platform for project MavenProject: studio.eclipse:com.foo.studio:1.0.0-SNAPSHOT @ /home/me/svn/new-studio/plugins/com.foo.studio/pom.xml
    [INFO] Cannot complete the request.  Generating details.
    [INFO] Cannot complete the request.  Generating details.
    [INFO] {org.osgi.framework.executionenvironment=OSGi/Minimum-1.0,OSGi/Minimum-1.1, osgi.ws=gtk, osgi.arch=x86_64, osgi.os=linux, org.eclipse.update.install.features=true, org.osgi.framework.system.packages=}
    [ERROR] Cannot resolve project dependencies:
    [ERROR]   Software being installed: com.foo.studio 1.0.0.qualifier
    [ERROR]   Missing requirement: com.foo.studio 1.0.0.qualifier requires ‘bundle org.eclipse.ui 0.0.0′ but it could not be found
    [ERROR]
    [ERROR] Internal error: java.lang.RuntimeException: “No solution found because the problem is unsatisfiable.”: [“Unable to satisfy dependency from com.foo.studio 1.0.0.qualifier to bundle org.eclipse.ui 0.0.0.”, “Unable to satisfy dependency from com.foo.studio 1.0.0.qualifier to bundle org.eclipse.core.runtime 0.0.0.”,

  • Erkki Lindpere

    Perhaps you don’t have an Eclipse repository in your pom.xml (or settings.xml)?

    For example, in the example application, the parent pom added Eclipse Helios repository, where these dependencies are resolved from:

     
       
          eclipse-helios
          p2
          http://download.eclipse.org/releases/helios
       
        …
     

  • Rstudner

    false

    is this supposed to prevent:
            0  2012-01-03 11:47   META-INF/maven/
            0  2012-01-03 11:47   META-INF/maven/studio.eclipse/
            0  2012-01-03 11:47   META-INF/maven/studio.eclipse/com.foo.studio/
         1284  2012-01-03 11:46   META-INF/maven/studio.eclipse/com.foo.studio/pom.xml
          128  2012-01-03 11:47   META-INF/maven/studio.eclipse/com.foo.studio/pom.properties

    from being in my plugin jar? 

    (if so, obviously it isn’t working heh)

  • Rstudner

    Aha!  it was related to this.. I had this in a parent/parent POM, but from the grandchild up, there was a typo so a parent reference was failing.

    thanks!

  • Erkki Lindpere

    I think there was a typo with this in the example project. The following should prevent that:

                   
                        org.eclipse.tycho
                        tycho-packaging-plugin
                        ${tycho.version}
                       
                           
                                false
                           
                       
                   

    Note that for some reason disqus is converting the camelCase words into lower case, add-maven-descriptor is supposed to be in camelCase

  • Komail Abbas Badami

    Hi,
    Thanks for the article Erkki. I need some help, I downloaded the zip file with poms and did a mvn clean install. It said “BUILD SUCCESS”.  I went into the -> achievementsupdate-sitetarget folder and then zipped the site folder and tried to install it as a plugin, this is giving me a error saying no software site found at jar file… the zip file has the features, plugin folders, content.jar, artifacts.jar and the site.xml files… Wonder what is wrong… Could you please help me with this… 

  • Skathayat

    Hi,

    Thank you so much the useful post.

    I am trying to add a java project (sources) as a dependency to my eclipse plugin project (rcp).

    Project structure is like this:

    +Parent-project
        – core-api (this is a simple java project)
        – plugin-project (this is eclipse plugin/rcp project and includes sources from core-api)
        – web-app (this is J2ee project and also includes sources from core-api)

    I am trying to include  core-api project  as dependency in the plugin-project POM.XML like:

    example.group
    core-api
    test

    But class cannot be resolved errors pops up when I use mvn install or mvn compile on the parent-project.

    Any suggestions will be highly appreciated.:)

    -Surya