Semantic Versioning in OSGi

At the Bndtools Hackathon this week we have been working hard on adding support for baselining. Baselining allows us to semantically compare the bundles generated from the current codebase to the latest release in a repository and report any discrepancies. This is an important step in always making sure you’re correctly versioning your bundles and exported packages.

Versioning

Versions in OSGi are composed of (a maximum of) four parts: major.minor.micro.qualifier: The major, minor and micro parts are numeric, the qualifier is alphanumeric. You can omit parts of the version, in which case the numeric parts default to 0 (zer0) and the alphanumeric part to “” (empty string). Examples of legal versions are:

  • 1.0.0
  • 1.0 (which is equal to 1.0.0)
  • 1.1.0.TEST
  • 2 (which is equal to 2.0.0)

Versions can be compared with each other. For example:

  • 1.0 < 2.0
  • 1.0 < 1.1
  • 1.9 < 1.10
  • 1.0.0 < 1.0.0.A (note that qualifiers are compared alphanumerically, and an empty string is always less than a non-empty one)

Semantic Versioning

Let’s first look at semantic versioning. If you’re familiar with OSGi, you might already know about it as there is a whitepaper on it that appeared several years ago. More recently, a more generic effort was published at semver.org, which basically aims to do the same, but outside of OSGi. Going back to OSGi, there are two things that need to be versioned:

  1. Exported packages. These have a version. Whenever the contents of the package changes, this version should be bumped. There are several rules that must be taken into account:
    • If you make a backward incompatible change, you must bump the major version. A backward incompatible change is, for example, the removal of a method from an interface.
    • If you make a backward compatible change, you must bump the minor version. A backward compatible change, for example, is the addition of a method to an interface, as long as that interface is a “provided” interface. A provided interface is an interface that is “provided”: the consumer only invokes methods on it. The second type of interface we recognize, is the “consumed” interface, which is an interface that you actually implement as a consumer. Adding a method to a “consumed” interface would be a backward incompatible change: in Java you must always implement all methods of an interface, so adding a new method would break all classes implementing the interface.
    • If you do a bugfix, you must bump the micro version or the qualifier.
  2. Bundles. These have a Bundle-Version manifest header. The combination of bundle version and symbolic name is the unique identification of a bundle, which means that whenever the contents of the bundle changes, the version number should be bumped:
    • If you make a backward incompatible change, you must bump the major version. In the case of a bundle, a backward incompatible change usually means that you have an exported package in there that has an incompatible change.
    • If you make a backward compatible change, you must bump the minor version. Again, this usually means there is an exported package in there with a compatible change.
    • If you do a bugfix, you must bump the micro version or the qualifier.

Now, whilst all of this sounds very logical, on a big project, with hundreds of bundles and exported packages, it becomes quite hard to manage. Versions should only be bumped when something changes, and this can be quite hard to track. That’s where baselining comes in. It allows us to compare the bundles generated based on the current code in the workspace with the latest released bundles and check if we made any changes to the code without bumping the proper part of the version. In Eclipse (and in the continuous build) it will immediately signal what is wrong, and we’re even working on quickfixes to correct those mistakes easily. All of this means that it is now very easy to correctly semantically version everything in your (large) project, so there is no more excuse not to do so. This is a big step forward!

At the moment, these features are only available in the development version of Bndtools, but they will make it into the next release. If you want to try them out, build the latest version yourself and then in the global settings add the following line:

-baseline: *

Then start releasing bundles, make changes and see how Bndtools responds! As with any new feature, let the Bndtools team know what you think of it, and we hope you’ll enjoy it as much as we do.

Posted in Article Tagged with: , ,