Multiple library versions
Working with vendor code in C can get very tricky, especially when you except breaking changes to occur. Especially when you have multiple binaries depending on that vendor code, updating at different times, necessitating different live versions. Let’s explore.
Introduction
Assume you’re working with an external vendor, who is providing you with code
for a wonderful function getFoo
:
|
|
You use this function in many of your products - for example, in your
best-selling barApp
application:
|
|
So barApp
, and other applications, would want to use a foo
library. It
makes sense to provide this function in a shared library (libfoo.so
).
However, this library will change in the future, in several ways:
- Binary-compatible changes
- Performance improvements (
sleep
will be removed) - Additional functionality will become available (new functions)
- Performance improvements (
- Binary-incompatibile changes - at the very least, recompilation will be necessary
- For C, this is usually caused by changes to macros
- For C++, a plethora of reasons: Virtual function reimplementation, function inlining, new private data members…
- Source-incompatible changes - these will require you to change your source
code (in
barApp
):- Functions (which you use) being removed or renamed
- Semantic changes -
getFoo
could return 43
This gets even more complicated due to the fact that barApp
is an operational,
mission-critical application for your organization. Developers may need to
hotfix older versions of barApp
, which use older versions of libfoo
. The build
servers and developer boxes will need to be able to have multiple versions of
libfoo
installed simultaneously.
Compiling, installing, and using a shared library properly
First, the upstream vendor should compile libfoo.so
with an SONAME
, like so:
|
|
The guarantee the upstream vendor should give is this: As long as SONAME
doesn’t change, binary compatibility will be retained.
Now, you (or, preferably, your package manager) should install the package on your machine like so:
|
|
Now, traditionally another symlink libfoo.so
-> libfoo.so.1.2.3
would be
created, so you could compile barApp
with -lfoo
. However, here’s an
alternative:
|
|
Now barApp
is compiled, and looks for libfoo.so.1
- it will find it thanks
to the symlink created by ldconfig
, and use libfoo.so.1.2.3
.
Aftermath
Binary-compatible updates
Suppose a new, compatible, faster version of libfoo
is released - say version
\1.3.0, which has removed that pesky sleep
. Well, just place it in /usr/lib
and rerun ldconfig
.
|
|
The symlink has been updated, and now all applications (barApp
, for example)
which were linked against libfoo.so.1
will have improved performance.
Incompatible updates
Suppose a new, incompatible version 2.0.0 of libfoo
is released, which would
force the newer barApp2.0
to be recompiled against the new, different
headers. No problem:
|
|
Both versions of libfoo
are installed simultaneously, and do not conflict.
Final thoughts
The Debian policy guide states that -dev
packages should include
the libfoo.so
symlink. However, this would cause a conflict between the
-dev
packages for two different generations of libfoo
. I am curious as to
how this problem is solved “in the wild”, as I’m sure Debian have good reasons
for suggesting this.