Common Syntax in CMake (Functions)

Previous exciting content:CMake Hello, WorldCMake VariablesCMake Official Tutorial (Basic Project Setup)CMake Official Tutorial (Creating Libraries)CMake Official Tutorial (Usage Requirements)CMake Official Tutorial (Installation and Testing)CMake Common Syntax (if Statements)CMake Common Syntax (Cache Variables)CMake Common Syntax (Environment Variables)CMake Common Syntax (Mathematical Calculations)CMake Common Syntax (Strings)CMake Common Syntax (Lists)CMake Common Syntax (Loops)CMake Common Syntax (Macros)

Functions in CMake are used to define reusable code blocks, enabling logic reuse through parameter passing.

When executed, a function creates an independent scope, and variables are by default only visible within the function (unless explicitly passed using PARENT_SCOPE).

Definition

The syntax for defining a function is as follows:

function(<name> [<arg1> ...])
  <commands>
endfunction()

Where<span><name></span> is the function name, and<span><arg1> ...</span> is the list of parameters.

A simple example is as follows:

# Define
function(print_message msg)
    message("Message: ${msg}")
endfunction()
# Call
print_message("Hello CMake!")

Parameters

Function parameters can be passed in the following ways:

  1. 1. Positional parameters: The function receives values in the order defined by the parameters
  2. 2. Variable parameters: Using ARGN to receive extra parameters during function calls, suitable for scenarios with a variable number of parameters
  3. 3. Keyword parameters: Using<span>cmake_parse_arguments</span> to parse complex formatted parameters, which was previously introduced in CMake Common Syntax (Macros) (detailed usage will not be elaborated here)

Examples are as follows:

# Positional parameters
function(add a b)
    math(EXPR result "${a} + ${b}")
    message("Sum: ${result}")
endfunction()
add(35)  # Sum: 8

# Variable parameters
# Once N parameters are explicitly defined, at least N parameters must be provided during the call, otherwise configuration fails
# Therefore, calling print_all requires at least one parameter
function(print_all first_arg)
    message("First: ${first_arg}")
    message("Others: ${ARGN}")  # Receive remaining parameters
endfunction()
print_all("A" "B" "C")  # First: A Others: B;C

# Keyword parameters
function(configure)
    set(options VERBOSE)                 # Flag-type parameter (no value)
    set(oneValueArgs NAME VERSION)       # Single-value parameters
    set(multiValueArgs FILES)            # Multi-value parameters
    cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
    if(ARG_VERBOSE)
        message("Verbose mode enabled")
    endif()
    message("Project: ${ARG_NAME} v${ARG_VERSION}, Files: ${ARG_FILES}")
endfunction()
configure(NAME Demo VERSION 1.0 FILES main.cpp util.cpp --VERBOSE)

Return Values

CMake functions do not have explicit return value syntax; generally, results can be passed through output parameters (PARENT_SCOPE):

function(calculate_sum a b output_var)
    math(EXPR sum "${a} + ${b}")
    # PARENT_SCOPE ensures that the variable modified is in the parent scope, not the current function scope
    set(${output_var} ${sum} PARENT_SCOPE) 
endfunction()
calculate_sum(3 5 RESULT)
message("Sum: ${RESULT}")  # Output Sum: 8

An incorrect implementation is as follows:

function(calculate_sum a b output_var)
    math(EXPR sum "${a} + ${b}")
    # ❌ Incorrect implementation
    set(output_var ${sum} PARENT_SCOPE) 
endfunction()
# Call the function
calculate_sum(3 5 RESULT)
message("Sum: ${RESULT}")     # Output empty value, because the variable output_var in the parent scope is actually modified
message("Sum: ${output_var}") # Output Sum: 8

In CMake functions, the parameter<span>output_var</span> receives thevariable name passed by the caller (e.g.,<span>RESULT</span>), not the variable itself.

If<span>output_var</span> is directly used as the target variable name for<span>set()</span><span>, the actual modification is to the variable named</span><code><span>output_var</span> in the parent scope, not the expected<span>RESULT</span> variable.

However, in<span>set(${output_var} ...)</span><span>, the</span><code><span>${output_var}</span> will be replaced with the variable name passed during the call (such as<span>RESULT</span>), thus correctly modifying the variable in the parent scope.

Scope

The scope of functions in CMake is global; once defined at a certain location, all subsequent code (including parent or child directories) can call it,provided it is defined before the call.

For variables, those defined within a function are only valid within that function’s scope by default. If you need to access a function’s internal variable from the parent scope, you must export it to the parent scope using PARENT_SCOPE:

function(my_func)
    set(local_var "hello" PARENT_SCOPE)  # Pass to parent scope
endfunction()

my_func()
message("${local_var}")  # "hello"

Similarly, if you need to change a variable in the parent scope from within a function, you must export it to the parent scope using PARENT_SCOPE; otherwise, the modification will not take effect:

set(var1 "var1 in parent")
set(var2 "var2 in parent")

function(test)
    # Cannot modify parent scope variable; this call effectively re-creates a local variable
    set(var1 "var1 in func")
    # Only by using PARENT_SCOPE can you change the parent scope variable
    set(var2 "var2 in func" PARENT_SCOPE)
endfunction()

test()
message("${var1}") # var1 in parent
message("${var2}") # var2 in func

If a function’s internal variable and a parent scope variable share the same name, the internal variable will shadow the parent scope variable. To modify the parent scope variable, you must explicitly use PARENT_SCOPE:

set(arg1 "arg1 in parent")

function(test arg1)
  message("${arg1}")
  set(arg1 "arg1 in func" PARENT_SCOPE)
endfunction()

test("hello")
message("${arg1}")

Output:

hello
arg1 in func

Leave a Comment