Code / XBnd aQute - Software Consultancy
Search
*

Bnd Experimental

This section contains descriptions of functions in bnd that are experimental. Feedback is appreciated about this functions.

Version Policy

One of the nastiest chores in software is versioning. And that is for environments that are notoriously bad in versioning, like Java. OSGi has taken versioning very serious and versions are used pervasively. However, that does not make them less of a chore, on the contrary, in OSGi it is hard to ignore.

To survive versioning, one must have a version policy. A version policy puts semantics on the version numbers. The recommended policy in OSGi is:

  major        Breaking changes
  minor        Backward compatible
  micro        Bug fix (no API change)
  qualifier    Usually a build identifier

In OSGi, the decision was taken to have a single export version. The import statement allows a version range to be set. For example:

  Export-Package: com.acme.foo; version=1.0.2
  Import-Package: com.acme.bar; version="[1,2)"

Calculating these ranges by hand is a non-trivial task and extremely error prone. For this reason, bnd has a number of features you can use. First, you can calculate the import range by setting a macro on the Import-Package clause. This can be done with a wildcard on the package name:

  Import-Package: *; version=${@}

When bnd calculates the import clause, it will expand the ${@} in an already set version to the version of the package that is exported. This feature can be expanded by using the ${version} macro. For example:

  Import-Package: *; "[${version;==;${@}},${version;=+;${@}})"

This might look a bit daunting, but it is actually not that hard. It calculates a range starting with a '['. This indicates inclusive. It then calls the ${version} macro, which takes a pattern and a version. the pattern is one of:

  =     Keep the version part identical
  +     Increment the part with 1
  -     Decrement the part with 1
  [0-9] Use value

The ${@} is the place holder for the version. It is defined when the version policy is expanded. The given version policy [==,=+) indicates that the exported version is the baseline but that changes up to the next minor version number are allowed. For example:

   Export         Import  
   1.2.3.a        [1.2,1.3)
   5              [5,5.1)

Instead of setting the version policy on the Import-Package clause, it is also possible to set it globally with the -versionpolicy clause. When the import clause is calculated, and no version has been set, it can calculate the import range based on the version of the exported package (if set) with the version policy. The policy is set with for example:

  -versionpolicy: "[${version;=0;${@}},${version;+;${@}})"

This seems to make sense, until you look closer. What does backward compatible really mean? In Java, backward compatibility depends on the way a class is used. A normal class has quite a large number of possible changes while remaining binary compatible with its users, including its subclasses. For example, adding a field or method does no harm. However, when an interface is implemented by a class, backward compatibility is much more stringent. Any added method to an interface will break the binary compatibility of an implementer. For this reason, imports to packages that contain interfaces that are implemented usually use a more restricted version policy than for packages that are only used. For example, different policies for implements and uses could be:

  implements        [==,=+)   e.g. 1.2.3.q = [1.2,1.3)
  uses              [=,+)     e.g. 1.2.3.q = [1,2)

However, taking into account if a package is implemented or not for the version policy is not trivial for humans. Therefore bnd can use different policies for implemented imports as well as imports that are only used:

  -versionpolicy-impl  : "[${version;==;${@}},${version;=+;${@}})"
  -versionpolicy-uses  : "[${version;=;${@}},${version;+;${@}})"

During the calculation of the imports, bnd tracks any packages that are implemented. Where implemented means that there is at least one class in the current jar that implements an interface from that package.

When the time comes calculate the imports, it will check if an imported package is using the -versionpolicy-impl or the -versionpolicy-impl. The default for these macros is the -versionpolicy header.

In many cases, a package consists of several interfaces. However, not all interfaces are created equal. Some interfaces are highly likely to be changed over the life time of a package. Other interfaces are a fundamental part of the package. For example, in the OSGi Event Admin service, there is an EventAdmin interface. This interface is an integral part of the package. It is likely that if the package is changed, this interface is modified. However, the same package contains the EventHandler interface. This interface is used by clients of the Event Admin to receive the events. It is highly unlikely that this interface changes in a minor release. However, a client does implement that interface. For this reason, bnd supports the UsePolicy annotation. If this annotation is applied to an interface, then implementing that interface is treated by bnd as a use policy, not an implementation policy. For example:

   @UsePolicy
   public interface EventHandler {
     ...
   }

In this example, implementing the EventHandler interface does not make bnd use the -versionpolicy-uses (as long as there is no other class in the output jar that implements another interface in this package).

Export Annotation

Since Java 5, the Java compiler support package-info.java. This semi-class was introduced to add annotations to packages. The Export annotation provides a facility to set the Export attributes and directives directly in the code. For example:

   package-info.java:
   @Export( version="1.2.3.${qualifier}", exclude=Private.class )
   package com.acme.foo;

   import aQute.bnd.annotation.*;

Annotations can be overridden by manifest headers. This annotation provides the following fields:

versionStringThe version is the standard version of the exported package. The version may refer to macros. For example:

@Export(version="1.2.4.${buildstamp}")
mandatoryString[]A list of mandatory attributes. The atributes must have the form <key>=<value>. The key must not be quoted and no spaces. The key and value must follow the rules for OSGi headers. All key names of the mandatory attributes are placed in the mandatory directive. The value part of an attribute can use macros. For example:

@Export( mandatory="impl=true")
optionalString[]A list of optional attributes. For the format see mandatory. Optional attribute's keys are not placed in the mandatory directive.

@Export( optional={"vendor=IBM","description=${descr}"})
excludeClass<?>[]List of classes that must be excluded from the export. This is translated to the exclude: directive.

@Export(exclude=PrivateImpl.class)
includeClass<?>[]List of classes that must be included in the export. This is translated tot the include: directive.

@Export(include={A.class, B.class, C.class})
usesString[]Use classes that will be added to the calculated use clauses.
Copyright 2006 aQute SARL, All Rights Reserved