CMake supports both functions and macros to provide a named abstraction for some repetitive works. A function or macro always define a new command.

Functions

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

where name is the name of the function, with arguments arg1, arg2, etc.

Example

function(foo)
    message("I am foo!!")
endfunction()

foo()
FOO()
fOO()
Foo()
I am foo!! 
I am foo!! 
I am foo!!
I am foo!!

The function name is case-insensitive. You can call in any case, but it’s always recommended to use the same name declared in the function definition.

Function Arguments

A cmake function can take two types of arguments.

  • named or keyword arguments
  • optional arguments

Named arguments are mandatory and will throw error if not provided. You don’t need a comma between argument names.

function(fooKeyword country city)
    message("Address: ${country} ${city}")
endfunction()

fooKeyword("germany" "munich")

Output

Address: germany munich

Optional arguments can be accessed using some predefined variables.

  • ARGC : Total number of arguments(named arguments + optional arguments)
  • ARGV : list of variables containing both named and optional arguments
  • ARGN : list of variables containing only optional arguments
function(foo name1 name2)
    message("Argument count: ${ARGC}")
    message("all arguments: ${ARGV}")
    message("optional arguments: ${ARGN}")
endfunction()

foo("asit" "dhal" "India" "Bhubaneswar")
message("only named arguments")
foo("harry" "potter")
# foo() #error

Output

Argument count: 4
all arguments: asit;dhal;India;Bhubaneswar
optional arguments: India;Bhubaneswar
only named arguments
Argument count: 2
all arguments: harry;potter
optional arguments:

Other than those three variables, CMake also provides ARGV0, ARGV1, ARGV2, … which will have the actual values of the arguments passed in. Referencing to ARGV# arguments beyond ARGC will have undefined behavior.

function(foo name1 name2)
    if (DEFINED ARGV0)
        message("ARGV0 defined value=${ARGV0}")
    else()
        message("ARGV0 not defined")
    endif()

    math(EXPR lastIndex "${ARGC}-1")
    foreach(index RANGE 0 ${lastIndex})
        message("arguments at index ${index}: ${ARGV${index}}")
    endforeach()
endfunction()

foo("asit" "dhal" "India" "Bhubaneswar")

Output

ARGV0 defined value=asit
arguments at index 0: asit
arguments at index 1: dhal
arguments at index 2: India
arguments at index 3: Bhubaneswar

Usually, named arguments are accessed using the variable and optional arguments are accessed using ARGN.

function(foo firstName lastName)
    message("first name: ${firstName}")
    message("last name: ${lastName}")

    foreach(arg IN LISTS ARGN)
        message("address parts: ${arg}")
    endforeach()

endfunction()

foo("asit" "dhal" "India" "Bhubaneswar")

Output

first name: asit
last name: dhal
address parts: India
address parts: Bhubaneswar

Variable Scope

CMake functions introduce new scopes, a variable modified inside the function won’t be accessible outside the function. Another problem in CMake, the functions don’t return any value. This will make the function difficult to use. So, CMake provides the keyword PARENT_SCOPE to set a variable in the parent scope.

You can send variable name as a function parameter. The function will set the variable in the parent scope.

set(name "Uttam Mohanty")

function(set_name varName )
    set(${varName} "Sriram Panda" PARENT_SCOPE)
endfunction()

message("before call first name: ${name}")
set_name(name)
message("after call first name: ${name}")

Output

before call first name: Uttam Mohanty
after call first name: Sriram Panda

This way of returning value from function is self documenting. But, it becomes repetitive if the function has to return many values. So, many library functions explicitly document well known variables which are set by a function and you don’t need to send any variable name as argument.

In some projects, it’s to common to have functions which set project specific variables in the parent scope. The cmake libraries provide FindPackageHandleStandardArgs function which sets a variable called Package_FOUND in the parent scope.

find_package(Git)
message("Git found: ${GIT_FOUND}")
if(Git_FOUND)
  message("Git executable: ${GIT_EXECUTABLE}")
  message("Git version: ${GIT_VERSION_STRING}")
endif()

Output

Git found: TRUE
Git executable: /usr/bin/git
Git version: 2.17.1

return() command

function(early_ret)
    message("one")
    return()
    message("two") # won't be printed
endfunction()

early_ret() 

You can call return() to exit a function early.

Macros

macro(<name> [<arg1> ...])
  <commands>
endmacro()

where name is the name of the macro, with arguments arg1, arg2, etc.

macro(foo_macro)
    message("I am a poor little macro!!")
endmacro()

