Hello lucy-dev,

I spent a bit of time researching how shared library versioning works on different operating systems and how the stand-alone C library could use it. First, let's recap some basics about versioning. A version usually consists of

    * Major version. This is increased whenever the API or ABI changes
      in a way that is not backward-compatible. The dynamic linker
      should never load a library with a different major version than
      the one specified in the executable, no matter if it's a newer
      or older version.

    * Minor version. This is increased whenever the API changes in a
      backward-compatible way. Executables linked against an older
      version of a library should continue to work with newer versions
      of the library as long as the major version doesn't change.
      On the other side, executables linked against a newer library
      version shouldn't run with older library versions as they might
      use some of the newer features.

    * Patch level. This is increased whenever a new version of the
      library is released that doesn't change the API at all,
      typically for bug fixes. An executable should work with every
      library that has the same major or minor version, regardless
      whether the patch level is higher or lower.

So if we have version strings of the form "major.minor.patchlevel" and a library with version "1.3.4", the following should happen with executables linked against different versions:

    * Executable version "0.9.2": FAIL
    * Executable version "1.0.3": OK
    * Executable version "1.3.0": OK
    * Executable version "1.3.4": OK
    * Executable version "1.3.9": OK
    * Executable version "1.4.0": FAIL
    * Executable version "2.2.2": FAIL

Or, if the executable version is linked against "1.3.4":

    * Library version "0.9.2": FAIL
    * Library version "1.2.5": FAIL
    * Library version "1.3.0": OK
    * Library version "1.3.4": OK
    * Library version "1.3.9": OK
    * Library version "1.4.0": OK
    * Library version "2.2.2": FAIL

Now, let's see how different operating systems or, more specifically, different object file formats load libraries and support library versioning.

Portable Executable (PE) on Windows
-----------------------------------

Windows searches for DLLs by filename in a couple of directories at run time:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms682586(v=vs.85).aspx

PE has no support for versioning (to my knowledge). It's only possible to encode the major version in the filename of the DLL.

Mach-O on OS X
--------------

At run time, Mach-O libraries are loaded using the "install name" encoded in the executable. The install name is a full filesystem path. This path can start with a number of placeholders like @executable_path, @loader_path, or @rpath.

The major version can be encoded in the install name. For minor versions and patch levels, every library has a so called "compatibility version". When an executable is linked against a library the compatibility version of the library is encoded in the executable at compile time. If the dynamic linker later finds a library with an older version, it refuses to load it. With the "major.minor.patchlevel" scheme, a compatibility version of "minor.0" could be used.

For more information, see the OS X man pages for dyld, libtool, ld, otool, and install_name_tool.

ELF on many UNIX variants
-------------------------

The ELF dynamic linker searches libraries at run time by "soname" in a couple of directories. The soname is basically an arbitrary string with no relation to the library filename, but typically it's "name.so.major-version". The soname is encoded in libraries and executables at compile time.

For minor versions, some ELF implementations support "version scripts" in which versions can be specified for every symbol. This allows for very sophisticated version checks. On Linux, you can even specify multiple versions of a symbol in a single library. The details are explained in-depth in chapter 3 of Ulrich Drepper's DSO Howto:

    http://www.akkadia.org/drepper/dsohowto.pdf

Regarding Lucy, I think it's too tedious to maintain version scripts with all the symbols. An easier approach might be to have a version script that covers only a single symbol like lucy_bootstrap_parcel. This would in effect be similar to Mach-O's compatibility version.

Another solution would be to implement minor version checks explicitly in lucy_bootstrap_parcel:

    void lucy_bootstrap_parcel(int minor_version) {
        if (minor_version > x) {
            // throw
        }
        ...
    }

Or we could ignore minor version checks completely and leave it all to the user.

Nick

Reply via email to