My previous post about CMake provided a simple CMakeLists.txt for a small, self-contained, project. In practice, very few projects are fully self-contained, as they either depend on external libraries or are themselves libraries that other projects depend on. This post shows how to create and consume simple libraries using modern CMake.

Consuming libraries

Let's say we want to build a program using a SAT solver , specifically Minisat . To check that using the library works, we will use this main.cpp to build a binary.

// main.cpp #include <minisat/core/Solver.h> #include <iostream> int main() { Minisat::Solver solver; auto x = Minisat::mkLit(solver.newVar()); solver.addClause( x); solver.addClause(~x); if (solver.solve()) { std::cout << "SAT

"; } else { std::cout << "UNSAT

"; } }

It creates a CNF formula with 2 clauses, x and ~x. Obviously a variable cannot be set to both true and false at the same time, so the output should be "UNSAT".

So how does CMakeLists.txt for building this executable look like? To start with, we will assume that the Minisat library has proper CMake-based build and has been already built and installed in the system we are building on.

cmake_minimum_required(VERSION 3.5) project(foo-sat LANGUAGES CXX) add_executable(foo main.cpp) find_package(MiniSat 2.2 REQUIRED) target_link_libraries(foo MiniSat::libminisat)

And that is it.

find_package(MiniSat 2.2 REQUIRED) looks for MiniSat package, in version 2.2, in the local CMake package registry. It being REQUIRED means that if CMake cannot find it, it should abort the configuration step. If CMake finds the package, all exported MiniSat targets are imported -- this is where we get the MiniSat::libminisat library target.

Because MiniSat::libminisat properly exports its include paths and other compilation settings it needs, linking against it is enough to get proper compilation settings for the foo binary.

Building subproject dependencies

The above works well if the package is already installed on the system we are building on. But what if we expect that it isn't, and would rather not make the user build and install the library separately?

If the library accommodates this in its CMakeLists.txt , we can do almost the same thing, except use add_subdirectory instead of find_package :

cmake_minimum_required(VERSION 3.5) project(foo-sat LANGUAGES CXX) add_executable(foo main.cpp) add_subdirectory(lib/minisat) target_link_libraries(foo MiniSat::libminisat)

This assumes that our folder structure looks like this:

lib/ └── minisat/ └── <stuff> CMakeLists.txt main.cpp

Easy.

What is harder is making this transparent: in both cases the executable links against a target with the same name, MiniSat::libminisat , but the way that this target gets into scope is different. The only solution I know of for this problem is not very satisfying or elegant.

Using non-CMake libraries

Until now we assumed that the library we want to use has a high-quality CMake build. This opens up a question: what if the library is not built using CMake, or maybe it is built using CMake, but the maintainer did not take care to enable proper installation? As an example, Boost is a common library that is not built using CMake, so in theory, we cannot depend on there being targets provided for it. There are two ways around this:

Wuss out and hardcode platform-specific flags Use a Find*.cmake to provide the targets instead

If you go with 2) and the library you want to use is common enough, there is a good chance that it will work out of the box, because CMake comes with some Find*.cmake scripts preinstalled, e.g. it provides FindBoost.cmake or FindThreads.cmake for you out of the box. Alternatively, you can look for one online, or write your own .

Creating libraries

As we have seen, using libraries from CMake can be downright pleasant, as long as the library supports this usage properly. The question now becomes, how do we create such library? Let's go over writing CMakeLists.txt for the Minisat library we were using in the first part of this post .

The first step is to build the library and binaries themselves. Going by the previous post about CMake and skipping over the IDE related improvements, we will end up with something like this :

cmake_minimum_required(VERSION 3.5) project(MiniSat VERSION 2.2 LANGUAGES CXX) add_library(libminisat STATIC minisat/core/Solver.cc minisat/utils/Options.cc minisat/utils/System.cc minisat/simp/SimpSolver.cc ) target_compile_features(libminisat PUBLIC cxx_attributes cxx_defaulted_functions cxx_deleted_functions cxx_final ) target_include_directories(libminisat PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(libminisat PUBLIC __STDC_LIMIT_MACROS __STDC_FORMAT_MACROS) # Also build the two MiniSat executables add_executable(minisat minisat/core/Main.cc) target_link_libraries(minisat libminisat) add_executable(minisat-simp minisat/simp/Main.cc) target_link_libraries(minisat-simp libminisat)

target_compile_features was not mentioned in the previous post, but it allows us to set what C++ features are used by the target and CMake then tries to figure out what flags are needed by the compiler to enable them. In this case, our fork of Minisat uses some C++11 features ( final , = delete , = default and [[]] attributes), so we enable those.

Note that since CMake version 3.8, the use of coarse-grained features for target_compile_features is discouraged. The reason is that as new standards add more and more features, trying to detect their support piecemeal is harder and harder. Instead, cxx_std_XX compile feature should be used to set the required C++ standard version to XX . This means that if we targetted newer CMake versions, we would instead use target_compile_features(libminisat PUBLIC cxx_std_11) .

This CMakeLists.txt will build a static library and the two binaries that depend on it. However, if we build this project on Linux, the library will be named liblibminisat.a , because CMake knows that library files on Linux are prefixed with lib as a convention, and it tries to be helpful. However, we cannot name the target just minisat , because that is the name of a target for executable. Let's fix that by instead changing the OUTPUT_NAME property of our target to minisat , to make the output of libminisat target libminisat.a on Linux and minisat.lib on Windows:

set_target_properties(libminisat PROPERTIES OUTPUT_NAME "minisat" )

Now we have a functional CMakeLists.txt, but it does not know how to install the resulting binaries.

Installing targets

CMake supports installing build artifacts made as part of a target via the install command. We can have CMake install the resulting library and binaries with this snippet

install( TARGETS libminisat minisat minisat-simp LIBRARY DESTINATION /usr/local/lib ARCHIVE DESTINATION /usr/local/lib RUNTIME DESTINATION /usr/local/bin )

This means install outputs of libminisat , minisat , minisat-simp to appropriate locations ( LIBRARY is the destination for dynamic libraries, ARCHIVE is the destination for static libraries and RUNTIME is the destination for executables). This snippet has 3 problems

The installation paths are hardcoded and obviously make no sense on Windows Only the build artifacts are installed, without any integration with CMake, so the libraries cannot be used the way shown at start of this post. There are no headers to be used with the library

We can fix the first one by relying on utility package GNUInstallDirs to provide reasonable default paths for Linux (Windows does not have a default path):

include(GNUInstallDirs) install( TARGETS minisat minisat-simp LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} )