foo_macro()
fOO_macro()
Foo_Macro()
FOO_MACRO()

Output

I am a poor little macro!!
I am a poor little macro!!
I am a poor little macro!!
I am a poor little macro!!

Like functions, macro names are also case insensitive. You can call in any case, but it’s always recommended to use the same name declared in the macro definition.

Macro Arguments

Like functions, macros also take both named and positional arguments.

macro(foo_macro name1 name2)
    message("Argument count: ${ARGC}")
    message("all arguments: ${ARGV}")
    message("optional arguments: ${ARGN}")
endmacro()

foo_macro("asit" "dhal" "India" "Bhubaneswar")
message("only named arguments")
foo_macro("harry" "potter")
#foo_macro() #error

Output

Argument count: 4
all arguments: asit;dhal;India;Bhubaneswar
optional arguments: India;Bhubaneswar
only named arguments
Argument count: 2
all arguments: harry;potter
optional arguments: 

Functions always introduce a new scope when called. In case of macro, the macro call is substituted with the macro body and arguments are replaced using string substitution. No new scope is created. So both functions and macros behave differently in some cases.

Using the DEFINED keyword, you can check if a variable, cache variable or environment variable with given is defined. The value of the variable does not matter.

macro(foo_macro name)
    if (DEFINED name)
        message("macro argument name is defined")
    else()
        message("macro argument name is not defined")
    endif()
endmacro()

function(foo_func name)
    if (DEFINED name)
        message("func argument name is defined")
    else()
        message("func argument name is not defined")
    endif()
endfunction()

foo_macro("asit")
foo_func("asit")

Output

macro argument name is not defined
func argument name is defined

Macros show strange behavior while using the three special variables(in some cases).

Variable Scope

Unlike functions, macro introduce no new scope. The variables declared inside the macro(other than the arguments) will be available after the call.

set(name "asit")
macro(foo_macro macro_arg1)
    message("macro arg: ${macro_arg1}")
    message("macro name: ${name}")
    set(middle_name "kumar")
    set(name "ASIT")
    message("micro middle name: ${middle_name}")
endmacro()

foo_macro("bhubaneswar")
message("after macro call name: ${name}")
message("after micro call middle name: ${middle_name}")

Output

macro arg: bhubaneswar
macro name: asit
micro middle name: kumar
after macro call name: ASIT
after micro call middle name: kumar

return() command

Since, micro doesn’t create any new scope, return() will exit the current scope.

macro(early_ret)
    message("one")
    return()
    message("two") # won't be printed
endmacro()

message("before")
early_ret()
message("will never be executed")

Output

before
one

Redefining Functions and Macros

When function() or macro() is defined and if a command already exists with that name, the previous command will be overridden. The previous command can be accessed using the name and an underscore prepended.

function (good_func)
    message("first definition of good_func")
endfunction()

function (good_func)
    message("second definition of good_func")
endfunction()

good_func()
_good_func()

Output

second definition of good_func
first definition of good_func

If the same function is redefined again, the underscore version will call previously defined function. The original function will never be available.

function (good_func)
    message("first definition of good_func")
endfunction()

function (good_func)
    message("second definition of good_func")
endfunction()

function (good_func)
    message("third definition of good_func")
endfunction()

good_func()
_good_func()

Output

third definition of good_func
second definition of good_func

It has two problems.

  • If a developer writes a function without knowing that the name is already used for some cmake library functions, then there are chances that the original function will be hidden forever.
  • There are some developers who use this feature(or a bug) to add behavior to an existing function. If you do it more than twice, it can cause infinite recursion. It’s difficult to debug when the function definition spans across many modules.
function (good_func)
    message("first definition of good_func")
endfunction()

function (good_func) # second redefinition
    message("second definition of good_func")
    _good_func()
endfunction()

function (good_func) 
    message("third definition of good_func")
    _good_func()
endfunction()

good_func()

Output

second definition of good_func
second definition of good_func
second definition of good_func
second definition of good_func
......
....

The desired behavior is that 2nd redefinition will call the original function. But, in this scope, the 2nd redefinition is always the underscore version.

CMakeParseArguments

CMake has a predefined command to parse function and macro arguments. This command is for use in macros or functions. It processes the arguments given to that macro or function, and defines a set of variables which hold the values of the respective options.

cmake_parse_arguments(<prefix> <options> <one_value_keywords> <multi_value_keywords> <args>...)

