I’d like to collect CMake Best Practices from the Internet.

Use function instead of macro

Macro overrides variables in caller’s variable scope. Use function which has an own variable scope. And, use set(<variable> <value>... PARENT_SCOPE) if you want to set a variables in parent scope.

function(hello_get_something var_name)
  ...
  set(${var_name} ${ret} PARENT_SCOPE)
endfunction()

Use target_sources

Using target_sources makes more concise than using a custom variable.

set(src hello.cxx)

if(WIN32)
  list(APPEND src system_win.cxx)
elseif(UNIX)
  list(APPEND src system_posix.cxx)
else()
  list(APPEND src system_generic.cxx)
endif()

add_library(hello ${src})
add_library(hello hello.cxx)

if(WIN32)
  target_sources(hello PRIVATE system_win.cxx)
elseif(UNIX)
  target_sources(hello PRIVATE system_posix.cxx)
else()
  target_sources(hello PRIVATE system_generic.cxx)
endif()

Use target_compile_features

Do not use a compile option -std=c++11 explicitly.

target_compile_options(hello PRIVATE -std=c++11)
target_compile_features(hello PRIVATE cxx_variadic_templates)

Prefix cache entries

The cache entry is global. Do not assume your project is the top project. Use a prefix to avoid name conflicts.

option(hello_use_foo "Use Foo" ON)

Do not use CMAKE_SOURCE_DIR

One of the main features of CMake is the ability for different C++ libraries to easily be assembled together. — Bill Hoffman (The original creator of CMake) https://blog.kitware.com/celebrating-20-years-of-cmake/

Do not assume your project is always the top project. Use CMAKE_CURRENT_SOURCE_DIR, PROJECT_SOURCE_DIR, or <PROJECT-NAME>_SOURCE_DIR instead.

Do not generate into CMAKE_CURRENT_BINARY_DIR

The directory ${CMAKE_CURRENT_BINARY_DIR} is shared by multiple configurations in multi-configuration build systems such as Visual Studio. Then, if you run clean in Release configuration, generated files of Debug configuration will also be deleted. Generated files should be in ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}.

target_sources(hello PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/out.c)
add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/out.c
  COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/script.py ${CMAKE_CURRENT_SOURCE_DIR}/in.txt > ${CMAKE_CFG_INTDIR}/out.c
  DEPENDS script.py in.txt
  )

On the other hand, you don’t need to use CMAKE_CFG_INTDIR in the following variables because multi-configuration build systems automatically append /${CMAKE_CFG_INTDIR} if CMAKE_CFG_INTDIR is not included.

  • CMAKE_ARCHIVE_OUTPUT_DIRECTORY
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY

If you write the following script, the output directory is ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR} in multi-configuration build systems.

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

Use cmake -E

Do not use Unix commands such as cd, rm, mkdir, cp, env and tar. CMake supports them. See cmake(1).

add_custom_command(
  OUTPUT ...
  COMMAND ${CMAKE_COMMAND} -E ...
  )

Do not specify a library type explicitly unless necessary

Users of your library should be able to choose a static library or a shared library to build. Do not specify a library type explicitly unless necessary.

add_library(hello hello.cxx)

Variable BUILD_SHARED_LIBS controls it.

cmake -D BUILD_SHARED_LIBS=ON $src_dir

The initial value of BUILD_SHARED_LIBS is OFF. If you want to change the default, make it an option.

option(BUILD_SHARED_LIBS "build as shared libraries" ON)

This violates the “Prefix cache entries” rule. Another option is:

option(hello_build_shared_libs "build hello as a shared library" ON)
set(BUILD_SHARED_LIBS ${hello_build_shared_libs})

Note that directories have an own variable scope, setting BUILD_SHARED_LIBS does not affect the parent directory.

Define FOLDER directory property

If you have a lot of targets, FOLDER target property is useful to organize the project files into folders in IDE. But, it is tedious to specify the property to all targets. There is a solution to specify a folder name to a directory. If a target property is not specified, directory property is inherited. First, define property FOLDER as INHERITED.

define_property(DIRECTORY PROPERTY FOLDER INHERITED BRIEF_DOCS "source file folder name in project file" FULL_DOCS "source file folder name in project file")

Then, set FOLDER directory property to the current directory.

set_directory_properties(PROPERTIES FOLDER gfx/hello)

References