CMake coding standards

In order to make your ROS packages most maintainable and avoid several frequent errors, follow the standards below.

Do care about the style your cmake code is written in. For inspiration, look at the guideline examples here: CMake Style recommendations KDE cmake coding guidelines

Call catkin_package before any targets

The following lines must always appear the CMakeLists.txt in this order:

cmake_minimum_required(VERSION 3.0.2)
project(myproject)
find_package(catkin REQUIRED <COMPONENTS ...>)
catkin_package(<...>)

While there might be additional function calls before catkin_package() (e.g. for finding other libraries or generating messages) it must be invoked before any targets are added.

Use ${PROJECT_NAME} wherever possible

Use ${PROJECT_NAME} for global variables, targets and labels instead of repeating the project name manually or using fixed names.

You can use ${PROJECT_NAME} as prefix for global variable names as well, as shown in examples below. Variables become global if they are set using the CACHE argument or created using the option() macro.

After you defined your project name like this:

project(myproject)

dont do this:

catkin_add_gtest(test ...)
add_executable(myproject ...)
set(use_feature 42 CACHE STRING "description")
option(use_feature "on or off" OFF)
macro(xyz) ...

do this instead:

catkin_add_gtest(${PROJECT_NAME}_test ...)
add_executable(${PROJECT_NAME} ...)
set(${PROJECT_NAME}_use_feature 42 CACHE STRING "description")
option(${PROJECT_NAME}_use_feature "on or off" OFF)
macro(${PROJECT_NAME}_xyz) ...

This will avoid conflicts between packages and errors due to copy&paste.

find_package(… REQUIRED)

Use REQUIRED on all calls to find_package if they aren’t actually optional (i.e. you’re not going to check thing_FOUND and enable/disable features).

Avoid Pkg_config

CMake allows to use pkg-config:

find_package(PkgConfig)
pkg_check_modules(XXX xxx)

However you should (in CMakeLists.txt files) if possible always prefer the method:

find_package(xxx)

This is usually the most flexible and portable way.

Keep lists sorted

Whenever using a list of items (i.e. in find_package(COMPONENTS …) or files which should be build or installed) keep them alphabetically sorted. This improves readability when looking for specific items. (There are exceptions which require a specific custom order like the list of projects inside a stack).

Lowercase keywords

Keywords like if, for etc. are all lowercase.

Upper arguments

Use UPPER_CASE for arguments to CMake functions and macros (e.g. REQUIRED, NO_MODULE, QUIET)

Closing keyword should have empty parenthesis

The closing keywords like endif() and endforeach() should not repeat the condition of the opening keyword.

Indentation

Indent all code correctly, i.e. the body of

  • if/else/endif
  • foreach/endforeach
  • while/endwhile
  • macro/endmacro
  • function/endfunction

Use spaces for indenting, 2 spaces preferably

Variable names

For custom variables, avoid the following list of suffixes in any CMakeLists.txt, those should only be set by FindXXX.cmake or XXXConfig.cmake files:

  • XXX_DEFINITIONS
  • XXX_EXECUTABLE
  • XXX_INCLUDE_DIRS
  • XXX_INCLUDE_DIR
  • XXX_YY_INCLUDE_DIR
  • XXX_LIBRARIES
  • XXX_LIBRARY_DIRS
  • XXX_LIBRARY
  • XXX_YY_LIBRARY
  • XXX_ROOT_DIR
  • XXX_FOUND
  • XXX_YY_FOUND
  • XXX_RUNTIME_LIBRARY_DIRS
  • XXX_VERSION_STRING
  • XXX_VERSION_MAJOR
  • XXX_VERSION_MINOR
  • XXX_VERSION_PATCH
  • XXX_VERSION_YY
  • XXX_WRAP_YY

You may use such variables of course by reading their value after calling find_package(), but do not manually change them.

Forbidden variables

Do not set

  • CMAKE_CXX_FLAGS
  • CMAKE_FIND_ROOT_PATH

Conditions and Variables

Always quote variable that represent a string:

set(myvar "foo")
if("${myvar}" STREQUAL "bar")
# ...
endif()

Do not quote variable that are booleans:

set(mybvar ON)
set(mybvar OFF)
if(${myvar})
# ...
endif()

When storing paths in variables, do NOT have the cmake variables end up with a slash:

# YES:
set(_my_path "path/to/foo")
set(_my_other_path "${_my_path}/${_my_var}")
# NO:
set(my_path "path/to/foo/")
set(_my_other_path "${_my_path}${_my_var}")   # wrong: this is ugly

Use if(DEFINED varname) to check if a variable is set:

if(DEFINED myvar)
#  ...
endif()

Use if(varname) to check it a variable has a non-empty value:

if(myvar)
#  ...
endif()