cmake_parse_arguments(PARSE_ARGV <N> <prefix> <options> <one_value_keywords> <multi_value_keywords>)
  • The first version can be used both in functions and macros. The 2nd version(PARSE_ARGV) can only be used in functions. In this case the arguments that are parsed come from the ARGV# variables of the calling function. The parsing starts with the <N>th argument, where <N> is an unsigned integer. This allows for the values to have special characters like ; in them.

  • prefix: a prefix string which will preceed all variable names.

  • options: all options for the respective macro/function, i.e. keywords which can be used when calling the macro without any value following. These are the variables if passed will be set as TRUE, else FALSE.

  • one_value_keywords: all keywords for this macro or funtion which are followed by one value, e.g. DESTINATION=/usr/lib

  • multi_value_keywords: all keywords for this macro/function which can be followed by more than one value, like e.g. FILES=test.cpp;main.cpp

  • All remaining arguments are collected in a variable <prefix>_UNPARSED_ARGUMENTS that will be undefined if all arguments were recognized. This can be checked afterwards to see whether your macro was called with unrecognized parameters.

function(demo_func)

    set(prefix DEMO)
    set(flags IS_ASCII IS_UNICODE)
    set(singleValues TARGET)
    set(multiValues SOURCES RES)

    include(CMakeParseArguments)
    cmake_parse_arguments(${prefix}
                     "${flags}"
                     "${singleValues}"
                     "${multiValues}"
                    ${ARGN})
    message(" DEMO_IS_ASCII: ${DEMO_IS_ASCII}")
    message(" DEMO_IS_UNICODE: ${DEMO_IS_UNICODE}")
    message(" DEMO_TARGET: ${DEMO_TARGET}")
    message(" DEMO_SOURCES: ${DEMO_SOURCES}")
    message(" DEMO_RES: ${DEMO_RES}")
    message(" DEMO_UNPARSED_ARGUMENTS: ${DEMO_UNPARSED_ARGUMENTS}")
endfunction()

message("test 1")
demo_func(SOURCES test.cpp main.cpp TARGET mainApp IS_ASCII config DEBUG)
message("test 2")
demo_func(TARGET mainApp.so RES test.png cat.png bull.png IS_UNICODE config RELEASE)

Output

test 1
 DEMO_IS_ASCII: TRUE
 DEMO_IS_UNICODE: FALSE
 DEMO_TARGET: mainApp
 DEMO_SOURCES: test.cpp;main.cpp
 DEMO_RES: 
 DEMO_UNPARSED_ARGUMENTS: config;DEBUG
test 2
 DEMO_IS_ASCII: FALSE
 DEMO_IS_UNICODE: TRUE
 DEMO_TARGET: mainApp.so
 DEMO_SOURCES: 
 DEMO_RES: test.png;cat.png;bull.png
 DEMO_UNPARSED_ARGUMENTS: config;RELEASE

The above example with PARSE_ARGV version

function(demo_func project_name project_type)
    set(prefix DEMO)
    set(flags IS_ASCII IS_UNICODE)
    set(singleValues TARGET)
    set(multiValues SOURCES RES)

    include(CMakeParseArguments)

    cmake_parse_arguments(PARSE_ARGV 2 ${prefix}
                     "${flags}"
                     "${singleValues}"
                     "${multiValues}")

    message(" DEMO_IS_ASCII: ${DEMO_IS_ASCII}")
    message(" DEMO_IS_UNICODE: ${DEMO_IS_UNICODE}")
    message(" DEMO_TARGET: ${DEMO_TARGET}")
    message(" DEMO_SOURCES: ${DEMO_SOURCES}")
    message(" DEMO_RES: ${DEMO_RES}")

    message(" named arg project_name: ${project_name}")
    message(" named arg project_type: ${project_type}")

endfunction()

message("test 1")
demo_func("main_gen" "gen" SOURCES test.cpp main.cpp TARGET mainApp IS_ASCII)
message("test 2")
demo_func("main_test" "unit test" TARGET mainApp.so RES test.png cat.png bull.png IS_UNICODE)

Output

test 1
 DEMO_IS_ASCII: TRUE
 DEMO_IS_UNICODE: FALSE
 DEMO_TARGET: mainApp
 DEMO_SOURCES: test.cpp;main.cpp
 DEMO_RES: 
 named arg project_name: main_gen
 named arg project_type: gen
test 2
 DEMO_IS_ASCII: FALSE
 DEMO_IS_UNICODE: TRUE
 DEMO_TARGET: mainApp.so
 DEMO_SOURCES: 
 DEMO_RES: test.png;cat.png;bull.png
 named arg project_name: main_test
 named arg project_type: unit test