For a brief user-level introduction to CMake, watch C++ Weekly, Episode 78, Intro to CMake by Jason Turner. LLVM’s CMake Primer provides a good high-level introduction to the CMake syntax. Go read it now.
After that, watch Mathieu Ropert’s CppCon 2017 talk Using Modern CMake Patterns to Enforce a Good Modular Design (slides). It provides a thorough explanation of what modern CMake is and why it is so much better than “old school” CMake. The modular design ideas in this talk are based on the book Large-Scale C++ Software Design by John Lakos. The next video that goes more into the details of modern CMake is Daniel Pfeifer’s C++Now 2017 talk Effective CMake (slides).
This text is heavily influenced by Mathieu Ropert’s and Daniel Pfeifer’s talks.
If you are interested in the history and internal architecture of CMake, have a look at the article CMake in the book The Architecture of Open Source Applications.
Modern CMake is only available starting with version 3.0.0.
CMake is code. Therefore, it should be clean. Use the same principles for CMakeLists.txt
and modules as for the rest of the codebase.
For example, a project might use a common set of compiler warnings. Defining such properties globally in the top-level CMakeLists.txt
file prevents scenarios where public headers of a dependent target causing a depending target not to compile because the depending target uses stricter compiler options. Defining such project properties globally makes it easier to manage the project with all its targets.
Those commands operate on the directory level. All targets defined on that level inherit those properties. This increases the chance of hidden dependencies. Better operate on the targets directly.
Different compilers use different command-line parameter formats. Setting the C++ standard via -std=c++14
in CMAKE_CXX_FLAGS
will brake in the future, because those requirements are also fulfilled in other standards like C++17 and the compiler option is not the same on old compilers. So it’s much better to tell CMake the compile features so that it can figure out the appropriate compiler option to use.
As an example, don’t add -Wall
to the PUBLIC
or INTERFACE
section of target_compile_options
, since it is not required to build depending targets.
Starting with CMake 3.4, more and more find modules export targets that can be used via target_link_libraries
.
Don’t fall back to the old CMake style of using variables defined by external packages. Use the exported targets via target_link_libraries
instead.
CMake provides a collection of find modules for third-party libraries. For example, Boost doesn't support CMake. Instead, CMake provides a find module to use Boost in CMake.
Report it as a bug to third-party library authors if a library does not support clients to use CMake.
CMake dominates the industry. It’s a problem if a library author does not support CMake.
It’s possible to retrofit a find module that properly exports targets to an external package that does not support CMake.
See Daniel Pfeifer’s C++Now 2017 talk Effective CMake (slide 24ff.) on how to do this. Keep in mind to export the right information. Use BUILD_INTERFACE
and INSTALL_INTERFACE
generator expressions as filters.
t.b.d. (see beginning of Daniel Pfeifer’s talk)
CMake is a build system generator, not a build system. It evaluates the GLOB
expression to a list of files when generating the build system. The build system then operates on this list of files. Therefore, the build system cannot detect that something changed in the file system.
CMake cannot just forward the GLOB
expression to the build system, so that the expression is evaluated when building. CMake wants to be the common denominator of the supported build systems. Not all build systems support this, so CMake cannot support it neither.
It just makes things simpler. See Dashboard Client via CTest Script for more information.
This simplifies filtering by regex when running tests via CTest.
By defining properties (i.e., compile definitions, compile options, compile features, include directories, and library dependencies) in terms of targets, it helps the developer to reason about the system at the target level. The developer does not need to understand the whole system in order to reason about a single target. The build system handles transitivity.
Calling the member functions modifies the member variables of the object.
Analogy to constructors:
add_executable
add_library
Analogy to member variables:
- target properties (too many to list here)
Analogy to member functions:
target_compile_definitions
target_compile_features
target_compile_options
target_include_directories
target_link_libraries
target_sources
get_target_property
set_target_property
If a target needs properties internally (i.e., compile definitions, compile options, compile features, include directories, and library dependencies), add them to the PRIVATE
section of the target_*
commands.
This associates the compile definitions with their visibility (PRIVATE
, PUBLIC
, INTERFACE
) to the target. This is better than using add_compile_definitions
, which has no association with a target.
This associates the compile options with their visibility (PRIVATE
, PUBLIC
, INTERFACE
) to the target. This is better than using add_compile_options
, which has no association with a target. But be careful not to declare compile options that affect the ABI. Declare those options globally. See “Don’t use target_compile_options
to set options that affect the ABI.”
t.b.d.
This associates the include directories with their visibility (PRIVATE
, PUBLIC
, INTERFACE
) to the target. This is better than using include_directories
, which has no association with a target.
This propagates usage requirements from the dependent target to the depending target. The command also resolves transitive dependencies.
Using a path outside a component’s directory is a hidden dependency. Instead, use target_include_directories
to propagate include directories as usage requirements to depending targets via target_link_directories
.
Being explicit reduces the chance to unintendedly introduce hidden dependencies.
Using different compile options for multiple targets may affect ABI compatibility. The simplest solution to prevent such problems is to define compile options globally (also see “Define project properties globally.”).
Packages defined in the same CMake tree are directly accessible. Make prebuilt libraries available via CMAKE_PREFIX_PATH
. Finding a package with find_package
should be a no-op if the package is defined in the same build tree. When you export target Bar
into namespace Foo
, also create an alias Foo::Bar
via add_library(Foo::Bar ALIAS Foo)
. Create a variable that lists all sub-projects. Define the macro find_package
to wrap the original find_package
command (now accessible via _find_package
). The macro inhibits calls to _find_package
if the variable contains the name of the package. See Daniel Pfeifer’s C++Now 2017 talk Effective CMake (slide 31ff.) for more information.
@rockerbacon, I have written a script that checks for new files and touches the corresponding CMakeLists file. Quoting Linus Thorwald freely I would say: "people who do not use GLOB are smart but ugly"