Skip to content

CMake Tips 'n Tricks

Best Practices

In the top level CMakeLists.txt, it is a good practice to use include(...) statements. These statements directly embed the included file in the location where the include statement is made, allowing for better modularity and readability of the CMakeLists.txt file. For clarity, a cmake folder is typically used to house important included CMake files. Important files that should be included are

  • cmake/Testing.cmake - Has build directives for the testing library.
  • cmake/CodeCoverage.cmake - Has build directives for making code coverage reports during testing (often included in Testing.cmake).
  • cmake/Cpack.cmake - Has build directives for packing the library into a DEB archive.

CMake Variables

See the Ubuntu Manpages for a list of variables provided by CMake. A useful subset of these variables is provided at the bottom of this article.

To manipulate variables used at build time (either custom or CMake-provided), use cmake -D<argument>=<value> .. and access them using ${<argument>}.

For example, if your CMakeLists.txt contains the following code.

cmake
message("CUSTOM_VARIABLE is ${CUSTOM_VARIABLE}")
message("CUSTOM_VARIABLE is ${CUSTOM_VARIABLE}")

Then at the command line use,

sh
$ cmake -DCUSTOM_VARIABLE=qwerty ..
CUSTOM_VARIABLE is qwerty
...
$ cmake -DCUSTOM_VARIABLE=qwerty ..
CUSTOM_VARIABLE is qwerty
...

CMake Commands

configure_file: At build time, copy a file to another location and modify it's contents.

  • This can be done with a config.h.in files, for example, to auto populate a configuration header given project name and version in the CMakeLists.txt.
  • @ONLY restricts variable replacement to references of the form @VAR@, otherwise all variables in the form ${<argument>} are also replaced.
cmake
configure_file(config.yaml.in ${CMAKE_CURRENT_BINARY_DIR}/config.yaml @ONLY)
configure_file(config.yaml.in ${CMAKE_CURRENT_BINARY_DIR}/config.yaml @ONLY)

install: Defines the installation rules for a target set of files, directories, libraries, etc.

Getting Clangd to work in Vim

Ensure you have the following snippets in your CMakeLists.txt.

cmake
# Exports the compile_commands.json file
set(CMAKE_EXPORT_COMPILE_COMMANDS
    ON
    CACHE INTERNAL "")
if(CMAKE_EXPORT_COMPILE_COMMANDS)
  set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
      ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
endif()
...
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES CXX_STANDARD 17)
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES ENABLE_EXPORTS 1)
# Exports the compile_commands.json file
set(CMAKE_EXPORT_COMPILE_COMMANDS
    ON
    CACHE INTERNAL "")
if(CMAKE_EXPORT_COMPILE_COMMANDS)
  set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
      ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
endif()
...
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES CXX_STANDARD 17)
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES ENABLE_EXPORTS 1)

Useful Variables

  • `CMAKE_CURRENT_SOURCE_DIR=/path/to/project-root
  • `CMAKE_CURRENT_BINARY_DIR=/path/to/project-root/build
  • CMAKE_PROJECT_NAME=project-root
  • CMAKE_PROJECT_VERSION=0.1.2 Available with GNUInstallDirs
  • CMAKE_INSTALL_BINDIR=bin
  • CMAKE_INSTALL_SYSCONFDIR=etc
  • CMAKE_INSTALL_LIBDIR=lib
  • CMAKE_INSTALL_INCLUDEDIR=include Additional important variables can be found here.

CMake learning

When using the find method you can filter the permission denied actor by following instructions at: https://unix.stackexchange.com/questions/42841/how-to-skip-permission-denied-errors-when-running-find-in-linux

See the beginner tutorials at: https://www.youtube.com/watch?v=abuCXC3t6eQ&list=PLK6MXr8gasrGmIiSuVQXpfFuE1uPT615s&index=4

OFFICIAL CMAKE tutorial: https://cmake.org/cmake/help/latest/guide/tutorial/index.html

(get the tutorial source code from the cmake git repo)

Step 1: Starting point

The most basic project is a simple executable that is built from source code files. For simple projects, a three line CMakeLists.txt is all that is required.

Always start with the cmake_minimum_required(VERSION 3.xx), then you can set a project name, configure files (see Step 1), and most importantly, addexecutable(Name-of-exec-file exec-file.cxx)

You can set variables in the cmake scripting language using the set() function.

To build and test

  1. Create a build directory where the build files and executables will be installed.

Mkdir build && cd build (for example)

  1. Run the cmake command on the directory containing the source files

Cmake ../src (or something similar)

  1. Run the cmake --build command specifying the build directory

Cmake --build .

Step 2: Adding a library

When adding a library you can use the add_library or add_subdirectory functions, check cmake documentation for more information.

Step 3: Adding usage requirements for library

Usage requirements allow better control over library/executable’s link and include line. Primary commands

target_compile_definitions()

target_compile_options()

target_include_directories()

target_link_libraries()

find_package() has two modes…

one is MODULE mode, which searches for a Find[package].cmake

The other is CONFIG mode which searches for [package]Config.cmake

EXTRAS

To run cmake you can pass the path to the src directory or to an already configured binary directory.

Some of the most common things in cmake is making libraries and executables.

Ninja build is a small build system that focuses on speed and is designed to be built into (embedded in) other build systems.

Compiler systems…

I know of gcc, g++, and now llvm, but I’m not sure of their differences and when it is better to employ which one.

ADDITIONAL RESOURCES