|
HowToUseIvyWithYourOsgiProject
How to use Ivy to manage your OSGi bundle dependencies
IntroThis tutorial provides an example of how to setup your OSGi project and manage your bundle dependencies using Ant and Ivy. The code and config samples are based on a Eclipse RCP project, but should apply equally to any OSGi project. The main problem with using Ivy with OSGi bundles, at the time of writing this tutorial, is that many open source project don't ship up-to-date bundles (there are a few exceptions, such as SLF4J). Also, Ivy piggy-backs off Maven repos, which do contain some bundles, but it is still a bit ad-hoc and incomplete. Another problem is when building an Eclipe RCP application, if you want to create your own Target Platform, you either dump all the RCP SDK bundles into a directory, or you copy them over individually by hand. Both of these methods can be fine for one-off projects, but if you have many projects on the go, each with different dependencies, it all starts to get a bit messy. This tutorial doesn't go into the detail of how to compile your bundle with Ant, it is assumed you are either using the Eclipse PDE or have already rolled your own build script. Ant, Ivy and BundlesThe tutorial application will use the following project layout. The coyote-bundle and roadrunner-bundle are provided as stubs for the main project implementation, and outside the scope of this tutorial. The lib directory is used to contain the Ant lib-build.xml, which manages the external bundle dependencies for the project. This script is intended as a utility script that will be imported by the two project bundles, rather than being a standalone build script.
To add Ivy support, the lib-build.xml contains the following targets. <!-- Provide relative access the lib and bundles dirs from the bundle build scripts. -->
<property name="lib.dir" value="${basedir}/../lib"/>
<property name="bundles.dir" value="${basedir}/../lib/bundles"/>
<!-- Initialize Ivy with our project-wide settings file. -->
<target name="lib-init">
<ivy:settings file="${lib.dir}/ivysettings.xml" />
</target>
<target name="lib-install-bundles" depends="lib-init">
<mkdir dir="${bundles.dir}"/>
<!-- Load and resolve the ivy dependencies. -->
<ivy:resolve file="${basedir}/ivy.xml"/>
<!-- Retrieve the dependencies -->
<ivy:retrieve pattern="${bundles.dir}/[artifact]-[revision].[ext]" />
</target>
When new bundle dependencies are added to the ivy.xml file, the lib-install-bundles target is used to build the acme-project/lib/bundles directory. This directory can also be used as the root directory for a Target Platform, when developing with the Eclipse PDE. This is where things start to get a little tricky. Ivy was not really intended to be used for OSGi bundles, because it doesn't support introspection of the bundle meta-data to determine external dependencies. Thankfully, the Ivy tool is very flexible and transparent, and can fairly easily be made to support bundle dependencies. This is also made easier by the great work done on the SpringSource Enterprise Bundle Repository, where many common open source projects are provided as bundles, and with Ivy <dependency/> snippets to add to your ivy.xml files. The following shows the content of the ivysettings.xml file, for acme-project, which uses the Spring framework, and other dependencies provided by the SpringSource repo. <ivysettings>
<property name="local-cache.dir" value="/workspace/osgi/ivy/local-cache" />
<property name="spring-repo.url" value="http://repository.springsource.com/ivy/bundles" />
<!-- Create a local cache of any downloads, and never delete them. -->
<caches default="localcache">
<cache name="localcache" basedir="${local-cache.dir}">
<ttl duration="0d" />
</cache>
</caches>
<resolvers>
<!-- Build a chain on resolvers, each one is tried in order. -->
<chain name="default" returnFirst="true">
<!-- First try our local cache -->
<filesystem name="local-cache-repo">
<ivy pattern="${local-cache.dir}/[organisation]/[module]/ivy-[revision].xml" />
<artifact pattern="${ivy-repo.dir}/[organisation]/[module]/[artifact]-[revision].[ext]" />
</filesystem>
<!-- Then try -->
<url name="spring-release-repo">
<ivy pattern="${spring-repo.url}/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="${spring-repo.url}/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
<url name="spring-external-repo">
<ivy pattern="${spring-repo.url}/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="${spring-repo.url}/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
</chain>
</resolvers>
</ivysettings>Finally, we can declare our project dependencies and Ivy can start to do its thing. Here is the content of the ivy.xml file for the coyote-bundle: <ivy-module>
<info organisation="acme" module="${module.name}" />
<dependencies>
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.logging" rev="1.1.1"/>
<dependency org="org.apache.log4j" name="com.springsource.org.apache.log4j" rev="1.2.15"/>
<dependency org="org.aopalliance" name="com.springsource.org.aopalliance" rev="1.0.0" />
<dependency org="org.springframework" name="org.springframework.beans" rev="2.5.5.A"/>
<dependency org="org.springframework" name="org.springframework.context" rev="2.5.5.A"/>
<dependency org="org.springframework" name="org.springframework.context.support" rev="2.5.5.A"/>
<dependency org="org.springframework" name="org.springframework.core" rev="2.5.5.A"/>
<dependency org="org.springframework" name="org.springframework.aop" rev="2.5.5.A" />
<dependency org="org.springframework.osgi" name="org.springframework.osgi.core" rev="1.1.0"/>
<dependency org="org.springframework.osgi" name="org.springframework.osgi.extender" rev="1.1.0"/>
<dependency org="org.springframework.osgi" name="org.springframework.osgi.io" rev="1.1.0"/>
</dependencies>
</ivy-module>As you can see in the above, the coyote-bundle now has the dependencies required to create a bundle using Spring. As powerful as Spring is, you are still probably going to need more external dependencies if you are building a 'real world' application, especially if it's based on Eclipse RCP. At this time, there is no Ivy repo that contains up-to-date Eclipse RCP bundles (well, certainly any that I know of). So, as mentioned in the Intro, the options are to download the RCP SDK and dump all the files into the acme-project/lib/bundles, or copy them by hand if you prefer to be a little more precise. Bundle Management with BushelNOTE: This is currently based on Bushel 0.5, it will (hopefully) be updated in the next few days to Bushel 0.6 So this is where Ivy, and the Bushel Ant Task, start to do some heavy lifting and allow you to get on with more important stuff. The purpose of Bushel is to convert a directory of ad-hoc OSGi bundles, into structured directory, with appropriate meta-data that can be used as an Ivy repository. Bushel was written with the eclipse/plugins directory as a bundle source in mind, however it can be used for any collection of bundles. The following is an example directory layout for more complex bundle management:
The first stage is to add an Ant task to install the bundles in eclipse-3.4/plugins and other-bundles. This is done using the following: <taskdef name="bushel" classname="org.ezaero.bushel.BushelAntTask">
<classpath>
<fileset dir="${jars.dir}" includes="bushel-*.jar" />
<fileset dir="${jars.dir}" includes="groovy-all-*.jar" />
</classpath>
</taskdef>
<target name="build-local-ivy-repo">
<bushel destdir="/workspace/ivy/local-repo" srcdir="/workspace/bundles/eclispse-3.4/plugins" verbose="true" />
<bushel destdir="/workspace/ivy/local-repo" srcdir="/workspace/bundles/other-bundles" verbose="true" />
</target>This will create the directory layout and meta-data in the local-repo folder that looks something like:
Where ivy-3.2.0.v20080529.xml contains the following: <ivy-module version='1.0'>
<info organisation='org.eclipse' module='org.eclipse.ant.core' revision='3.2.0.v20080529' status='release' />
<configurations>
<conf name='default' visibility='public' />
</configurations>
<publications>
<artifact name='org.eclipse.ant.core' type='jar' ext='jar' />
<artifact name='org.eclipse.ant.core.source' type='src' ext='jar' />
</publications>
</ivy-module>Now, the local repo can be added to the ivysetting.xml, like this: <!-- Add the following property with the others. -->
<property name="local-repo.dir" value="/workspace/osgi/ivy/local-repo" />
<!-- Add the following file resolver to the chain, after the local-cache. -->
<filesystem name="local-repo">
<ivy pattern="${local-repo.dir}/[organisation]/[module]/ivy-[revision].xml" />
<artifact pattern="${local-repo.dir}/[organisation]/[module]/[type]s/[artifact]-[revision].[ext]" />
</filesystem>
With the local repo wired up, you can now added dependencies to the ivy.xml file, like this: <dependencies>
<dependency org="org.eclipse" name="org.eclipse.ui" rev="3.4.+" />
<dependency org="org.eclipse" name="org.eclipse.swt" rev="3.4.+" />
<dependency org="org.eclipse" name="org.eclipse.swt.gtk.linux.x86" rev="3.4.+" />
<dependency org="org.eclipse" name="org.eclipse.jface" rev="3.4.+" />
<dependency org="org.eclipse" name="org.eclipse.core.commands" rev="3.4.+" />
<dependency org="com.ibm" name="com.ibm.icu" rev="3.8.+" />
<dependency org="org.apache" name="org.apache.log4j" rev="1.2.+" />
<dependency org="com.jcraft" name="com.jcraft.jsch" rev="0.1.+" />
<dependency org="org.slf4j" name="slf4j.api" rev="1.5.2" />
<dependency org="org.slf4j" name="slf4j.log4j12" rev="1.5.2" />
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.logging" rev="1.1.1" />
<dependency org="org.apache.log4j" name="com.springsource.org.apache.log4j" rev="1.2.15" />
<dependency org="org.aopalliance" name="com.springsource.org.aopalliance" rev="1.0.0" />
<dependency org="org.springframework" name="org.springframework.beans" rev="2.5.5.A" />
<dependency org="org.springframework" name="org.springframework.context" rev="2.5.5.A" />
<dependency org="org.springframework" name="org.springframework.context.support" rev="2.5.5.A" />
<dependency org="org.springframework" name="org.springframework.core" rev="2.5.5.A" />
<dependency org="org.springframework" name="org.springframework.aop" rev="2.5.5.A" />
<dependency org="org.springframework.osgi" name="org.springframework.osgi.core" rev="1.1.0" />
<dependency org="org.springframework.osgi" name="org.springframework.osgi.extender" rev="1.1.0" />
<dependency org="org.springframework.osgi" name="org.springframework.osgi.io" rev="1.1.0" />
</dependencies>Now, you're good to go. You can run ant lib-install-bundles and you have a Target Platform (acme-project/lib/bundles) that only contains the bundles your application is using. The thing to note is that Bushel is a work in progress. The Ivy meta-data still does not include the bundle dependencies of the bundle itself. So, transitive installation with Ivy is a little way off. |
Sign in to add a comment
Very Interesting staff, I am eager to see how it evolves!
Is there any way to have a per-project Target Platform? It seems painful to switch projects or have multiple projects open with different dependencies if there is only one target platform at a time.
If I understand your question correctly, I think this is a limitation of the current version of Eclipse. I've often wondered the same thing.