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