CMake 101¶
Build & Metabuild Systems¶
Make and Ninja are build systems, CMake is a metabuild system. CMake generates makefiles that are given to build systems to actually build the project. So the usual workflow is
mkdir build
cd build
cmake ../
make
To see what CMake does, we can write the CMake equivalence of
clang++ -std=c++17 -c tools.cpp -o tools.o
ar rcs libtools.a tools.o
clang++ -std=c++17 main.cpp -L . -ltools -o main
which is
add_library(tools tools.cpp)
add_executable(main main.cpp)
target_link_library(main tools)
We can find a one-to-one correspondence between them. The add_library(tools tools.cpp)
command corresponds to clang++ -std=c++17 -c tools.cpp -o tools.o
and ar rcs libtools.a tools.o
. The add_executable(main main.cpp)
part corresponds to clang++ -std=c++17 main.cpp -o main
. The target_link_library(main tools)
part corresponds to -L . -ltools
.
If you want to find an external library, you can use the find_package
command. Note that the find_package
command is looking for Find<package>.cmake
files.
Basic Installing Using CMake¶
We now talk about how to install packages using CMake. All of the source code in this section is in the cmake_install
folder.
Compared to the last section, in the C++ code all we changed is remove the reliance on Eigen3
in main.cpp
. What it means to install a package is to store the generated binary file, the header files, and the library files to some predefined location. To do this we add the following three lines in CMakeLists.txt
install(TARGETS tools DESTINATION lib)
install(FILES include/tools.hpp DESTINATION include)
install(TARGETS main DESTINATION bin)
which is then installed using (only for CMake V3.15.0 and up, see here for older versions)
cmake --install .
We can see that the installation can be seperated into two types, installing targets and installing files. The targets are what is generated within the building process such as the library file libtools.a
and the binary file main
. The files are files that alread exists like the header file. The DESTINATION
specifies where the installed files are stored at. The destinations are relative to a prefix directory, which can be specified when performing the installation using
cmake --install . --prefix "/Users/BolunDai0216/Documents/BuildingCPP/cmake_install/install/"
and the files will then be installed within
/Users/BolunDai0216/Documents/BuildingCPP/cmake_install/install/bin/
/Users/BolunDai0216/Documents/BuildingCPP/cmake_install/install/include/
/Users/BolunDai0216/Documents/BuildingCPP/cmake_install/install/lib
The Basic Way to Use Installed Packages¶
To use installed libraries the easiest way is to figure out where the header files and library files are located and just use them directly. The source code of this section can be found in the cmake_use_installed_package_basic
folder.
First, to make our life easier we create a variable to store the location of the installation
set(CUSTOM_INSTALLATION_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../cmake_install/install)
We are using a CMake-defined variable CMAKE_CURRENT_SOURCE_DIR
, which gives us the directory where the CMakeLists.txt
files is stored. Another commonly used CMake-defined variable is CMAKE_CURRENT_BINARY_DIR
which gives us the directory within the build folder.
We will be using the installed files from the last section, so the CUSTOM_INSTALLATION_DIR
will be the install
folder from last section. Then, like before we can include the header files as
include_directories(${CUSTOM_INSTALLATION_DIR}/include/)
Since we already have the library files (.a
files), we would not need to run add_library
, we can directly link the .a
file using
target_link_libraries(main ${CUSTOM_INSTALLATION_DIR}/lib/libtools.a)
A bit more advanced and general way to do this is using find_library
. In this example, we can use find_library
as
find_library(LIBTOOLS_LIBRARY
NAMES libtools.a
HINTS ${CUSTOM_INSTALLATION_DIR}
PATH_SUFFIXES lib/
)
a nice intro to find_library
can be found here. Then if we compare the variable LIBTOOLS_LIBRARY
and what we had before ${CUSTOM_INSTALLATION_DIR}/lib/libtools.a
, we can see that they are the same. Then, we can simply change the target_link_libraries
to
target_link_libraries(main ${LIBTOOLS_LIBRARY})
Similarly, for the header files we can do the same thing. We can find the path to the include folder using the find_path
command
find_path(LIBTOOLS_INCLUDE_DIR
NAMES tools.hpp
HINTS ${CUSTOM_INSTALLATION_DIR}
PATH_SUFFIXES include/
)
and if we compare the result of LIBTOOLS_INCLUDE_DIR
and ${CUSTOM_INSTALLATION_DIR}/include/
we can see that they are pointing to the same directory. Then to utilize LIBTOOLS_INCLUDE_DIR
and LIBTOOLS_LIBRARY
, we can first create an empty library target (I don’t think empty is the official terminology), and then set the required properties. In this case, we only need to set the INTERFACE_INCLUDE_DIRECTORIES
to LIBTOOLS_INCLUDE_DIR
and IMPORTED_LOCATION
to LIBTOOLS_LIBRARY
. We do this using the following commands
add_library(tools STATIC IMPORTED)
set_target_properties(tools
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${LIBTOOLS_INCLUDE_DIR}
IMPORTED_LOCATION ${LIBTOOLS_LIBRARY}
)
After this is done, we can then simply link the library using
target_link_libraries(main tools)
CMake Installation Using Config Files¶
A more general approach to use external packages is using <package-name>Config.cmake
files. This section, we will look into how to create such a config file. All of the source code of this section can be found in the cmake_install_with_config
folder.
We first build the tools library using the commands
include_directories(include/)
add_library(tools src/tools.cpp)
target_include_directories(tools INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>)
The target_include_directories
commands uses generator expressions to say when using the built tools library the content within the ${CMAKE_CURRENT_SOURCE_DIR}
directory is included, while when using the installed tools library the content within the /include
directory in the installation directory is included. Next, we generate the executables as before using the commands
add_executable(main src/main.cpp)
target_link_libraries(main tools)
The next step is to install the files.
install(TARGETS tools EXPORT toolsTargets DESTINATION lib)
install(FILES include/tools.hpp DESTINATION include)
These lines installs the tools library within the lib
folder within the installation directory and install the header files within the include
folder within the installation directory. The installation directory is specified when installing it. When installing the tools library, it also exports a target called toolsTargets
. Then, we use the following line
install(EXPORT toolsTargets FILE toolsTargets.cmake DESTINATION lib/cmake/tools)
to install a <lib-name>Targets.cmake
file that contains information about the toolsTargets
target. Now, we are in a good position to install the Config.cmake
file. First, we include CMakePackageConfigHelpers
, which provides helper functions for generating the Config.cmake
file. The Config.cmake
file is generated using the command
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/toolsConfig.cmake"
INSTALL_DESTINATION "lib/cmake/tools"
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO)
The INSTALL_DESTINATION
only needs to have the same number of subdirectories, i.e., if we want to install it in install/lib/cmake/tools
we would need a three level destination as a/b/c
here. By changing the INSTALL_DESTINATION
to a/b/c
and a/b
you’ll get what I mean. The Config.cmake.in
file simply points to the Target.cmake
file. A ConfigVersion.cmake
file can be created using the command
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/toolsConfigVersion.cmake"
VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
COMPATIBILITY AnyNewerVersion)
Then we install both the Config.cmake
and ConfigVersion.cmake
files. Finally, we can build and install the package using the commands
cmake .. && cmake --build .
cmake --install . --prefix "~/Documents/BuildingCPP/cmake_install_with_config/install/"
Using find_package()
to use external libraries¶
In the last section, we showed how to create config files. This section, we will look into how to use the config files to import external libraries in your own CMake project. All of the source code of this section can be found in the cmake_use_config
folder.
In the last section, we created a Config.cmake
file. In this section, we will enable the find_package
command to find it. Since we are installing it in a non-default location, we will need to pass on the directory to the find_package
command
find_package(tools
CONFIG
REQUIRED
PATHS /path/to/cmake_install_with_config/install/lib/cmake/tools)
Then, we can directly using the tools
library by linking it
target_link_libraries(main tools)