In my previous post, I discussed various strategies for managing third party libraries. In this post I’ll discuss a couple of techniques you can use to ensure that a specific version of your source code will get compiled with the correct version of the required libraries.
Yes, you can rely on your package management tools to always deliver you the correct versions. If you’re a little more paranoid and/or spent way too much time debugging problems stemming from mixing the wrong libraries, you may want to continue reading.
Suggestion #1 – use C++ compile time assertions to check library versions
This suggestion only works if your libraries have version numbers that are easily accessible as compile time constants. You can use something like BOOST_STATIC_ASSERT or C++ 11’s static_asset to do a compile time check of the version number against your expected version number. If the test fails, it’ll break the compilation so you get an immediate hint that there might be a problem.
The code for this could look something like this example:
First, in the header file the version number constant is defined:
... const int libgbrmpzyyxx_version = 0x123; ...
The header file or source file pulling in all the version headers then checks that it’s pulled in the correct version:
#include "boost/static_assert.hpp" #include "lib_to_check" BOOST_STATIC_ASSERT(libgbrmpzyyxx_version == 0x123);
If you are so inclined and are using C++ 11, you can replace the BOOST_STATIC_ASSERT with the standard
My suggested approach would be to have a single header file in each module of your project that pulls in all relevant #include files from all required libraries. This file should also contain all the checks necessary to determine if the libraries that got pulled in have the correct version numbers. This way, having a compilation error in a single, well-named file (call it ‘libchecks.H’, for example) should immediately suggest to you that a library needs updating. If you keep the naming schema consistent, a quick glance at the error message should provoke the right sort of “don’t make me think – I know what the problem is already” type response.
Suggestion #2 – use link failures to indicate library versioning problems
This is a variation of suggestion #1, only that instead of using a compile time check along the lines of BOOST_ASSERT, your library contains a version specific symbols which your code references. Obviously if the code is referencing a symbol that doesn’t exist in the library, the linker will fail and you’ll get a message with is relatively easy to parse for a human and still pinpoints the problem. The advantage of this method is that it does work across languages – you can use it in plain C code when linking against a library that is implemented in C++, for example, or in C extension modules built for dynamic languages. Its main downside is that in case of a version mismatch, the build fails a lot later in the process and gives you the same information that you may have received using suggestion #2, only three cups of coffee later. That said, if your project builds fast enough the difference in elapsed time between suggestions #2 & #3 might be negligible. On the other
hand if your build takes hours or days to complete, you really should try to make suggestion #2 work for you.
This suggestion relies on the fact that somewhere in the library code, a constant is defined that is externally visible, ie
... const int libgbrmpzyyxx_1_23_version = 0; ...
And somewhere in your code, you try to access the above constant simply by referencing it:
int test_lib_version = libgbrmpzyyxx_1_23_version;
Suggestion #3 – use runtime checks
Sometimes, the only way to work out if you are using the right version of a library is a runtime check. This is unfortunate especially if you have long build times but if your library returns, say, a version string this would be the earliest you can check that your project is linked with or loaded the correct version. If you are working a lot with shared libraries that are loaded dynamically at runtime , this is a worthwhile check anyway to ensure that both your build and runtime environments are consistent. If anything I would consider this an additional check to complement the ones described in suggestions #1 & #2. It also has the advantage that you can leave the check in the code you ship and thus detect a potential misconfiguration at the client end a lot easier.
I personally prefer suggestion #1 as I want to ensure the build fails as early as possible. Suggestion #2 works especially when you can’t use boost for whatever reason and don’t have a C++11 compiler, but otherwise I personally would not use it. Suggestion #3 is something you use when you need it, but if you do at least try to cover the relevant cases in your unit tests so your QA team doesn’t have to try and find out manually if you are using the correct library version for every component.