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)storesa.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 includeNOT,AND,OR,STREQUAL,VERSION_GREATER_EQUAL,DEFINED VAR,EXISTS path,TARGET name.foreach(x IN LISTS SRC)andfunction(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.oRemember
- 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.