maintain library project for Maven and Eclipse RCP/ OSGi in the same repository

If you are working both in the Maven and the Eclipse RCP/ OSGi universe, you might find it quite frustrating that these two worlds cooperate so poorly. In order to use the same library in both worlds, a lot of error-prone copy-and-paste is needed, at least if you would like to use this project in a Maven and Maven Tycho build.

In principle, there is not much that is different for a Maven artifact and an Eclipse RCP/ OSGi Plug-in:
In an Eclipse RCP/ OSGi Plug-in you just need an additional META-INF/MANIFEST.MF and build.properties file. The problem comes with the Maven pom file, which looks completely different for standard Maven and Maven Tycho. Luckily, you can specify the name of the pom file when invoking Maven:

Therefore, you can just maintain two pom files and with only few limitations, it is possible to maintain a library that can be used in a standard Maven build and in a Maven Tycho build within the same repository:

  1. Modify your .gitignore so that it contains the following lines:
    bin
    .project
    .settings
    .classpath
    
    We don’t want to have any Eclipse project configuration information in the project, since we want to be able to import it both as ‘Maven project’ and ‘Eclipse/ OSGi project’.
  2. Remove all files from the git index that match the pattern above.
    • If your project is a Maven project, right-click on the project and select ‘Configure -> Convert to Plug-in project’. This will create the META-INF/MANIFEST.MF and build.properties files.
    • If your project is a Plug-in project, right-click on the project and select ‘Configure -> Convert to Maven project’. This will create the pom.xml file.
    These steps are optional and only needed if you are missing either pom.xml or META-INF/MANIFEST.MF and build.properties .
  3. Create the Tycho pom file, name it e.g. pom-tycho.xml. It’s content should look like this:
  4. Commit all changes and delete the project from your workspace.
  5. Import the project using the ‘Import Existing Maven Project’ or ‘Checkout Maven Project from scm’ wizard. To use the project as an Eclipse RCP/ OSGi Plug-in project, disable it’s Maven nature via ‘Maven -> disable Maven nature’.
  6. Done!
Note: You manually need to keep in sync the version strings in pom.xml, META-INF/MANIFEST.MF and pom-tycho.xml! E.g. after running a Maven release, you need to adapt the version numbers in META-INF/MANIFEST.MF and pom-tycho.xml so that they match the automatically changed version strings in pom.xml.
Also note that you cannot use a Bundle Activator in this project. If you need one, just create another Eclipse-RCP Plug-in that depends on your library and contains a Bundle Activator.

Troubleshooting: After importing as Maven project or converting to Maven project, you might run into build-path problems. Try to resolve using ‘right-click on project -> Maven -> Update Project..’ And/ or fix the Java Build Path of the project (Standard Eclipse source folder is src, in Maven it is src/main/java). java-build-path

Maven and OSGi version hierarchies

Maven:
1
1.1
1.1.1-SNAPSHOT
1.1.1
OSGi:
1
1.1
1.1.1
1.1.1.qualifier
Note that in Maven terms, a snapshot version is lower than the corresponding ‘normal’ version, but in OSGi terms, it is higher (qualifier equals SNAPSHOT).

More generally: OSGi comes with four identifiers (major.minor.micro.qualifier). Maven comes with three identifiers (major.minor.incremental).

The big difference is that Maven additionally provides a qualifier, which is separated by a - instead of .. A version containing a qualifier is always considered to be older than the same version without qualifier string. This is in contrast to OSGi, where the qualifier is just another part of the full version string, just like major or minor(see above).

