In this article I’m going to talk about building a C++ library with CMake, but it won’t be a CMake tutorial.
Let’s say you have a C++ library which depends upon a few open source libraries, which have a CMake project structure, but not necessarily done by the book (which means that they get only get built, and not deployed / installed)
Your library will include tests (unit-tests / integration tests), and the deployment can be just packing the headers and the binaries together in a tar.gz file.
This is not necessarily by the book, but it will do the job, and it could fit into any build system that the client has.
A book that one can use to do CMake right is Profesional CMake. Awesome CMake also has a great list of resources regarding CMake.
Coming back to the C++ library, which decisions do we take to build it? Shared library, static library, both?
Shared library
The most common decision is to build as a shared library (BUILD_SHARED_LIBS
set to TRUE
in the CMake script).
The open source dependencies could be also shared libraries, or static libraries. If they are shared libraries you need to take care of deployment. Sometimes you might be forced to compile them as shared libraries, due to licensing for example.
It’s all good, until you have to deal with operating systems like QNX, which has a problem with shared libraries that have lots of symbols
.
The problem is that it takes longer to load them.
The default GCC and Clang compilers will compile all symbols (functions, classes, global variables) with default visibility. The Visual C++ compiler does the opposite, it hides all the symbols.
You might be familiar with macros like MY_LIB_API
which might look like this:
And then in your CMake script code you have:
This will ensure that your shared library will contain only the MY_LIB_API
symbols. This also means that you won’t have any problems with visible
symbols from any open source libraries that you linked statically. Hopefully you can control how that open source libraries decide how to export their symbols.
The generated shared object will also be smaller in size. It depends upon the number of symbols though.
CMake has the GenerateExportHeader which can help with this matter.
But now you will notice that your tests will fail to build, since the symbols they require are not there anymore. So what now?
Shared and static library
We need to have a shared library with only the MY_LIB_API
symbols exported, but also have tests working.
The problem with visibility flags is that it will affect the compiler command line, CMAKE_CXX_VISIBILITY_PRESET
,
and CMAKE_VISIBILITY_INLINES_HIDDEN
will result in having -fvisibility=hidden
and -fvisibility-inlines-hidden
added to the compiler command line.
So we compile a shared library with all symbols, and one with only the MY_LIB_API
symbols. But this means compiling
twice, which is a bit wasteful.
We could compile a static library with hidden symbols, then create a shared library based on this static library, and link the tests to the static library. The tests will link because the symbols are there in the static library, marked hidden, but still accessible to the linker.
You will have to take care of the POSITION_INDEPENDENT_CODE CMake property, which is not set for static libraries.
This solves it. Everything works. But what if you want to make the QNX case even faster? (by removing the shared library all together!)
Static library
We could just build only the static library, with hidden visibility and ship that. But this also means everything (including client code) needs to be compiled with the same compiler / toolchain.
The problem lies with the open source library dependencies. They also need to be shipped along side with your library, and then the client code needs to link them too.
If you export your CMake targets, you can have the dependencies “linked” to your target, and the client code will only have to specify one target. But this requires proper CMake exports!
Bundled static library
What if you could bundle the open source dependencies in the static library?
Stackoverflow has this article: Using cmake to build a static library of static libraries, which boils down to:
You need to run a script which does this, but wouldn’t it be nice if we had a CMake function which enumerates the dependencies and bundles them into one library?
Here it is:
The usage of this function is as simple as:
Another benefit of a static library is that you could provide a build with Interprocedural Optimization / Link Time Optimization (IPO/LTO) enabled, and then the client code will generate smaller, faster binaries.
CMake has support for IPO/LTO, see CheckIPOSupported, and CMP0069.