The CMake language: variables, lists, if

CMake · Essentials · 12 min

Every CMakeLists.txt is a script; knowing its quirks avoids hours of confusion.

CMake has its own small scripting language. Everything is a command call name(args...), and every value is a string.

  • set(NAME value) sets a variable; ${NAME} reads it. An unset variable expands to an empty string, silently.
  • A list is just a string with ; separators: set(SRC a.cpp b.cpp) stores a.cpp;b.cpp. list(APPEND SRC c.cpp), list(LENGTH ...), list(FILTER ...) manipulate them.
  • Unquoted arguments are split on whitespace and on ;, so ${SRC} passes three arguments. Quote it ("${SRC}") to pass a single string.
  • message(STATUS "...") prints during configure; message(FATAL_ERROR "...") stops.
  • if(cond) ... elseif() ... else() ... endif(); operators include NOT, AND, OR, STREQUAL, VERSION_GREATER_EQUAL, DEFINED VAR, EXISTS path, TARGET name.
  • foreach(x IN LISTS SRC) and function(name args) / endfunction().

Scope: functions and add_subdirectory create a new variable scope; changes don't leak to the caller unless you use set(VAR value PARENT_SCOPE). Macros don't create a scope.

if(VAR) versus if(${VAR}) is a classic trap: if dereferences bare names itself, so write if(ENABLE_X), not if(${ENABLE_X}). True values are ON, YES, TRUE, Y, 1 and non-zero numbers.

Example

cmake_minimum_required(VERSION 3.25)
project(lang LANGUAGES NONE)

set(SRC main.cpp util.cpp)
list(APPEND SRC net.cpp)
list(LENGTH SRC n)
message(STATUS "SRC=${SRC} (${n} items)")    # SRC=main.cpp;util.cpp;net.cpp (3 items)

foreach(f IN LISTS SRC)
  if(f MATCHES "^net")
    message(STATUS "network file: ${f}")      # network file: net.cpp
  endif()
endforeach()

function(add_suffix out suffix)
  set(result "")
  foreach(x IN LISTS ARGN)
    list(APPEND result "${x}${suffix}")
  endforeach()
  set(${out} "${result}" PARENT_SCOPE)        # return a value to the caller
endfunction()
add_suffix(objs ".o" a b)
message(STATUS "objs=${objs}")                # objs=a.o;b.o

Remember

  • Every value is a string; lists are ;-separated strings
  • Quote "${VAR}" to keep a list as one argument
  • Functions have their own scope: return values with PARENT_SCOPE

Watch out for

  • Writing if(${FLAG}): if dereferences names itself, so the value is looked up as a variable name.

Check yourself

  1. After set(L a b c), how many arguments does foo(${L}) receive?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cmake.org/cmake/help/latest/manual/cmake-language.7.html