set up your own p2 repository

  1. Prepare a directory of your choice that will contain your plug-ins and features that should be packed into a p2 repository. It can be any directory but needs to contain a folder named plugins and features. E.g.
    p2-source-dir/
    ├── category.xml
    ├── features
    └── plugins
        ├── [..]
        ├── [..]
    
    Place all your plug-ins into the plugins folder, all features into the feature folder. Use this category.xml:
    
    
       
    
            
            
                providedCapabilities.exists(p | p.namespace == 'osgi.bundle')
            
        
    
    
  2. Execute the Features and Bundles Publisher Application which will generate the p2 repository for you:
    java -jar bin/eclipse-rcp-mars/plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar -application org.eclipse.equinox.p2.publisher.FeaturesAndBundlesPublisher -metadataRepository file:$repo_target -artifactRepository file:$repo_target -source $repo_source -configs gtk.linux.x86_64 -publishArtifacts
    
  3. Execute the Category Publisher:
    java -jar bin/eclipse-rcp-mars/plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar -application org.eclipse.equinox.p2.publisher.CategoryPublisher -metadataRepository file:$repo_target -categoryDefinition file:$category_file_location
    
  4. The generated repository will look like this:
    repository/
    ├── artifacts.xml
    ├── content.xml
    ├── category.xml
    └── plugins
        ├── [..]
        ├── [..]
    └── features
        ├── [..]
        ├── [..]
    
    Done!
    The p2-repository is ready for local use!
  5. As a next step, you can copy the repository to a location accessible via HTTP, as a Tycho build e.g. is not able to use a local p2 repository (See here).
  6. If you are developing some libraries using maven and would like to put them to your p2-repository as well, you can prepare them as described here.
  7. If you would like to use some third-party libs, such as apache-commons, you can convert them into OSGi/ p2 compatible plug-ins and publish them into another p2-repository using the p2-maven-plugin.

release jar’s using maven that can be used as OSGi bundles as well

Once
  1. Configure your jar plugin to include an OSGi compatible MANIFEST.MF file:
    
    
    
    org.codehaus.mojo
    build-helper-maven-plugin
    1.9.1
    
    
    set-osgi-version
    verify
    
    parse-version
    
    
    
    regex-property
    verify
    
    regex-property
    
    
    hans
    ${parsedVersion.osgiVersion}
    SNAPSHOT
    qualifier
    false
    
    
    
    
    
    org.apache.maven.plugins
    maven-jar-plugin
    2.3.2
    
    
    
    ${parsedVersion.osgiVersion}
    ${project.description}
    ${project.version}
    
    META-INF/MANIFEST.MF
    
    
    
    
    org.apache.maven.plugins
    maven-source-plugin
    2.4
    
    
    attach-sources
    verify
    
    jar
    
    
    
    
    
    
    2
    ${project.name}
    ${project.description}
    ${project.groupId}.${project.artifactId}.source
    ${project.organization.name}
    ${hans}
    ${project.groupId}.${project.artifactId};version="${hans}";roots:="."
    
    
    
    
    
    
Every release
  1. Commit all your changes.
  2. Adapt pom.xml to match the new version plus -SNAPSHOT string.
  3. Adapt MANIFEST.MF to match the new version omitting -SNAPSHOT string or .qualifer string respectively. Example:
    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-RequiredExecutionEnvironment: J2SE-1.5
    [..]
    Bundle-Version: 1.0.0
    [..]
  4. Make sure all your exported packages match the new version. Example:
    [..]
    Bundle-Version: 1.0.0
    Export-Package: net.sf.my.tools;version="1.0.0",
     net.sf.my.tools.async;version="1.0.0",
     net.sf.my.tools.comparator;version="1.0.0",
    Import-Package: org.junit;resolution:=optional,
     org.slf4j
    
    Again no .qualifier string!
  5. Perform your release as usual:
    user@client:~$ mvn clean release-prepare
    user@client:~$ mvn release-perform
    
  6. During the release, Maven will update the artifact version to the next iteration, e.g. 1.0.1-SNAPSHOT. This is done automatically but affects only the pom.xml. You have to adapt MANIFEST.MF manually to reflect the new version. This time, take over the Maven version string as given (replacing -SNAPSHOT with .qualifer). Example:
    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-RequiredExecutionEnvironment: J2SE-1.5
    [..]
    Bundle-Version: 1.0.1.qualifier
    [..]
  7. Again, make sure all your exported packages match the new version. Example:
    [..]
    Bundle-Version: 1.0.0
    Export-Package: net.sf.my.tools;version="1.0.1.qualifier",
     net.sf.my.tools.async;version="1.0.1.qualifier",
     net.sf.my.tools.comparator;version="1.0.1.qualifier",
    Import-Package: org.junit;resolution:=optional,
     org.slf4j
    
    This time include the .qualifier string.
  8. Done!
Known bugs and limitations:
  1. When you add this p2-repository to an Eclipse Target Platform, Eclipse will not correctly distinguish between artifact and source artifact.
  2. It is not very convenient to adapt the OSGi versions manually before and after each release. A shell script or Maven plugin may help here.