3. Bundle Dependencies
3.7. Version Numbers and Ranges
3.7. Version Numbers and Ranges
Versioning is a critically important part of OSGi. Modules, or libraries, are not immortal and unchanging entities — they evolve as their requirements change and as new features are added.
To take the example of Apache Log4J again, the most widely used version is 1.2. Earlier versions than this are obsolete, and there were also significant changes in the API between 1.1 and 1.2, so any code that has been written to use version 1.2 will almost certainly not work on 1.1 or earlier. There is also a 1.3 version, but it introduced a number of incompatibilities with 1.2 and is now discontinued — it is considered a failed experiment, and never produced a “stable” release. Finally there is a new version 2.0, but it is still experimental and not widely used.
This illustrates that we cannot simply use a bundle without caring about which version we wish to use. Therefore OSGi provides features which allow us first to describe in a consistent way the versions of all our bundles and their exports, and second to allow bundles to describe the range of versions that are acceptable for each of their dependencies.
3.7.1. Version Numbers
OSGi follows a consistent scheme for all version numbers. It uses three numeric segments plus one alphanumeric segment, where any segment may be omitted. For example the following are all valid versions in OSGi:
• 1 • 1.2
• 1.2.3
• 1.2.3.beta_3
The three numeric segments are known as the major,minor andmicro num- bers and the final alphanumeric segment is known as thequalifier. When any one of the numeric segments is missing, it takes the implicit value of zero, so 1is equivalent to1.0and1.0.0. When a version string is not supplied at all, the version0.0.0is implied.
Versions have a total ordering, using a simple algorithm which descends from the major version to the qualifier. The first segment with a difference in value between two versions “short-circuits” the comparison, so later segments need not be compared. For example 2.0.0 is considered higher than1.999.999, and this is decided without even looking at the minor or micro levels.
Things get interesting when we consider the qualifier, which may contain let- ters A-Z in upper or lower case, numbers, hyphens (-) and underscores (_). The qualifier is compared lexicographically using the algorithm found in the compareTo()method of the standard Java String class. There are two points to beware of: qualifier strings are compared character by character until a dif- ference is found, and shorter strings are considered lower in value than longer strings.
Suppose you are getting ready to release version 2.0 of a library. This is a significant new version and you want to get it right, so you go through a series of “alpha” and “beta” releases. The alphas are numbered as follows:
• 2.0.0.alpha1
• 2.0.0.alpha2
• . . .
This works fine up until the ninth alpha, but then when you release version 2.0.0.alpha10you find that it doesn’t appear to be the highest version! This is because the number ten starts with the digit 1, which comes before the digit 2. So versionalpha10will actually come between versionsalpha1andalpha2. Therefore, if you need to use a number component inside the qualifier, always be sure to include some leading zeros. Assuming that 99 alpha releases are enough, we should have started withalpha01,alpha02and so on.
Finally after lots of testing, you are ready to unleash the final release version, which you call simply2.0.0. Sadly this doesn’t work either, as it comes before
allof the alpha and beta releases. The qualifier is now the empty string, which comes before all non-empty strings. So we need to add a qualifier such asfinal to the final release version.
Another approach that can work is to always add a date-based qualifier, which can be generated as part of the build process. The date would need to be written “backwards” — i.e. year number first, then month, then day and perhaps time — to ensure the lexicographic ordering matches the temporal order. For example:
• 2.0.0.2008-04-28_1230
This approach scales well, so it is especially useful for projects that release frequently. For example, Eclipse follows a very similar scheme to this. However this approach can be inconvenient because the version strings are so verbose, and it’s difficult to tell which versions are important releases versus mere development snapshots.
3.7 Version Numbers and Ranges 63
3.7.2. Versioning Bundles
A version number can be given to a bundle by supplying theBundle-Version manifest header:
1 B u n d l e−V e r s i o n: 1 . 2 . 3 .a l p h a
Bundle-level versioning is important because of Require-Bundle, and also because OSGi allows multiple versions of the same bundle to be present in the framework simultaneously. A bundle can be uniquely identified by the combination of itsBundle-SymbolicNameand itsBundle-Version.
3.7.3. Versioning Packages
However, bundle-level versioning is not enough. The recommended way to describe dependencies is with Import-Package, and a single bundle could contain implementations of multiple different APIs. Therefore we need version information at the package level as well. OSGi allows us to do this by tagging each exported package with a version attribute, as follows:
E x p o r t−P a c k a g e: o r g.o s g i.b o o k.r e a d e r.a p i;v e r s i o n= " 1 . 2 . 3 .a l p h a" , o r g.o s g i.b o o k.r e a d e r.u t i l;v e r s i o n= " 1 . 2 . 3 .a l p h a"
Unfortunately in the manifest file, the version attributes must be added to each exported package individually. However bnd gives us a shortcut: # b n d s a m p l e
E x p o r t−P a c k a g e: o r g.o s g i.b o o k.r e a d e r∗ ;v e r s i o n= " 1 . 2 . 3 .a l p h a"
We can also, if we wish to, keep the bundle version and the package export versions synchronized: # b n d s a m p l e v e r: 1 . 2 . 3 .a l p h a B u n d l e−V e r s i o n: ${v e r} E x p o r t−P a c k a g e: o r g.o s g i.b o o k.r e a d e r∗ ;v e r s i o n=${v e r}
3.7.4. Version Ranges
When we import a package or require a bundle, it would be too restrictive to only target a single specific version. Instead we need to specify a range. Recall the discussion of Apache Log4J and its various versions. Suppose we wish to depend on the stable release, version 1.2. However, “1.2” is not just a single version, it is in fact a range from 1.2.0 through to 1.2.15, the latest at the time of writing, meaning there have been fifteen “point” releases since the main 1.2 release. However, as in most projects, Log4J tries to avoid changes in the API between point releases, instead confining itself to bug fixes and
perhaps minor API tweaks that will not break backwards compatibility for clients. Therefore it is likely that our code will work with any of the 1.2 point releases, so we describe our dependency on 1.2 using a versionrange.
A range is expressed as a floor and a ceiling, enclosed on each side either by a bracket “[” or a parenthesis “(”. A bracket indicates that the range is inclusive of the floor or ceiling value, whereas a parenthesis indicates it is exclusive. For example:
• [1.0.0,2.0.0)
This range includes the value 1.0.0, because of the opening bracket. but excludes the value 2.0.0 because of the closing parenthesis. Informally we could write it as “1.x”.
If we write a single version number where a range is expected, the framework still interprets this as a range but with a ceiling of infinity. In other words 1.0.0would be interpreted as the range[1.0.0,∞). To specify a single exact version, we have to write it twice, as follows: [1.2.3,1.2.3].
For reference, here are some further examples of ranges, and what they mean when compared to an arbitrary versionx:
[1.2.3,4.5.6) 1.2.3 ≤x < 4.5.6 [1.2.3,4.5.6] 1.2.3 ≤x≤4.5.6 (1.2.3,4.5.6) 1.2.3 < x <4.5.6 (1.2.3,4.5.6] 1.2.3 < x≤4.5.6 1.2.3 1.2.3 ≤x 0.0.0 ≤x
In our Log4J example and in real world usage, the first of these styles is most useful. By specifying the range [1.2,1.3) we can match any of the 1.2.x point releases. However, this may be a leap of faith: such a range would match all future versions in the 1.2 series, e.g. version1.2.999(if it were ever written) and beyond. We cannot test against all future versions of a library, so we must simply trust the developers of the library not to introduce breaking changes into a future point release. If we don’t or can’t trust those developers, then we must specify a range which includes only the known versions, such as [1.2,1.2.15].
In general it is probably best to go with the open-ended version range in most cases. The cost in terms of lost flexibility with the more conservative closed range outweighs the risk of breaking changes in future versions.