Functions in CMake are the mechanism through which build logic is encapsulated and reused. CMake ships with a rich set of built-in functions that handle the most common build tasks. Beyond those, CMake's scripting language supports custom function definitions that you can use to eliminate repetition and organize complex build configurations.
Built-in Functions Overview
CMake's built-in functions follow a consistent naming convention that makes their purpose clear:
CMake Built-in Function Naming Patterns:
+------------------+------------------------------------------+
| Pattern | Purpose |
+------------------+------------------------------------------+
| add_* | Add a target to the project |
| target_* | Configure a specific target |
| *_include_* | Relate to header file directories |
| *_link_* | Relate to linking (linker configuration) |
| *_directories | Relate to directory paths |
| find_* | Search for external resources |
| get_* | Retrieve a value |
| set_* | Set a property or value |
+------------------+------------------------------------------+
Variable Manipulation Functions
`get_cmake_property` and Variable Retrieval
# Retrieve the value of a variable by name
set(myVariable "Hello, world!")
get_cmake_property(myVariable_value VARIABLES)
A more practical pattern for passing variable names dynamically uses get_variable():
set(myVariable "Hello, world!")
get_variable(myVariable TheValueVariable)
message("${TheValueVariable}") # Output: Hello, world!
`unset`
Remove a variable from the current scope:
set(myVariable "Hello, world!")
message("Before: ${myVariable}") # Hello, world!
unset(myVariable)
message("After: ${myVariable}") # (empty)
`string` — String Manipulation
The string() function provides a comprehensive API for working with string variables:
# REPLACE: substitute a substring
set(myString "Hello, world!")
string(REPLACE "world" "CMake" myString "${myString}")
message("${myString}") # Output: Hello, CMake!
# TOUPPER / TOLOWER
string(TOUPPER "${myString}" upper_str)
message("${upper_str}") # HELLO, CMAKE!
# LENGTH
string(LENGTH "${myString}" str_len)
message("Length: ${str_len}")
# SUBSTRING
string(SUBSTRING "${myString}" 0 5 first_word)
message("First word: ${first_word}") # Hello
`string(REGEX)` — Regular Expression Matching
set(myString "Hello, CMake!")
# MATCH: find the first match
string(REGEX MATCH "CMake" match "${myString}")
message("${match}") # Output: CMake
# MATCHALL: find all non-overlapping matches
string(REGEX MATCHALL "[A-Za-z]+" words "Hello World CMake")
message("${words}") # Hello;World;CMake
# REPLACE with regex
string(REGEX REPLACE "[0-9]+" "#" result "abc123def456")
message("${result}") # abc#def#
Build Functions
These are the core functions that drive the actual compilation and linking process.
`add_library`
Creates a build target that produces a library:
add_library(libraryName
[STATIC|SHARED|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ...)
- STATIC: Archive linked into executables at compile time (
.a,.lib) - SHARED: Loaded at runtime by the OS (
.so,.dll,.dylib) - MODULE: Like SHARED but not linked, only loaded dynamically (plugins)
- EXCLUDE_FROM_ALL: Not built unless explicitly requested
# Static library
add_library(mathlib STATIC
src/math/add.cpp
src/math/multiply.cpp
src/math/divide.cpp)
# Shared library
add_library(mylib SHARED
src/mylib.cpp)
# Header-only interface library (no compilation)
add_library(myheaders INTERFACE)
target_include_directories(myheaders INTERFACE include/)
`set_target_properties`
Configures build properties on a target:
set_target_properties(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
Example — set the C++ standard for a target:
set_target_properties(mathlib PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF # disable GNU extensions
VERSION 1.2.3 # library version
SOVERSION 1 # ABI version (for shared libs)
)
`target_include_directories`
Specifies where the compiler should look for header files for a target:
target_include_directories(target_name
{PUBLIC|PRIVATE|INTERFACE}
directories...)
target_include_directories(mathlib
PUBLIC include/ # headers that users of mathlib need
PRIVATE src/internal/) # headers only used internally by mathlib
Visibility semantics:
PUBLIC: - Applied to mathlib itself during compilation
- Also propagated to any target that links mathlib
PRIVATE: - Applied only to mathlib itself
INTERFACE: - Not applied to mathlib, only propagated to dependents
`target_link_libraries`
Links a target against other libraries:
target_link_libraries(my_program
PRIVATE mathlib # our own static library
PRIVATE m # system math library (libm)
PRIVATE pthread # POSIX threads
PUBLIC some_shared) # propagate this dependency to users
`target_compile_options`
Adds compiler flags to a target:
target_compile_options(myapp PRIVATE
-Wall -Wextra -Wpedantic
$<$<CONFIG:Debug>:-g -O0>
$<$<CONFIG:Release>:-O3>)
`target_compile_definitions`
Adds preprocessor definitions to a target:
target_compile_definitions(myapp PRIVATE
APP_VERSION="1.2.3"
$<$<CONFIG:Debug>:DEBUG_BUILD>
$<$<CONFIG:Release>:NDEBUG>)
`find_package` — External Dependencies
find_package() searches for an installed CMake-compatible library or tool and sets up the required targets/variables:
# Find OpenSSL (will look for FindOpenSSL.cmake or OpenSSLConfig.cmake)
find_package(OpenSSL REQUIRED) # REQUIRED causes error if not found
target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto)
# Find Boost, specific components
find_package(Boost 1.71 REQUIRED COMPONENTS filesystem system)
target_link_libraries(myapp PRIVATE Boost::filesystem Boost::system)
# Optional package
find_package(CUDA)
if(CUDA_FOUND)
target_link_libraries(myapp PRIVATE ${CUDA_LIBRARIES})
endif()
Under the hood, CMake looks for a Find\
Custom Functions
CMake supports defining reusable functions with the function() command:
function(function_name [param1 param2 ...])
# function body
endfunction()
Basic Custom Function
function(add_numbers num1 num2)
math(EXPR sum "${num1} + ${num2}")
message("The sum of ${num1} and ${num2} is ${sum}")
endfunction()
# Call the function
add_numbers(5 7)
# Output: The sum of 5 and 7 is 12
add_numbers(100 200)
# Output: The sum of 100 and 200 is 300
Function with Conditional Logic
function(compare_numbers num1 num2)
if(${num1} EQUAL ${num2})
message("${num1} and ${num2} are equal")
else()
message("${num1} and ${num2} are not equal")
endif()
endfunction()
compare_numbers(5 5) # 5 and 5 are equal
compare_numbers(3 7) # 3 and 7 are not equal
Functions with Return Values via PARENT_SCOPE
Functions cannot return values directly, but can set variables in the parent scope:
function(get_platform_name result_var)
if(WIN32)
set(${result_var} "windows" PARENT_SCOPE)
elseif(APPLE)
set(${result_var} "macos" PARENT_SCOPE)
else()
set(${result_var} "linux" PARENT_SCOPE)
endif()
endfunction()
get_platform_name(PLATFORM)
message("Platform: ${PLATFORM}")
`cmake_parse_arguments` — Advanced Argument Handling
For functions with optional or keyword arguments, use cmake_parse_arguments:
function(create_target)
cmake_parse_arguments(
ARG # prefix for result variables
"STATIC;SHARED;WITH_TESTS" # boolean options (no value)
"NAME;OUTPUT_DIR" # single-value keywords
"SOURCES;INCLUDES" # multi-value keywords
${ARGN} # remaining arguments
)
if(ARG_STATIC)
add_library(${ARG_NAME} STATIC ${ARG_SOURCES})
elseif(ARG_SHARED)
add_library(${ARG_NAME} SHARED ${ARG_SOURCES})
else()
add_executable(${ARG_NAME} ${ARG_SOURCES})
endif()
if(ARG_INCLUDES)
target_include_directories(${ARG_NAME} PRIVATE ${ARG_INCLUDES})
endif()
if(ARG_WITH_TESTS)
message("Tests enabled for ${ARG_NAME}")
endif()
endfunction()
# Usage:
create_target(
NAME my_lib
STATIC
SOURCES src/lib.cpp src/utils.cpp
INCLUDES include/
WITH_TESTS)
`macro` vs `function`
CMake also provides macro() which is similar to function() but with a crucial difference:
function | macro | |
|---|---|---|
| Variable scope | Own scope | Caller's scope |
return() support | Yes | No |
ARGN behavior | Copy of args | Reference to args |
| Recommended for | General use | Simple text substitution |
macro(simple_macro name)
message("Hello from macro: ${name}")
endmacro()
# Prefer functions over macros for most use cases
function(better_alternative name)
message("Hello from function: ${name}")
endfunction()
Complete Example: A Reusable Library Target Function
# A function that creates a well-configured library target
function(add_my_library)
cmake_parse_arguments(LIB "" "NAME;STANDARD" "SOURCES;PUBLIC_HEADERS;DEPS" ${ARGN})
# Create the library
add_library(${LIB_NAME} STATIC ${LIB_SOURCES})
# Set C++ standard
set_target_properties(${LIB_NAME} PROPERTIES
CXX_STANDARD ${LIB_STANDARD}
CXX_STANDARD_REQUIRED ON)
# Public headers
target_include_directories(${LIB_NAME}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
# Dependencies
if(LIB_DEPS)
target_link_libraries(${LIB_NAME} PRIVATE ${LIB_DEPS})
endif()
# Warnings
target_compile_options(${LIB_NAME} PRIVATE -Wall -Wextra)
endfunction()
# Usage:
add_my_library(
NAME mathlib
STANDARD 17
SOURCES src/add.cpp src/multiply.cpp
PUBLIC_HEADERS include/mathlib.h
DEPS fmt::fmt)
Conclusion
CMake's function system — both built-in and custom — provides everything needed to build, link, and configure complex C and C++ projects. Built-in functions like add_library, target_include_directories, target_link_libraries, and find_package cover the common build tasks. string() and related functions handle variable manipulation. Custom function() definitions encapsulate repetitive patterns, making large multi-target CMake projects maintainable.
Mastering CMake functions is the final step toward writing professional-grade build configurations that are readable, reusable, and correct across all target platforms.