The complete guide to CMake. Part One: Syntax
- From the sandbox
- Tutorial
Introduction
CMake is an open and cross-platform toolkit designed to automate the testing, compilation, and packaging of C / C ++ projects. Once you have written a small script that everyone understands, you will ensure the same build of your project on any platforms where CMake is available.
The CMake language , when translated to a native build file (for example, Makefile or Ninja), defines the process of the entire project management. At your disposal, from the functional side, there are only commands that can be formed into rather complex structures. With them we begin.
Launch CMake
Below are examples of using the CMake language that you should practice using. Experiment with the source code, changing existing commands and adding new ones. To run these examples, install CMake from the official site .
Teams
Commands in CMake are similar to functions in many programming languages. To call a command, you need to write its name, and then pass arguments surrounded by parentheses to it, separated by space characters. In the given example, the command is message
passed six arguments for output to the console:
# Напечатает в консоль "CMake is the most powerful buildsystem!"message("CMake ""is ""the ""most ""powerful ""buildsystem!")
Arguments
Arguments, framed in double quotes, allow you to inside yourself to make the screening and substitution of variables. Frameless arguments do not allow such things to be done and cannot include characters ()#"\
and spaces, but are more convenient to use. Example:
# Напечатает "Hello, my lovely CMake", один таб и "!":message("Hello, my lovely CMake\t!")
# Напечатает "Hello,_my_lovely_CMake!" без пробелов:message(Hello,_my_lovely_CMake!)
It is worth noting that the argument Walk;around;the;forest
expands to a list Walk around the forest
, since any unwrapped argument automatically expands to a list of values (provided that the values of the original argument are separated by semicolons), but with an argument framed in double quotes, such transformation does not occur (semicolon characters just disappear). This feature was mentioned in the comments.
Comments
Comments begin with a pound sign and end at the end of the line where they were printed. Text enclosed in comments is ignored by the build system and has no effect on its work. The examples above also demonstrate the use of comments.
Variables
Variables can be defined by calling a command set
, and deleted by calling unset
. You can get the value of a variable by design ${VARIABLE}
. If the variable is not yet defined and somewhere it was necessary to get its value, then this variable will turn into an empty string. Example:
# Определить переменную VARIABLE со значением "Mr. Thomas":set(VARIABLE "Mr. Thomas")
# Напечает "His name is: Mr. Thomas":message("His name is: "${VARIABLE})
# Напечатает "'BINGO' is equal to: []", так как "BINGO" не определена:message("'BINGO' is equal to: [${BINGO}]")
# Удалить переменную VARIABLE:unset(VARIABLE)
Options
CMake supports setting options to be modified by users. Options look like variables and are set by a command option
that takes only three arguments: the variable name, the string description of the variable, and the default value of the variable ( ON
or OFF
):
# Задать опцию `USE_ANOTHER_LIBRARY` с описанием# "Do you want to use an another library?" и значением "OFF":option(USE_ANOTHER_LIBRARY "Do you want to use an another library?"OFF)
Boolean expressions
Before proceeding to the study of conditional operators and cyclic constructions, it is necessary to understand the operation of logical expressions. Logical expressions are used when checking conditions and can take one of two values: true or false. For example, the expression 52 LESS 58
will turn into the truth, since 52 <58. The expression 88 EQUAL 88
will turn into the truth, 63 GREATER 104
will turn into a lie. You can compare not only numbers, but also strings, versions, files, belonging to the list and regular expressions. A complete list of logical expressions can be found here .
Conditional statements
Conditional operators in CMake work exactly like other programming languages. In this example, only the first conditional operator will work, which checks that 5> 1. The second and third conditions are false, since 5 cannot be less than or equal to one. Blocks of commands elseif
and else
optional, but endif
mandatory and signals the completion of previous checks.
# Напечатает "Of course, 5 > 1!":if(5GREATER1)
message("Of course, 5 > 1!")
elseif(5LESS1)
message("Oh no, 5 < 1!")
else()
message("Oh my god, 5 == 1!")
endif()
Cycles
Cycles in CMake are similar to cycles in other programming languages. In the given example, the value of the variable is set VARIABLE
to Airport
, and then the four nested commands are executed sequentially until the value of the variable VARIABLE
is equal Airport
. The last fourth command set(VARIABLE "Police station")
sets the value of the variable being checked in Police station
, so the loop will immediately stop before reaching the second iteration. The command endwhile
signals the completion of the list of nested commands.
# Напечатает в консоль три раза "VARIABLE is still 'Airport'":set(VARIABLE Airport)
while(${VARIABLE}STREQUAL Airport)
message("VARIABLE is still '${VARIABLE}'")
message("VARIABLE is still '${VARIABLE}'")
message("VARIABLE is still '${VARIABLE}'")
set(VARIABLE "Police station")
endwhile()
This cycle example foreach
works as follows: at each iteration of a given cycle, the variable is VARIABLE
assigned the next value from the list Give me the sugar please!
, and then the command is executed message(${VARIABLE})
that displays the current value of the variable VARIABLE
. When there are no values in the list, the loop ends its execution. The command endforeach
signals the completion of the list of nested commands.
# Напечатает "Give me the sugar please!" с новых строк:foreach(VARIABLE Give me the sugar please!)
message(${VARIABLE})
endforeach()
There are 3 more forms of record cycle foreach
. The first cycle in this example generates integers from 0 to 10 in place of the list, the second cycle generates in the range from 3 to 15, and the third cycle works in a segment from 50 to 90, but in increments of 10.
# Напечатает "0 1 2 3 4 5 6 7 8 9 10" с новых строк:foreach(VARIABLE RANGE 10)
message(${VARIABLE})
endforeach()
# Напечатает "3 4 5 6 7 8 9 10 11 12 13 14 15" с новых строк:foreach(VARIABLE RANGE 315)
message(${VARIABLE})
endforeach()
# Напечатает "50 60 70 80 90" с новых строк:foreach(VARIABLE RANGE 509010)
message(${VARIABLE})
endforeach()
Functions and Macros
The CMake syntax allows you to define your own commands that can be called exactly as built-in commands. The following example demonstrates the use of functions and macros: a function and a macro are first defined with their own commands, and when they are called, their commands are executed sequentially.
# Определение функции "print_numbers":function(print_numbers NUM1 NUM2 NUM3)
message(${NUM1}" "${NUM2}" "${NUM3})
endfunction()
# Определение макроса "print_words":macro(print_words WORD1 WORD2 WORD3)
message(${WORD1}" "${WORD2}" "${WORD3})
endmacro()
# Вызов функции "print_numbers", которая напечатает "12 89 225":
print_numbers(1289225)
# Вызов макроса "print_words", который напечатает "Hey Hello Goodbye":
print_words(Hey Hello Goodbye)
The command of the function
first argument takes the name of the future function, and the remaining arguments are the names of the parameters, which you can work with as with ordinary variables. The parameters are visible only to the function being defined, which means we cannot get access to its parameters outside the function. Moreover, all other variables defined and redefined within the function are visible only to it itself.
Macros are similar to functions with the exception that they do not have their own scope: all variables inside macros are treated as global. You can read more about the differences between macros and functions here .
As noted in the comments, the macros in CMake are similar to macros in the C preprocessor: if you put a command in the macro body return
, then the output from the calling function (or the entire script) will occur, as this example demonstrates:
# Определить макрос, содержащий команду выхода:macro(demonstrate_macro)
return()
endmacro()
# Определить функцию, вызывающую предыдущий макрос:function(demonstrate_func)
demonstrate_macro()
message("The function was invoked!")
endfunction()
# Напечатает "Something happened with the function!"
demonstrate_func()
message("Something happened with the function!")
In the example above, the function demonstrate_func
will not have time to print the message The function was invoked!
, as before, the demonstrate_macro
exit command will be substituted for the place where the macro was called .
Argument analysis
As noted in the comments, the Powerful mechanism cmake_parse_arguments
allows the analysis of arguments passed to a function or macro.
This command accepts a prefix used in defining variables (see the next paragraph), a list of options used without subsequent values, a list of keywords, followed by a single value, a list of keywords, followed by sets of values, and a list of all values passed to the function or macro.
The work of the argument analysis mechanism is to convert the received arguments into variable values. Thus, the considered command for each option and keyword defines its own view variable <Prefix>_<OptionOrKeyword>
encapsulating a certain value. For options, these are boolean values (true — the option is specified, otherwise, false), and for keywords — all passed values located after them.
The function custom_function
contains the command call cmake_parse_arguments
, and then the command to print the values of certain variables. Further, the function is called with arguments LOW NUMBER 30 COLORS red green blue
, and then the screen is printed:
function(custom_function)
# Вызвать механизм обработки аргументов для текущей функции:cmake_parse_arguments(CUSTOM_FUNCTION "LOW;HIGH""NUMBER""COLORS"${ARGV})
# Напечатает "'LOW' = [TRUE]":message("'LOW' = [${CUSTOM_FUNCTION_LOW}]")
#Напечатает "'HIGH' = [FALSE]":message("'HIGH' = [${CUSTOM_FUNCTION_HIGH}]")
# Напечатает "'NUMBER' = [30]":message("'NUMBER' = [${CUSTOM_FUNCTION_NUMBER}]")
# Напечатает "'COLORS' = [red;green;blue]":message("'COLORS' = [${CUSTOM_FUNCTION_COLORS}]")
endfunction()
# Вызвать функцию "custom_function" с произвольными аргументами:
custom_function(LOW NUMBER 30 COLORS red green blue)
Scopes
In the previous section, you learned that some constructions in CMake can define their own scopes. In fact, all variables are considered global by default (they can be accessed everywhere), except for those defined and redefined in functions. There are also cache variables that have their own scope, but they are not used so often.
As mentioned in the comments, variables can be defined in the "parent" scope with a command set(VARIABLE ... PARENT_SCOPE)
. This example demonstrates this feature:
# Функция, определяющая переменную "VARIABLE" со значением# "In the parent scope..." в родительской области видимости:function(demonstrate_variable)
set(VARIABLE "In the parent scope..." PARENT_SCOPE)
endfunction()
# Определить переменную "VARIABLE" в текущей области видимости:
demonstrate_variable()
# Теперь возможно получить к переменной "VARIABLE" доступ:message("'VARIABLE' is equal to: ${VARIABLE}")
If the variable is VARIABLE
removed from the definition of the variable PARENT_SCOPE
, then the variable will be accessible only to functions demonstrate_variable
, and in the global scope it will take an empty value.
Conclusion
This is where the CMake syntax ends. The next article will be released in about a couple of days and will introduce the use of the CMake build system. See you soon!