From Cmake Syntax To Libstdc++ Abi Incompatibiliy Migrations Are Always Hard
This is the short story of the 0.7.X conan releases, in which we had to launch several minor versions to fix unexpected problems related to CMake and the libstdc++ ABI incompatibility, though the lessons learnt might be useful for any C and C++ developer, so let’s try to summarize them in this post.
From the beginning, conan has tried to be as compatible as possible with not bleeding-edge systems, because we know that in many corporate environments there are some restrictions and tend to attach to old distributions and tools. So conan started for its cmake generator (it support others as Visual or XCode) with the typical CMake 2.8 syntax for conditionals:
In old cmake, undefined variables were evaluated to empty strings, so the above code worked fine. But as soon as our users started to use more modern versions of CMake, the CMake#CMP0054 policy for CMake>3.1 states that the variables inside strings will not be dereferenced, which affect most of the above string, even the MSVC one that is a cmake variable.
The problem is that if using the new approach, cmake commands fail when such variable is not defined, due to an incorrect number of parameters, so existence of the variables must be checked in advance:
Here, we did another mistake, can you spot it in the above code? Yes, the
return() from CMake macros, behaves different from the
return() inside a function, it returns from the caller, considering that macros are not called but rather an inline expansion. The solution was obviously to use functions instead of macros.
Also, the C projects (which can be defined in the CMake project declaration as
project(MyProject C) ), were another corner case that we didn’t consider at first, which made the above code to break, as CMAKE_CXX_COMPILER_ID was not defined either, and instead
CMAKE_C_COMPILER_ID should be checked. Also, some checks had to be introduced for
CMAKE_CXX_FLAGS_RELEASE and other similar variables.
But why all these compiler checks?
Some reader might wondered why we need all these compiler checks, as this is something the user typically manages, as when specifying a generator in cmake:
Or by defining CC, CXX environment variables, or just by installing a certain version of a the gcc compiler in their system.
The problem is the ABI incompatibility between different compiler versions, which mean that a library compiled with Visual Studio 14, might not be linkable from Visual Studio 12, or one compiled with gcc 4.8, might not be linkable from gcc 5.2. So, in conan, the user specifies their setup (OS, compiler, compiler version, architecture, build type…), in order to retrieve if possible compatible pre-built binaries for their dependencies. Also package creators specify their setup and build and upload their packages in a similar way.
But what happens if a user specify usage of a certain compiler version, but actually the installed and used one is different? For package consumers that would typically produce linking errors, but for package creators that would mean that they could affect many people with incompatible builds.
So introducing a check in the build system that compares the settings used to install dependencies against the actual build settings seems a sane thing.
And the checks proved useful
These kind of checks are typically useless, like assertions in code, until they raise. We realized that in some contexts, specifically when users were building with their own tools (as opposed to using the conan build command), the compiler being used by conan was not defined and nothing was being checked.
This soon became notorious in CI environments like travis-ci, that uses quite old Ubuntu 12.04 machines, with default compiler gcc 4.6. It is typical that travis users install more modern compilers as part of the setup procedure, but depending on it, it was possible that conan only auto-detected the system gcc 4.6, instead of the recently installed one.
So we found projects building executables with gcc 5.2 linking against Boost libraries compiled with gcc 4.6. And it worked and run properly! But we knew this was probably not the user’s intention, and that actually Boost packages built with gcc 5.2 were most likely preferred. So the compiler checks easily allowed to spot this compiler version incompatibility and to fix the setup to properly point to the correct version.
But then… surprise! This was a typical output, when linking against Boost built with gcc 5.2.:
WHAT!!?? We were able to link and run using gcc 5.2 against Boost with gcc 4.6, but we get linking errors against Boost built with gcc 5.2?
Libstdc++ ABI incompatibility
Yes, we already knew it, that from gcc 5.1 there were two different libstdc++ implementations, supposedly being the default one the most modern one, i.e. the C++11 one (let’s call it
libstdc++11). Is this true? The answer: not always, it depends on your machine and OS.
It turns out that to build Boost in travis-ci we are using conan-package-tools, which use docker to manage different compiler versions easily. Such docker images are based on modern Ubuntu distros like Xenial Xerus (15.04), with a default gcc 5.2 compiler and libstdc++11 by default. So those packages were using the modern libstdc++ ABI.
But most travis-ci users will not use docker to build their projects. They will just upgrade their compiler to gcc 5.2. But travis-ci machines are old Ubuntu 12.04 distributions, so upgrading them to gcc 5.2, do not upgrade by default the libstdc++. In fact, such upgrade is very difficult, as many programs depend on libstdc++, so the system one cannot be upgraded without a major system upgrade. It could in theory be possible to download a separate copy of a modern libstdc++ and link against it, but seems a bit complex and not something that travis-ci users were doing.
Libstc++11 and its new ABI will be the default for gcc>5.1 in modern distributions, but not for old distros, even if upgrading the gcc compiler.
So what could be done? First of all, we have added a new setting to gcc and clang compiler families, that can take several values:
That setting is then managed as the other settings, so the user can write:
And it will aim for a binary package linked against the new ABI libstdc++.
In the case of cmake, it is translated (just an excerpt) to a variable that is checked to define the compiler flag
It can be seen that Visual Studio does not have this setting, but instead the runtime, which in essence solves the same problem. It could be argued that C projects do not link against libcxx, and that is true, so we decided that the most conceptually correct approach for C projects would be to remove that settings, so they do not depend on it, which can be done:
Migrations are hard. Augmenting cmake support from cmake 2.8 to more modern 3.4, and also dealing with modern gcc>5.1 incompatibilities has meant a lot of work done in latest 0.8 conan release, but we are confident that conan will keep improving for one main reason: all of the above has been detected and reported by active users, building their projects using conan, and we have received tons of help, feedback and contributions in order to solve these issues.
Special thanks go to @mcraveiro, @tyroxx, @Manu343726, @nathanaeljones, and of course to all contributors of conan! (tip, clone the repo and “git shortlog -sne” ;) )