2. CMake

2.1. Formatting

2.1.1. No space between command and (

# 'if' is not a keyword, it's a command, like 'execute_process'
if(var1 EQUAL var2)
  command(...)
  command(...)
endif()

2.1.2. Enclose path with double quotes

sugar_foo(sources "./sources")
    # sources - local variable
    # "./sources" - some directory

Next code produce error set_target_properties called with incorrect number of arguments:

set(x "")
set_target_properties(foo PROPERTIES INCLUDE_DIRECTORIES ${x})

Works fine:

set(x "")
set_target_properties(foo PROPERTIES INCLUDE_DIRECTORIES "${x}")

Example with file(WRITE ...):

file(WRITE "${A}/path/to/file/with spaces/in" "message") # quotes required
file(WRITE "${A}/path/to/file/without-spaces/in" "message") # quotes not necessary but keep style the same

Note that quotes required for anything related to configure_file:

# script.cmake.in
file(WRITE ${filename} ${content})
set(filename "/path/with/spaces/A B C")
set(content "a b c")
configure_file(.../script.cmake.in .../script.cmake)
execute_process(COMMAND ${CMAKE_COMMAND} -P .../script.cmake ...)

Result will be creation of file /path/with/spaces/A with content BCabc. Fix:

# script.cmake.in
file(WRITE "${filename}" "${content}")

or

# script.cmake.in
file(WRITE "@filename@" "@content@")

Quite the same happens in install(CODE command:

install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E echo hello ...)")

content of cmake_install.cmake will be:

execute_process(COMMAND /.../bin/cmake.exe -E echo hello ...) # no quotes!

it means that if CMake is installed to path with spaces this command will not be executed.

2.2. Indentation

2 spaces is default indentation:

command(...)
command(...)
if(...)
  # +2 spaces
  command(...)
  if(...)
    # +2 spaces
  endif()
endif()

function(...)
  # +2 spaces
  command(...)
endfunction()

Use 4 spaces for breaking long line:

command(short line)
command(
    # +4 spaces
    long line arg1 arg2
)
command(
    # +4 spaces
    very
    long
    line
    arg1
    arg2
    arg3
)

alternatively same line can be kept for name-value:

command(
    # +4 spaces
    VALUE1 value1
    VALUE2 value2
    VALUE3 value3
    # break long line with additional indentation
    VALUE4
        # +4 spaces
        value4a
        value4b
        value4c
)

2.3. Naming

Lower case for commands:

if(A)
  command(...)
endif()

Upper case for command specifiers:

list(APPEND list_var append_var)

Lower case for local variables (temps, parameters, ...):

foreach(x ${ARGV})
  message(${x})
endforeach()

Upper case for global variables (like variables which Find-modules use/setup):

include(ModuleA) # define MODULE_A_MODE
if(MODULE_A_MODE)
  command(...)
endif()

Lower case for function/macro names:

macro(do_foo)
  command(...)
endmacro()

do_foo(...)

Start internal variable’s name with the _ in macro and headers:

macro(do_foo)
  # command `macro` doesn't introducing new scope
  # hence this `set` commands will pollute user's space
  set(_value1 "...")
  set(_value2 "...")
  # ...
endmacro()
# MyModule.cmake
# same for the header
string(COMPARE EQUAL "${A}" "${B}" _is_equal)
if(_is_equal)
  # ...
endif()
# variable _is_equal not defined
include(MyModule)
# variable _is_equal defined

Examples:

Note

Use functions when it’s possible!

To prevent collisions guard variable name should match path to the module:

# module flags/gcc.cmake from project Polly
if(DEFINED POLLY_FLAGS_GCC_CMAKE_)
  return()
else()
  set(POLLY_FLAGS_GCC_CMAKE_ 1)
endif()
# module cmake/Hunter from project Hunter
if(DEFINED HUNTER_CMAKE_HUNTER_)
  return()
else()
  set(HUNTER_CMAKE_HUNTER_ 1)
endif()

2.4. Pitfalls

2.4.1. STREQUAL

Usage of if(${A} STREQUAL ${B}) is not recommended, see this SO question. Preferable function is string:

string(COMPARE EQUAL "${A}" "${B}" result)
if(result)
  message("...")
endif()

Note

Fixed in CMake 3.1 by CMP0054 policy

2.4.2. export(PACKAGE ...)

2.5. Library of CMake extra modules

  • All defined functions/macros start with <libname>_ (example)
  • no message command inside, only wrappers
  • <libname>_STATUS_PRINT option control message output (default value is ON)
  • <libname>_STATUS_DEBUG option used for more verbose output and additional debug checks (default value is OFF)
  • one function/macro - one file
  • <include-name> equal <function-name>

As the result: include only what you need, check that included function is used by simple in-file search (and, of course, delete it if it’s not). If you need to use sugar_foo_boo function, just include sugar_foo_boo.cmake:

include(sugar_foo_boo) # load sugar_foo_boo.cmake file with sugar_foo_boo function
sugar_foo_boo(some args) # use it

2.6. Note about wrappers

Probably some wrappers (like sugar_fatal_error) occupy more space than functionality it is wrapping (: The purpose of this functions is to make additional check. See the difference between this two misprints:

message(FATA_ERROR "SOS!") # Output will be: "FATA_ERRORSOS!", no error report...
include(sugar_fata_error) # include error will be reported
sugar_fata_error(...) # function not found error will be reported

2.7. iOS detection

Polly toolchain set IOS variable:

if(IOS)
  # iOS code
endif()

also CMAKE_OSX_SYSROOT can be checked:

string(COMPARE EQUAL "${CMAKE_OSX_SYSROOT}" "iphoneos" is_ios)

2.8. Temporary directories

  • ${PROJECT_BINARY_DIR}/_3rdParty/<libname>