This will get the two binaries installed into a reasonable default paths, namely /usr/local/bin on Linux and `` (empty, meaning local) on Windows. The library target has been split off because it will need special treatment to fix the second problem of the original install command.

The second problem, integrating nicely with other CMake builds, takes a lot of boilerplate CMake:

set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/MiniSat) install( TARGETS libminisat EXPORT MiniSatTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) install(EXPORT MiniSatTargets FILE MiniSatTargets.cmake NAMESPACE MiniSat:: DESTINATION ${INSTALL_CONFIGDIR} ) install(DIRECTORY minisat/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/minisat FILES_MATCHING PATTERN "*.h*" )

The first install command marks the libminisat target for export under the name MiniSatTargets (and obviously also installs the library). The second install command then saves the libminisat target into file MiniSatTargets.cmake , in namespace MiniSat:: in a subfolder of the library folder and the third install command copies all headers from the minisat subdirectory to the proper destination.

This is enough to use the MiniSat::libminisat target from external projects, but not enough to have it imported by the find_package command for us. For this to happen, we need 2 more files, MiniSatConfig.cmake and MiniSatConfigVersion.cmake , to be used by find_package :

##################### # ConfigVersion file ## include(CMakePackageConfigHelpers) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/MiniSatConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) configure_package_config_file( ${CMAKE_CURRENT_LIST_DIR}/CMake/MiniSatConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/MiniSatConfig.cmake INSTALL_DESTINATION ${INSTALL_CONFIGDIR} ) ## Install all the helper files install( FILES ${CMAKE_CURRENT_BINARY_DIR}/MiniSatConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/MiniSatConfigVersion.cmake DESTINATION ${INSTALL_CONFIGDIR} )

write_basic_package_version_file is a helper function that makes creating proper *ConfigVersion files easy, the only part that is not self-explanatory is COMPATIBILITY argument. AnyNewerVersion means that the MiniSatConfigVersion.cmake accepts requests for MiniSat versions 2.2 and lesser (2.1, 2.0, ...).

configure_package_config_file is a package-specific replacement for configure_file , that provides package-oriented helpers. This takes a file template CMake/MiniSatConfig.cmake.in and creates from it MiniSatConfig.cmake , that can then be imported via find_package to provide the targets. Because MiniSat does not have any dependencies, the config template is trivial, as it only needs to include MiniSatTargets.cmake :

@PACKAGE_INIT@ include(${CMAKE_CURRENT_LIST_DIR}/MiniSatTargets.cmake)

There is only one more thing to do, before our CMakeLists for MiniSat properly packages the library target for reuse, setting up proper include paths. Right now, libminisat target uses ${CMAKE_CURRENT_SOURCE_DIR} for its include paths. This means that if the library was cloned to /mnt/c/ubuntu/minisat , built and installed, then a project linking against MiniSat::libminisat would look for its includes in /mnt/c/ubuntu/minisat , rather than in, e.g. /usr/local/include . We cannot change the include paths blindly to the installed location either, as that would prevent the build from working. What we need to do is to have a different set of include paths when the target is built versus when the target is installed somewhere, which can be done using generator expressions:

target_include_directories(libminisat PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> )

Support for use as a subdirectory

Always alias exported targets to give them the same name as when they are exported in a namespace.

After all this work, our CMakeLists for MiniSat supports installation and CMake package export, but cannot be properly used as a subdirectory, without installation. Luckily, supporting this is trivial, all we need to do is to create an alias for libminisat with namespaced name:

add_library(MiniSat::libminisat ALIAS libminisat)

Now we are done. At least for simple libraries like Minisat, which have no dependencies of their own.

Packaging libraries with dependencies

So what can you do when your library has a dependency? Your package should check whether its dependency is present while configuring itself, which means that the checks go into FooConfig.cmake . There is even a helper macro for use within FooConfig.cmake , find_dependency .

As an example, if your library depends on Boost.Regex, your FooConfig.cmake.in will look something like this:

@PACKAGE_INIT@ find_dependency(Boost 1.60 REQUIRED COMPONENTS regex) include("${CMAKE_CURRENT_LIST_DIR}/FooTargets.cmake")

Other things that go into FooConfig are various variables that you want your package to provide to consumers, platform-specific configuration and so on.

The actual CMakeLists from our Minisat fork can be found here. It should be functionally the same as the one explained in this post, but with some minor differences.