CMake is the de-facto C++ build system used by an overwhelming number of C++ projects. Even if you personally favor more modern alternatives such as Meson, Bazel, or Pants, if you ever pull in a 3rd party dependency, there is a good chance that it uses CMake so knowing enough about CMake to understand it is worth knowing.
How to get started
I recommend new users to CMake start with the following resources:
- Mastering CMake is a high level overview of how to use CMake developed and maintained by the CMake Developers.
- Modern CMake Is focuses on a useful subset of CMake for a number of common tasks.
Using CMake to build something
If a package cmake 3.15 or newer (most people), the following sequence will build and install a cmake project
cmake -S ./path/to/sourcedir -B ./path/to/builddir
cmake --build -j $(nproc)
cmake --install
Here ./path/to/sourcedir
is the directory where you source files (specifically the “toplevel” CMakeLists.txt
is stored).
and ./path/to/builddir
is a directory (which may not exist) where you want to store build artifacts prior to installation.
You can customize the build by passing flags to the first cmake command. I commonly use the following
cmake -S ./path/to/sourcedir -B ./path/to/builddir \
-G Ninja \
-DCMAKE_INSTALL_PREFIX=$(pwd)/.local \
-DBUILD_SHARED_LIBS=ON \
-DBUILD_TESTING=ON \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build -j $(nproc)
cmake --install
To prefer the Ninja
build generator, and ccache
for faster development builds with shared libraries
installed to my user’s prefix instaed of the the /
or /usr
prefix which may require admin privledges
and to enable LLVM based tooliing (i.e. clangd
for completions in my editor). These preferences
are encoded into my m
build tool.
CMake also respects GNU style envionment variables (i.e. CXX
, CC
, CFLAGS
,
CXXFLAGS
, and LDLIBS
) to pick up common defaults.
Key Commands
Required boilerplate that must appear at the top of your top-level cmake
cmake_minimum_required
project
Basics
add_library
andadd_executable
create build objectstarget_link_libraries
andPUBLIC
vsPRIVATE
to consume dependancies (including their header file flags) and link librariestarget_include_directories
to set include paths not provided by link librariestarget_compile_features
a portable way to set the C++ standard- The most essential cmake variables
$CMAKE_CURRENT_SOURCE_DIRECTORY
and$CMAKE_CURRENT_BINARY_DIRECTORY
- Basic generator expressions such as
$<BUILD_INTERFACE:>
vs$<INSTALL_INTERFACE:>
for use withtarget_include_directories
add_subdirectory
for code organization
Adding 3rd party dependencies. Do yourself a favor and put all dependencies imports at the top level so the scope is right for the whole project.
find_package
to import dependancies built using CMake and certain system dependancies such as threads, MPI, Python, and OpenMP.find_package(PkgConfig)
andpkg_search_modules
to import dependencies described by pkg-configFetchContent
download and provide dependencies as part of the build, but consider instead third party tools such asspack
,connan
, andvcpackage
.
I’ve written a guide on CMake package dependencies here.
- How do you currently handle dependencies and builds in your system? Is this portable to other machines where you would like to use this software? If not, what do you need to change and why?
Build Options
option
to add build optionsif
for basic control flowconfigure_file
to add a “configure file” which is typically header which has#defines
for certain build system variables set with#cmakedefine
or#cmakedefine01
target_add_sources
to add additional source files to a library after the fact for example to compile in an optional plugin.
Installation
include(GNUInstallDirs)
and its associated variables likeCMAKE_INSTALL_PREFIX
,CMAKE_INSTALL_INCLUDEDIR
, andCMAKE_INSTALL_LIBDIR
install(TARGETS)
to install your libraries and their export filesinstall(DIRECTORY)
to install your header files and data filesinclude(CMakePackageConfigHelpers)
andconfigure_package_config_file
andwrite_basic_package_version_file
to make your package importable by others
See this project for an example on how to properly expose a CMake dependency
Testing
include(CTest)
,BUILD_TESTING
, andadd_test
include(GoogleTest)
andgtest_discover_tests
for high quality integration of googletest based tests
CMake Magic Variables to reconfigure builds
CMAKE_BUILD_TYPE
automatically configure optimizations like-O2
and-Og -g
with settings likeRelease
orDebug
there are also other settings forRelWithDebInfo
andMinSizeRel
for small build artifactsBUILD_SHARED_LIBS
to choose between shared and static linkingINTERPROCEDURAL_OPTIMIZATION
property enables IPO for faster optimizations
Enabling 3rd Party technologies
CMake provides most of what you want on its own, but a few tools are worth knowing about:
spack
a dependencies manager for HPC softwareconnan
andvcpackage
a dependency managers more common in enterpriseccache
andsccache
can be provided toCMAKE_<Lang>_COMPILER_LAUNCHER
to dramatically speed up re-builds buildsninja
is a much faster project builder thanMake
. You can enable it with-G Ninja
passed to cmakeccmake
a terminal user interface for setting cmake build optionsclang-tidy
andinclude-what-you-use
for extra static analysis can be used by setting the<LANG>_CLANG_TIDY
and<LANG>_INCLUDE_WHAT_YOU_USE
Doxygen
for automatic documentation form the header-files of your project. Can be used automatically from CMake withfind_package(Doxygen)
Important Concepts
PUBLIC
,INTERFACE
vsPRIVATE
dependency and configurationsSHARED
,STATIC
,INTERFACE
vsIMPORTED
libraries. The later you will use directly only seldom but is used forfind_package
internally.- that
target_*
functions accumulate from everywhere they are used allowing code that adds specific features to be spread out - Don’t use globbing to add files to a build. List them explicitly for best performance
Debugging CMake
CMake can be obtuse at times. A few key commands can help:
--trace
puts cmake into trace mode to print all calls made in a CMake build system.CMAKE_FIND_DEBUG_MODE
prints out extra information when finding a packages that can be used to track down how a variable was set. Newer versions have a flag--debug-find-pkg=
which can enable this for specific packages.message(WARNING ...)
print a warning message to the console with the specified message can be used for tracing and viewing values of a variable at a point in the code
Advanced Topics
for
,function
, andmacro
for advanced control flowexecute_process
for when you just need to run a script- CUDA and other accelerator language support
Where to learn more
Changelog
- 2023-02-09 linked to other cmake resources added section on debugging
- 2023-01-09 Added basic usage example
- 2022-11-15 Created