Sunday, March 7, 2010

Automating Eclipse Source Bundle Generation using Maven

Introduction:

In my previous blog post, I explained about how to convert third party and in-house API libraries to OSGi bundles. Once you convert your in-house API libraries to OSGi bundles, you will not be able to browse/debug your API source code, while working on GUI code. "Attach source" will not be an option anymore in Eclipse, since the dependency is now a plugin-dependency. To be able to view/debug the source code of plugin dependency, you need to have the corresponding source bundle in target platform.

What is Eclipse Source Bundle?

Eclipse source bundle is a normal source jar and is also a valid OSGi bundle. It has OSGi related information in it's manifest. Below is a sample manifest for a source bundle:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: cruise
Build-Jdk: 1.6.0_10
Bundle-ManifestVersion: 2
Bundle-Name: Utility
Bundle-SymbolicName: com.mycompany.utility.source
Bundle-Vendor: MyCompanyName
Bundle-Version: 1.0.0.SNAPSHOT
Eclipse-SourceBundle: com.mycompany.utility;version="1.0.0.SNAPSHOT";roots:="."

Here the last line (Eclipse-SourceBundle) is the one which identifies this bundle as a source bundle. This says that this bundle is source bundle of "com.mycompany.utility" bundle whose version is "1.0.0.SNAPSHOT". Note that bundle name and version has to match exactly.

roots directive says which folder within the source bundle has source code. By default it is "." and it is true for most cases. If you have a special case, you can change it.

Automation:

Automation of source bundles can be done using maven build helper and maven source plugins. In your project's POM or parent POM, add the following block in build section:

<!-- Build helper maven plugin sets the parsedVersion.osgiVersion property -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>set-osgi-version</id>
<phase>verify</phase>
<goals>
<goal>parse-version</goal>
</goals>
</execution>
</executions>
</plugin>

<!-- source maven plugin creates the source bundle and adds manifest -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestEntries>
<Bundle-ManifestVersion>2</Bundle-ManifestVersion>
<Bundle-Name>${name}</Bundle-Name>
<Bundle-SymbolicName>${groupId}.${artifactId}.source</Bundle-SymbolicName>
<Bundle-Vendor>${organization.name}</Bundle-Vendor>
<Bundle-Version>${parsedVersion.osgiVersion}</Bundle-Version>
<Eclipse-SourceBundle>${groupId}.${artifactId};version="${parsedVersion.osgiVersion}";roots:="."</Eclipse-SourceBundle>
</manifestEntries>
</archive>
</configuration>
</plugin>

Here, maven source plugin takes care of creating the source jar and adding appropriate information to it's manifest so that it can be a valid OSGi source bundle. One small problem is, maven adds "-" before qualifier, whereas OSGi adds "." before qualifier. If you use BND to turn your jar into bundle, the bundle version would be 1.0.0.SNAPSHOT, whereas maven version is 1.0.0-SNAPSHOT. This is where, maven build helper plugin helps above. Make sure to use the latest version (1.5) of this plugin, which has parse-version goal. This goal sets the parsedVersion.osgiVersion property, which has the version string, but has the "-" replaced by ".".

Automate copying source bundles to Eclipse target platform:

Now, that source bundles are automatically created and installed/deployed to your maven repo, next step is to make sure they are automatically copied to Eclipse target platform, so that Eclipse can recognize it. Only then, you can view/debug the source code from within Eclipse. In my previous post, I explained about how to automate copying dependency bundles to target platform. Look at "Automate Copying Bundles to Eclipse target platform" section in that post. Similarly, you should be able to copy the dependency source bundles to target platform.

<properties>
<pluginsDir>./plugins</pluginsDir>
</properties>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<!-- This execution downloads and copies the dependency bundles -->
<execution>
<id>copy-dependencies</id>
<phase>process-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${pluginsDir}</outputDirectory>
</configuration>
</execution>
<!-- This execution downloads and copies the dependency source bundles -->
<execution>
<id>copy-dependency-sources</id>
<phase>process-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<classifier>sources</classifier>
<failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact>
<outputDirectory>${pluginsDir}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>

Maven dependency plugin takes care of downloading/copying the dependencies and it's corresponding source bundles. Here, "sources" classifier takes care of the source bundle. Now, whenever you do "mvn process-resources", this would pull in latest OSGi bundle dependencies and also it's source bundles from maven repo and copy to plugins folder in target platform.

Conclusion:

We saw what an Eclipse source bundle is and how to automatically create it, whenever the bundle is built. We also saw how to download and copy the source bundles from maven repo to your local Eclipse target platform, using maven plugins.

6 comments:

AlBlue said...

Just so that you're aware; it's also possible to get Eclipse to embed a Maven-like SCM URL using Eclipse-SourceReferences. It will be coming up in the 3.6M6 build as far as I know - more details from my blog.

Raja said...

Thanks Alex. That would be very useful. I'll keep an eye on this when 3.6 comes out.

Royal Bakes said...

How to read Bundle-Version from code?

Raja said...

@java_realtime - You could get BundleContext from your Activator's start and stop methods. From BundleContext you can get the Bundle and from Bundle you can call getVersion to get the version of a bundle. This is one way to get the bundle version.

Iulian said...

I always get:


[INFO] --- maven-source-plugin:2.2.1:jar (attach-sources) @ org.scala-ide.scala.library ---
[INFO] No sources in project. Archive not created.
[INFO]


Like you, I am generating bundles from jars, using bnd. I unpack sources in ${project.basedir}/src in the generate-sources phase, but still it complains there are no sources (the sources are there).

Anonymous said...

Is it possible to use the OSGi bundles in place of utility jars - public as well as home grown - in a JavaEE application or a simple standalone application?

If it is possible, how can I achieve it? I cannot use Maven or ony other utilities of that kind. I can use RAD / Eclipse