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.
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 .
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 message
command is passed six arguments for output to the console:
# "CMake is the most powerful buildsystem!" message("CMake " "is " "the " "most " "powerful " "buildsystem!")
Arguments, framed in double quotes, allow you to inside yourself to make the screening and substitution of variables. Unstructured arguments do not allow the production of such things and cannot include the 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 Walk;around;the;forest
argument expands to the Walk around the forest
list, since any unwrapped argument automatically expands to a list of values (provided that the values of the original argument are separated by semicolons), but framed in double quotes by the argument of such a transformation does not occur (the semicolon characters simply disappear). This feature was mentioned in the 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 can be defined by calling the set
command, and removed by calling unset
. You can get the value of a variable using the ${VARIABLE}
construction. 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)
CMake supports setting options to be modified by users. Options are similar to variables and are set by the option
command, which 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)
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 appeal to 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 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 elseif
and else
commands are optional, and endif
required and signals the completion of previous checks.
# "Of course, 5 > 1!": if(5 GREATER 1) message("Of course, 5 > 1!") elseif(5 LESS 1) message("Oh no, 5 < 1!") else() message("Oh my god, 5 == 1!") endif()
Cycles in CMake are similar to cycles in other programming languages. In the given example, the value of the variable VARIABLE
in Airport
, and then the four nested commands are executed sequentially until the value of the variable VARIABLE
equals Airport
. The last fourth command set(VARIABLE "Police station")
sets the value of the variable to be checked in the Police station
, so the cycle will immediately stop before reaching the second iteration. The endwhile
command signals the completion of the list of nested loop 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 foreach
example works as follows: at each iteration of this cycle, the variable VARIABLE
assigned the following value from the list Give me the sugar please!
, and then the message(${VARIABLE})
command is executed, which displays the current value of the VARIABLE
variable. When there are no values in the list, the loop ends its execution. The endforeach
command signals the completion of the list of nested loop commands.
# "Give me the sugar please!" : foreach(VARIABLE Give me the sugar please!) message(${VARIABLE}) endforeach()
There are 3 more forms of foreach
records. 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 3 15) message(${VARIABLE}) endforeach() # "50 60 70 80 90" : foreach(VARIABLE RANGE 50 90 10) message(${VARIABLE}) endforeach()
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(12 89 225) # "print_words", "Hey Hello Goodbye": print_words(Hey Hello Goodbye)
The function
command first of arguments takes the name of the future function, and the remaining arguments are the names of the parameters with which you can work 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 the macros in the C preprocessor: if you put the return
command in the macro body, then the exit 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 exit command will be substituted and executed to the place of calling the demonst_macro macro.
As noted in the comments, the powerful cmake_parse_arguments
mechanism allows for parsing the 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 variable of the form <Prefix>_<OptionOrKeyword>
, encapsulating some value. For options, these are boolean values (true — the option is specified, otherwise, false), and for keywords — all passed values located after them.
The custom_function
function contains a call to the cmake_parse_arguments
, and then commands to print the values of certain variables. Further, the function is called with arguments LOW NUMBER 30 COLORS red green blue
, after which the screen is printed on:
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)
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 the set(VARIABLE ... PARENT_SCOPE)
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 PARENT_SCOPE
removed from the definition of the variable VARIABLE
, then the variable will be accessible only to the PARENT_SCOPE
function, and in the global scope it will take an empty value.
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!
Source: https://habr.com/ru/post/431428/
All Articles