BUILD: CMake Functions — Built-in and Custom Functions

CMake functions — both built-in and custom — are the primary mechanism for organizing and reusing build logic. Built-in functions handle variable manipulation, library building, and dependency management. Custom functions encapsulate repetitive patterns into readable, reusable blocks of CMake script.

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:

text
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

cmake
# 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():

cmake
set(myVariable "Hello, world!")
get_variable(myVariable TheValueVariable)
message("${TheValueVariable}")  # Output: Hello, world!

`unset`

Remove a variable from the current scope:

cmake
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:

cmake
# 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

cmake
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:

cmake
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

cmake
# 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:

cmake
set_target_properties(target1 target2 ...
                      PROPERTIES prop1 value1
                                 prop2 value2 ...)

Example — set the C++ standard for a target:

cmake
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:

cmake
target_include_directories(target_name
    {PUBLIC|PRIVATE|INTERFACE}
    directories...)

cmake
target_include_directories(mathlib
    PUBLIC  include/         # headers that users of mathlib need
    PRIVATE src/internal/)   # headers only used internally by mathlib

Visibility semantics:

text
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

Links a target against other libraries:

cmake
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:

cmake
target_compile_options(myapp PRIVATE
    -Wall -Wextra -Wpedantic
    $<$<CONFIG:Debug>:-g -O0>
    $<$<CONFIG:Release>:-O3>)

`target_compile_definitions`

Adds preprocessor definitions to a target:

cmake
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:

cmake
# 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\.cmake module (in CMake's module directory) or a \Config.cmake file (installed by the package itself).


Custom Functions

CMake supports defining reusable functions with the function() command:

cmake
function(function_name [param1 param2 ...])
    # function body
endfunction()

Basic Custom Function

cmake
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

cmake
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:

cmake
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:

cmake
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:

functionmacro
Variable scopeOwn scopeCaller's scope
return() supportYesNo
ARGN behaviorCopy of argsReference to args
Recommended forGeneral useSimple text substitution

cmake
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

cmake
# 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.