4. Library Versioning

One of the hardest part during the development of a library package is handling of version information, as it relates to the binary interface exposed by said libraries and is, thus, governed by those rules rather than what is a more human readable release version.

To understand what libtool support, in term of version handling, it's important to know what that version refers to. Libraries written in compiled languages – such as C, C++ and so on – have, in addition to the interface that the programmers need to know, an interface that the dynamic loader, and the consumers, know. The former is called API, the latter is called ABI.

The former includes, among others, the names of the functions, the meaning of the parameters, and the elements within structures. The latter adds to this the data types used for parameters and return values, and the order of the structures themselves. As you can imagine, it's easy to change the ABI without changing the API — a common way to do so is to change the type of a parameter from int to long: users of the library will not have to change their code, but the size expected in the function, between the two versions, will change.

The rule of thumb, when writing libraries, is basing the release version on the API, which comes down to changing the major version when the interfaces are changed in a completely incompatible way, changing the minor version when interfaces are added, or changed in mostly-compatible ways, and finally increasing the micro, or “patch level” version at each release. Obviously, all the version components to the right of the one that is increased should then be reset to zero.

For what concerns the version of the ABI, it should not, generally, be tied to the release version; exceptions are possible, but will be documented later on. To solve the issue of ABI compatibility, both ELF and Mach-O provide a simple version number attached to the shared libraries. In the case of ELF, the pair of library name and version is recorded into a data field called DT_SONAME tag, and is used for listing dependencies within the DT_NEEDED tags of the library's consumer.

Since the version attached to a library refers to its ABI, whenever the ABI changes, the version need to change, even if this happens within the same minor version, just with a new release. This is the reason why the two versions are not supposed to have the same value. Whenever the ABI changes in an incompatible way, the DT_SONAME (and its equivalent for non-ELF systems) need to change, to make sure that the library is not loaded by incompatible consumers.

For library version support on Microsoft Windows and other PE environments, please see Section 5.4, “Library Version Support”.

4.1. Setting the proper Shared Object Version

Developers working on Linux workstations will probably have noticed that most libraries built through libtool have three numbers at the end of their name, such as libfoo.so.0.0.0; this brings to an unfortunately incorrect implication that the version of the shared object uses the full three components. This is not the case. The version of the library is the one indicated in the DT_SONAME tag and is, generally, only one component, so in the aforementioned case, that would be libfoo.so.0.

To set the version of the library, libtool provides the -version-info parameter, which accepts three numbers, separated by colons, that are called respectively, current, revision and age. Both their name and their behaviour, nowadays, have to be considered fully arbitrary, as the explanation provided in the official documentation is confusing to say the least, and can be, in some cases, considered completely wrong.

Warning

A common mistake is to assume that the three values passed to -version-info map directly into the three numbers at the end of the library name. This is not the case, and indeed, current, revision and age are applied differently depending on the operating system that one is using.

For Linux, for instance, while the last two values map directly from the command-line, the first is calculated by subtracting age from current. On the other hand, on modern FreeBSD, only one component is used in the library version, which corresponds to current.

The rules of thumb, when dealing with these values are:

  • Always increase the revision value.

  • Increase the current value whenever an interface has been added, removed or changed.

  • Increase the age value only if the changes made to the ABI are backward compatible.

The main reason for which libtool uses this scheme for version information, is that it allows to have multiple version of a given library installed, with both link editor and dynamic linker choosing the latest available at build and run time. With modern distributions packaging standards, this situation should not be happening anyway.

Another common mistake is to match the value of -version-info with the package version or vice-versa. The two values are not designed to match: current increases with any interface change, compatible or not, and revision should be incremented with any package version.

If the package version were to match the -version-info options, you would obtain a sequence of 0.0.0, 0.0.1, 0.0.2… which would appear correct. It would keep correct if every release had backward-incompatible ABI changes (0.0.0, 1.0.0, 2.0.0, 3.0.0, …), but it becomes confusing when each release has backward-compatible ABI changes (0.0.0, 1.1.1, 2.2.2, 3.3.3, …), or when different type of changes happen: (0.0.0, 0.0.1, 1.0.2, 2.1.3, 2.2.4, …).

While this versioning scheme is monotonically increasing, its format is exceedingly complicated and is due to cause confusion in packagers and users, as a non-backward-compatible ABI change might still be fully source compatible, and thus a jump from version 1.0.2 to 2.1.3 would provide the wrong impression to all. It is suggested to refrain from idiosyncratic versioning for packages, in favour of more reliable and understandable semantic versioning.

Following semantic versioning for the package while trying to apply the same to the library would also lead to confusing and inconvenient behaviour as the parameters are mangled by libtool according to the system it is building for and are thus going to produce incorrect results in some cases.

4.2. Internal libraries' versions

As noted in the previous section, the rules on versions for shared libraries are complex, and bothersome to maintain, but they are fundamental for the compatibility of the consumers of the library. On the other hand, sometimes it's possible to get away without having to follow these rules.

For internal libraries, that are shared among executables of the same package, but are not supposed to have any other consumer, and as such do not install headers, it's possible to simply use the -release option to provide a different name for the library at each release.

4.3. Multiple libraries versions

While libtool was designed to handle the presence of multiple libraries implementing the same API (and even ABI) on the system, distributions made that necessity moot. On the other hand, it is not uncommon for multiple versions of a library to be installed, with multiple API implemented, allowing consumers to pick their supported version. This is the case, for instance, of Gtk+ and Glib.

The first reaction would be to combine the two options, -release and -version-info; this would, though, be wrong. When using -release the static archive, the one with .a extension, the libtool archive (see Section 6, “Libtool Archives”) and the .so file used by the link editor would not have a revision appended, which means that two different version of the library can't be installed at the same time.

In this situation, the best option is to append part of the library's version information to the library's name, which is exemplified by Glib's libglib-2.0.so.0 soname. To do so, the declaration in the Makefile.am has to be like this:

lib_LTLIBRARIES = libtest-1.0.la

libtest_1_0_la_LDFLAGS = -version-info 0:0:0