Image Blog Building Eclipse Plug-Ins
March 1, 2011

How to Build Eclipse Plugins With Maven 3 & Tycho

Java Tools

Automatically building Eclipse plugins 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 plugins when choosing Export... -> Deployable plugins..., 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 plugins, 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 plugins in an environment familiar to most developers and with minimal additional configuration.

Background on Eclipse Plugins

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. 

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 plugins, some of which depend on each other. One plugin 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 plugins at once when doing a new JRebel release
  • sign all our plugins and features
  • build single plugins 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 plugin 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 plugin 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 plugin
    • achievements-general -- adds some general achievements (other plugins might add language specific ones)
    • achievements-general.feature -- feature containing the above plugin
    • update-site -- update site containing all of the above

Since we want to see how Tycho can build existing plugins, let's create the sample projects as PDE plugin projects first, and later add Maven metadata.

The core/UI plugin 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 plugin will contribute two achievements: The Cloner (Copy-paste more than 50 lines) and Manual Man (Turn off Build Automatically). Y

You can import them into Eclipse by doing Import... -> Existing Projects into Workspace.

Creating the Maven & Tycho Metadata

After the projects have been implemented as Eclipse plugins, 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 plugins 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 plugins 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 plugin 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 plugins, features and the update site. cd to each plugin/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 plugins, 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.

Plugin 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 plugins and features - and it didn't require too much configuration.

But what if we've already released the plugins, 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 plugin and not the whole project, and that is why we chose to use the P2 resolver. It resolves plugins 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 plugin. This way, Tycho will download the exact released versions of other plugins 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).


*the reason we didn't want to build all plugins every time for JRebel is that if our Embedded JRebel for Eclipse plugin 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 plugin if it didn't actually change.

Additional Resources

Learn about adding custom installs in Eclipse here.

Read our JRebel Quick Start Guide for Eclipse.

Want to See How JRebel Works?

Try JRebel free for 10-days by clicking the link below.

Try JRebel Today