Modern CMake – Tips and Tricks

Dori Exterman
Dori Exterman reading time: 5 minutes
December 14, 2020

Introduction

On 31st August 2020, CMake celebrated its 20th Birthday. CMake has taken the software world by storm. It is estimated to be used by at least 50% of all C++ projects as their build system. CMake versions after 3.0 are called Modern CMake (analogous to C++11 and afterward being known as ‘modern’ C++) and this document gives some tips and tricks to use modern CMake. Learn more about what is CMake. Also, to better understand CMake and its history, and for a comparison with Make check out our CMake vs. Make comparison.

1. Use Modern CMake

The most important tip: use modern CMake. If your project is still using CMake versions below 2.6, spend some time and effort to move to the newer versions. The thumb rule is to use the version of CMake that came after your compiler version.

2. CMake Is Not Just About C++

CMake is no longer just about C++. CMake is constantly growing support for more languages and as on CMake 3.8, has added support for C# and CUDA.  It already supports languages C/C++, Java, Objective C/C++, Swift and Fortran.

3. Passing -std=C++11 Flag Is So Old School

This is more of a caution than a tip. Don’t manually append -std=C++11 to CMAKE_CXX_FLAGS. This practice is vestiges of old school CMake. For modern CMake use CXX_STANDARD and CXX_STANDARD_REQUIRED flags instead.

set(CMAKE_CXX_STANDARD 11)

set(CMAKE_CXX_STANDARD_REQUIRED True)

c++ under the hood

4. Make Sure There Are No in-Source Builds

Prevent in-source builds by explicitly disallowing it in your top level CMakelists.txt. In-source builds pollute the source directory with build related artifacts. You can use

if ( ${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR} )

message( FATAL_ERROR “In-source builds not allowed! Create a build directory and run CMake from there. ” )

endif()

5. Use Documented Options for Specifying Source and Binary Directories

While using the recommended out of source builds in CMake, use the documented -S and -B flags to specify the source and build directory. Older versions used undocumented -H and -B flags to specify the same.

cmake -S . -B build -G “Visual Studio 16 2019” à supported by CMake 3.13+, recommended approach. cmake -H. -Bbuild -G “MSYS Makefiles” à For older CMake versions. No space between the -B flag and folder name!

6. Help Your Users With Pre-Set Options for Building

Presets are a way to specify a collection of CMake options using a file. For complex projects that support multiple platforms, it is always a good idea to provide (also as a user to use) CMake preset files. Presets make sure that multiple configurations across different compiler tool chains and packages are consistent. Preset files are loaded and used by passing the -C flag and is a best practice to provide them in cmake/presets folder in the source directory.

7. Integrate Linter and Formatter to CMake

It is always a best practice to run linter against the codebase to report issues that the compiler misses. A central place to run linter – as against the IDE of programmer – is to integrate linting with CMake. For C/C++ based projects, CMake supports clang-tidy natively starting from version 3.7.2. Enable treating warnings as errors in the CI builds to keep technical debts in check.

Formatting code is akin to religion where developers have strong opinions. But it is always a good practice, more so when multiple developers are involved, to enforce a consistent formatting style. For C++ projects clang-format can easily be integrated to CMake.

8. Integrate Testing Into the Build Using CMake

Whether you decide to use CTest or Google test framework, it is always a good idea to make sure your tests are executed as a part of the continuous integration process. It is even possible as a post build step (POST_BUILD) to run the tests in the compilation time too.

9. Declare Build Flags and Build Dependencies With TARGET_*()

It is not just old school to use include_directories but using the alternatives target_include_directores and target_link_libraries will enforce the design of your project. It is for a good reason that CMake has PRIVATE, PUBLIC and INTERFACE keywords, using them correctly is the key to maintain unidirectional layering of components in your project.

10. Understand When to Use Macros and Functions

Custom commands can be created in CMake using both macros and functions. Macros introduce no extra scope to the variables but functions do. Macros are useful for wrapping commands that have output parameters and in all other cases use functions.

11. Use CMake to Visualize Module Dependencies

CMake natively supports graphviz of dependencies. The output dot file can easily be viewed by using programs like ZGRViewer.

12. Treat CMake Code as Production Code

Give the same attention that you give to production code to CMakeLists.txt too. Make sure it is liberally commented and all the modern CMake best practices are followed.

13. When to Use PRIVATE, PUBLIC or INTERFACE

Here is a nice table describing the use of PRIVATE, PUBLIC and INTERFACE compiler dependencies mean in CMake

Dependency Description
PRIVATE Needed by me, but not by my dependents
PUBLIC Needed both by me and my dependents
INTERFACE Not needed by me, but my dependents

14. FindPackage vs PackageConfig

Findpackage is a CMake or user supplied search script that knows how to look for a package. The modern way for a package author is to supply a <package>Config.cmake. If your package is installed, PackageConfig can provide its details to CMake.

15. CMake Parallel Build – Use All Cores While Building for a Faster Build

During a CMake build, pass –parallel flag with a number of parallel jobs as an option. Remember that not all build generators support –parallel option for a faster build. This option is supported by CMake 3.12 onwards. When using distributed compilation solutions to accelerate your CMake build, such as Incredibuild, you’ll want to set the –parallel flag to a very large number, such as 300, instructing CMake to execute as much as 300 tasks to execute concurrently, which will allow Incredibuild to distribute up to 300 tasks to remote idle cores – resulting in a much faster compilation.

16. Don’t Give Up

This is more of an advice than a tip. CMake is heavily battle-tested and if you have decided to use or migrate to CMake, don’t give up! For a newbie, CMake can be intimidating and frustrating. CMake is open source and the community is very helpful. The reference documentation is very nicely written for CMake. Just don’t give up, help is at hand.

Parting Words

CMake is a great build system generator supported on all major platforms. Visual Studio 17 natively supports CMake and Qt is growing CMake support from Qt6.0. This shows how successful CMake is! The above tips and tricks should guide you in your endeavor. Best of luck working with CMake!

 

 

Dori Exterman
Dori Exterman reading time: 5 minutes minutes December 14, 2020
December 14, 2020

Table of Contents

Related Posts

5 minutes 8 Reasons Why You Need Build Observability

Read More  

5 minutes These 4 advantages of caching are a game-changer for development projects

Read More  

5 minutes What Level of Build Observability Is Right for You?

Read More