Using a C++ library, particularly a 3rd party one, can be complicated affair. Library binaries compiled on Windows/OSX/Linux can not simply be copied over to another platform and used there. Linking works differently, compilers bundle different code into binaries on each platform etc.

This is not an insurmountable problem. Libraries like Qt distribute dynamically compiled binaries for major platforms and other libraries have comparable solutions.

There is a category of libraries which considers the portable binaries issue to be a terminal one. Boost is a widespread source of many ‘header only’ libraries, which don’t require a user to link to a particular platform-compatible library binary. There are also many other examples of such ‘header only’ libraries.

Recently there was a blog post describing an example library which can be built as a shared library, or as a static library, or used directly as a ‘header only’ library which doesn’t require the user to link against anything to use the library. The claim is that it is useful for libraries to provide users the option of using a library as a ‘header only’ library and adding preprocessor magic to make that possible.

However, there is yet a fourth option, and that is for the consumer to compile the source files of the library themselves. This has the

advantage that the .cpp file is not #include d into every compilation unit, but still avoids the platform-specific library binary.

I decided to write a CMake buildsystem which would achieve all of that for a library. I don’t have an opinion on whether good idea in general for libraries to do things like this, but if people want to do it, it should be easy as possible.

Additionally, of course, the CMake GenerateExportHeader module should be used, but I didn’t want to change the source from Vittorio so much.

The CMake code below compiles the library in several ways and installs it to a prefix which is suitable for packaging:

cmake_minimum_required(VERSION 3.3) project(example_lib) # define the library set(library_srcs example_lib/library/module0/module0.cpp example_lib/library/module1/module1.cpp ) add_library(library_static STATIC ${library_srcs}) add_library(library_shared SHARED ${library_srcs}) add_library(library_iface INTERFACE) target_compile_definitions(library_iface INTERFACE LIBRARY_HEADER_ONLY ) set(installed_srcs include/example_lib/library/module0/module0.cpp include/example_lib/library/module1/module1.cpp ) add_library(library_srcs INTERFACE) target_sources(library_srcs INTERFACE $<INSTALL_INTERFACE:${installed_srcs}> ) # install and export the library install(DIRECTORY example_lib/library DESTINATION include/example_lib ) install(FILES example_lib/library.hpp example_lib/api.hpp DESTINATION include/example_lib ) install(TARGETS library_static library_shared library_iface library_srcs EXPORT library_targets RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib INCLUDES DESTINATION include ) install(EXPORT library_targets NAMESPACE example_lib:: DESTINATION lib/cmake/example_lib ) install(FILES example_lib-config.cmake DESTINATION lib/cmake/example_lib )

This blog post is not a CMake introduction, so to see what all of those commands are about start with the cmake-buildsystem and cmake-packages documentation.

There are 4 add_library calls. The first two serve the purpose of building the library as a shared library and then as a static library.

The next two are INTERFACE libraries, a concept I introduced in CMake 3.0 when it looked like Boost might use CMake. The INTERFACE target can be used to specify header-only libraries because they specify usage requirements for consumers to use, such as include directories and compile definitions.

The library_iface library functions as described in the blog post from Vittorio, in that users of that library will be built with LIBRARY_HEADER_ONLY and will therefore #include the .cpp files.

The library_srcs library causes the consumer to compile the .cpp files separately.

A consumer of a library like this would then look like:

cmake_minimum_required(VERSION 3.3) project(example_user) find_package(example_lib REQUIRED) add_executable(myexe src/src0.cpp src/src1.cpp src/main.cpp ) ## uncomment only one of these! # target_link_libraries(myexe # example_lib::library_static) # target_link_libraries(myexe # example_lib::library_shared) # target_link_libraries(myexe # example_lib::library_iface) target_link_libraries(myexe example_lib::library_srcs)

So, it is up to the consumer how they consume the library, and they determine that by using target_link_libraries to specify which one they depend on.

Share this: Email

Like this: Like Loading... Related