A few months ago I started writing about ABI (Application Binary Interface) and the problems related to maintaining that. In particular I’ve written about shared object versioning and the need of updating that when you change the interface of functions.
In all this topic, though, I have ignored one big issue with ABI and the versioning: optional interfaces. While a lot of software, included libraries, have optional features, not all of them have optional interfaces. The difference here is the key, but let me proceed with order.
When a library has optional features but not optional interfaces, it means that both the programming and binary interfaces are kept constant, but the way the library works depend on the features enabled at build time. For instance, a library like FFmpeg’s libavcodec will be able to transcode some given codec type only if the support is not disabled at build time, but the function from the program to the library to request the encoding is the same nonetheless; if the library cannot handle the encoding, it will tell that back to the program and it’s up to that to handle the thing properly.
Instead, when a library has optional interfaces, the functions called from program to library are dropped at build time entirely if they are not compiled in; this is what libxml does, as well as what the ALSA library does with midi (which I guess is what created the FUD about midi support in ALSA that made our terrific ALSA team from dropping all the work I’ve done for making that optional in the first place).
While using optional interfaces can reduce the size of a library, there are advantages and disadvantages with this approach:
- you get an error at buildtime for the program if it’s trying to use features that are not available — assuming you’re using the “no undefined” linking method;
- if you disable a previously enabled feature and launch a binary that use that, it either not start up, or will die as soon as it tries to call a function that’s gone.
The latter is more insidious than you might expect: for instance if you build the software on a machine where the library has the interfaces and then move it on another, it might not warn you that it has missing dependencies.
Taking this into consideration, I really really suggest not to use optional interfaces in libraries at all, and rather, if you do want optional interfaces, break the library in multiple sub-libraries. This is what libxml really should be doing for instance (Mart has been saying that for a long time — although we disagree how the compatibility issue should be tackled, he’d like to use a standard ELF library that would break with
--as-needed, while I’m more inclined on using a ldscript that redirects to the actual libraries), so that each program can request the actual interface that it’s going to need. This slightly increases the overhead because you have no longer the local symbols for the shared functions, but you instead use a single “core” private-interface library (think of libpulsecore) that is 1:1 versioned (with the release in the filename, and soversion 0). But this is usually negligible.
So what’s the next step? Well I guess soon enough I’ll illustrate how to actually do the compatibility trick with the ldscript, but that’s for another day.