0%

Syntax of CMake

This article is the supplementary material of Build using CMake.

Command Invocations

A CMake source file is just a sequence of Command Invocations. A command invocation is an identifier followed by paren-enclosed arguments. Arguments are seperated by space. For examlpe:

1
add_executable(hello world.c)

Argument

There are three types of arguments. Brackets and Qouted Arguments are used for constructing long, usually multi-line arguments, typically, the text of message.

Bracket Argument

This syntax is inspired by the long bracket syntax of Lua. The format of open bracket is '[' '='* '[' whereas the format of close bracket is ']' '='* ']'. The = could repeat zero or multiple times; The bracket with same number of = will be paired together.

All text between the paired brackets will be treated as exactly one bracket argument. No nest, No Evaluation will be performed. For example:

1
2
3
4
5
message([=[
No \-escape sequences or ${variable} references are evaluated.
This is always one argument even though it contains a ; character.
The text does not end on a closing bracket of length 0 like ]].
]=])

Quoted Argument

A quoted argument is all the text between a pair of double-quote characters. Different from bracket argument, both Escape Sequences and Variable References will be evaluated here. The line continuation character \ is optional here. For example:

1
2
3
4
5
message("
This is always one argument even though it contains a ; character. \
Both \\-escape sequences and ${variable} references are evaluated.
The text does not end on an escaped double-quote like \".
")

Unquoted Argument

An unquoted argument is not enclosed by any quoting syntax. It may not contain any whitespace, (, ), #, ", or \ except when escaped by a backslash. Both Escape Sequences and Variable References are evaluated. An unquoted argument may be treated as zero or a list arguments that splitted by ; (not \;), which is same as List. For example:

1
2
3
4
5
6
7
8
foreach(arg
NoSpace
Escaped\ Space
This;Divides;Into;Five;Arguments
Escaped\;Semicolon
)
message("${arg}")
endforeach()

The output will be

1
2
3
4
5
6
7
8
NoSpace
Escaped Space
This
Divides
Into
Five
Arguments
Escaped;Semicolon

Lists

A list of elements is represented as a string by concatenating the elements separated by ;. For example, the set() command stores multiple values into the destination variable as a list:

1
set(srcs a.c b.c c.c) # sets "srcs" to "a.c;b.c;c.c"

Lists are meant for simple use cases such as a list of source files and should not be used for complex data processing tasks. Most commands that construct lists do not escape ; characters in list elements, thus flattening nested lists:

1
set(x a "b;c") # sets "x" to "a;b;c", not "a;b\;c"

Variable

Variables are the basic unit of storage in the CMake Language. They are always of string type, though some commands may interpret them as values of other types. Variable are case-sensitive; It's recommended to use alphanumeric characters with _ and - only.

Reserved Words

  1. Begin with CMAKE_ or _CMAKE_ (upper-, lower-, or mixed-case)
  2. Begin with _ followed by the name of any CMake Command.

Please visit https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#manual:cmake-commands(7) for the list of cmake commands.

Set and Unset

Signatures <value>... expect zero or more arguments. Multiple arguments will be joined as a semicolon-separated list to form the actual variable value to be set. Zero arguments will cause normal variables to be unset.

Set Normal Variable

1
set(<variable> <value>... [PARENT_SCOPE])

If the PARENT_SCOPE option is given, then the variable will be set in the scope above the current scope.

Set Cache Entry

1
set(<variable> <value>... CACHE <type> <docstring> [FORCE])

Sets the given cache (cache entry). Since cache entries are meant to provide user-settable values this does not overwrite existing cache entries by default. Use the FORCE option to overwrite existing entries.

  • Type should be one of the following:
    • BOOL: Boolean ON/OFF value. cmake-gui offers a checkbox.
    • FILEPATH: Path to a file on disk. cmake-gui offers a file dialog.
    • PATH: Path to a directory on disk. cmake-gui offers a file dialog.
    • STRING: A line of text. cmake-gui offers a text field or a drop-down selection if the STRINGS cache entry property has been set.
    • INTERNAL A line of text. cmake-gui does not show internal entries. They may be used to store variables persistently across runs. Use of this type implies FORCE.
  • <docstring> should be a line of text providing a quick summary of the option for presentation to cmake-gui users.
  • FORCE should be used when it's required to overwrite the value of this entry. Since cache entries are meant to provide user-settable values, this does not overwrite existing cache entries by default.

if the Type is PATH or FILEPATH and the value provided is a relative path, the set command will treat the path as relative to the current working directory and convert it to an absolute path.

Set Environment Variable

1
set(ENV{<variable>} [<value>])

If more than one arguments passed, extras will be ignored and an author warning is issued.

Unset Variables

1
2
unset(<variable> [CACHE | PARENT_SCOPE])
unset(ENV{<variable>})

Scope

Variables have dynamic scope. Each variable set or unset creates a binding in the corresponding scope.

Each new directory or function creates a new scope. When evaluating the variable references, CMake first searches the function call stack: if a set binding is found, use it; Else if no binding or unset binding is found, searches the current directory scope, and the cache scope respectively. If there's no binding in all scopes, CMake evaluates it as an empty string.

Function Scope

A variable set or unset binds in function is visible only within the current and nested functions.

Directory Scope

Each directory in the source tree has its own variable bindings. Before processing the CMakeLists.txt file for the directory, CMake copies all variable bindings defined in the parent directory, and initialize a new directory scope.

A variable set or unset outside the functions binds to the current directory scope.

Persistent Cache

CMake stores a separate set of cache variables that persist across multiple runs within a project build tree. Cache entries have an isolated binding scope and coule be modified only by explicit request.

Variable References

Normal Variables

A variable reference has the form ${<variable>}; When it appears inside a Quoted Argument or an Unquoted Argument, it is evaluated and replaced by the value of the variable, or by an empty string if it is not set. Variable references can nest and are evaluated from the inside out. For example

1
2
3
set(inner_var foo)
set(outer_foo_var 999)
message("nest variable value is :${outer_${inner_var}_var}")

The output will be

1
nest variable value is :999

The if() command has a special condition syntax that allows for variable references in the short form <variable> instead of ${<variable>}. However, environment and cache variables always need to be referenced as $ENV{<variable>} or $CACHE{<variable>}.

Environment Variable Reference

Environment variable references have the form $ENV{<variable>}. Environment variables have global scope in a CMake process and never cached. set() and unset() commands only temporarily affect the running CMake process, without altering the actual system environment, the process called from current process or the environment of subsequent build or test process.

Cache Variable Reference

A cache variable reference has the form $CACHE{<variable>}.

Control Structures

Conditional Blocks

1
2
3
4
5
6
7
if(<condition>)
<commands>
elseif(<condition>)
<commands>
else()
<commands>
endif()

Conditions

Type of Test Identifier
Unary EXISTS, IS_DIRECTORY, IS_SYMLINK, IS_ABSOLUTE, COMMAND
Binary EQUAL, LESS, LESS_EQUAL, GREATER, GREATER_EQUAL,
STREQUAL, STRLESS, STRLESS_EQUAL, STRGREATER, STRGREATER_EQUAL,
VERSION_EQUAL, VERSION_LESS, VERSION_LESS_EQUAL, VERSION_GREATER, VERSION_GREATER_EQUAL,
MATCHES
Boolean NOT, AND, OR

Behaviors of path cheking are well-defined only for full path.

Other useful tests:

  • if(TARGET target-name) to check if the given name is an existing logical target name created by add_executable(), add_library(), or add_custom_target()
  • if(TEST test-name) to check if the given name is an existing test name created by the add_test().
  • if(file1 IS_NEWER_THAN file2) returns True if file1 is newer than file2 or if one of the two files doesn’t exist.
  • if(<variable|string> IN_LIST <variable>) to check if the given element is contained in the named list variable.
  • if(DEFINED <name>|CACHE{<name>}|ENV{<name>}) to check if the variable is defined.

Variable Expansion

Normal variable evaluation with ${} applies before the if command even receives the arguments; Therefore, within condition statements that accepting <variable|string>, automatic evaluation is applied. Developers is instead excepted to pass the variable directly without ${} enclosed.

Loops

break(), continue()

For Each

1
2
3
foreach(<loop_var> <items>)
<commands>
endforeach()

<items> is a list of items that are separated by ; or . At the beginning of each iteration, loop_var will be set to the value of the current item.

Variants

  1. Iterate from 0 or start if set to stop (inclusive), with step:

    1
    foreach(<loop_var> RANGE [<start>] <stop> [<step>])

  2. Iterate over a list of items.

    1
    foreach(<loop_var> IN LISTS [<lists>])
    <lists> is a whitespace or semicolon separated list of list-valued variables. For example,
    1
    2
    3
    4
    5
    6
    7
    8
    set(A 0;1)
    set(B 2 3)
    set(C "4 5")
    set(D 6;7 8)
    set(E "")
    foreach(X IN LISTS A B C D E)
    message(STATUS "${X}")
    endforeach()
    This will print numbers from 1 to 8.

  3. Iterate over multiple lists simultaneously
    1
    foreach(<loop_var>... IN ZIP_LISTS <lists>)
    <lists> is also a whitespace or semicolon separated list of list-valued variables.
    • If only one loop_var given, then access the values via loop_var_N correspondingly;
    • If multiple variable names passed, the number of variable and the number of lists should match;
    • If any of the lists are shorter, the corresponding iteration variable become undefined.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      list(APPEND English one two three four)
      list(APPEND Bahasa satu dua tiga)

      foreach(num IN ZIP_LISTS English Bahasa)
      message(STATUS "${num_0}, ${num_1}")
      endforeach()

      foreach(en ba IN ZIP_LISTS English Bahasa)
      message(STATUS "${en}, ${ba}")
      endforeach()
      Both yield
      1
      2
      3
      4
      -- one, satu
      -- two, dua
      -- three, tiga
      -- four,

While

1
2
3
while(<condition>)
<commands>
endwhile()

Function and Macro

macro is very similar to function. They are defined by

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

and

1
2
3
macro(<name> [<arg1> ...])
<commands>
endmacro()
  • The name of functions or marcros are case-insensitive.
  • They could be invoked via <identifier>([args]) or cmake_language(CALL <identifier>)
  • When a function or marcro is invoked, CMake first evaluates (${arg1}, ...) with the arguments passed, and then invoked them as normal commands.
  • For each invoking, there are some implicit variables available:
    • ARGC: the number of arguments.
    • ARGV holds the list of all arguments passed to the function; Alternatively, the arguments could be accessed via ARGV0, ARGV1 ...
  • In a function, ARGC, ARGV and ARGV0 are true variables; In a macro, they are not, they are string replacements.
  • A function is executed by transferring control from the calling statement to the function body. A macro is executed as if the macro body were pasted in place of the calling statement. This has the consequence that a return() in a macro body does not just terminate execution of the macro but also the control from the scope of the macro call.

Comments

Bracket Comment

Bracket Comments adopt the same syntax as bracket argument, with a # immediately ahead. It's helpful to construct multi-line comment or inline comments that terminated in the middle and followed by other arguments.

1
2
3
#[[This is a bracket comment.
It runs until the close bracket.]]
message("First Argument\n" #[[Bracket Comment]] "Second Argument")

The output will be

1
2
First Argument
Second Argument

Line Comment

Line comments are the content between # and the end of the line.