标签:ems comm 博文 sse build reference varname spl uil
在上一篇博客中我们提到了每一个CMake项目都需要包含一个 CMakeLists.txt 脚本。这个脚本定义了目标文件(targets),也可以做很多其他的事情,例如:寻找第三方库,生成C++头文件等。 CMake脚本有很大的灵活性。
每当你集成一个外部库,以及添加其他平台的支持的大部分情况下,你需要编辑CMake脚本。我曾在并不动CMake脚本的情况下花了很长时间来编辑 CMakeLists.txt - 因为CMake脚本的文档很分散。但最终我搞懂了CMake脚本,这篇博文的目的就是让你尽快想我一样了解CMake脚本。
这篇博文不会覆盖所有的CMake命令(总共有数百个!),但这对CMake脚本的语法和编程模型将是个颇为完整的指南。
Hello World
如果你创建一个包含以下内容的 hello.txt 文件:
message(“Hello World!”) # A message to print
你能够在命令行中用 cmake -P hello.txt 来运行此文件。(-P 选项告诉cmake 运行指定脚本,但是不生成构建流水线(build pipeline))正如所期待的,运行此命令会输出 “Hello World!”。
$ cmake -P hello.txt
Hello world!
所有的变量都是字符串
在CMake中,所有的变量都是字符串。你可以在字符串常量(string literal)中使用 ${} 代入变量, 这被称作变量引用(variable reference)。修改 hello.txt 如下:
message("Hello ${NAME}!") # Substitute a variable into the message
现在,如果我们在 cmake 命令中使用 -D 选项定义 NAME, hello.txt将会使用此变量:
$ cmake -DNAME=Newman -P hello.txt
Hello Newman!
当一个变量未被定义时,默认值是一个空字符串:
$ cmake -P hello.txt
Hello !
我们还可以在脚本中使用 set 命令定义变量。set 的第一个参数时变量名,第二个参数时变量的值:
set(THING "funk")
message("We want the ${THING}!")
只要字符串中没有空格或变量引用,set 参数中的引号是可选的。例如,set("THING" funk) 和上面第一行的命令是等价的。对于大多数CMake命令(if 和 while 命令除外)而言,是否使用引号只是风格问题。当参数只是变量名时,我倾向于不使用引号。
用前缀(prefixes)模拟数据结构
CMake中没有类(classes)的概念,但你可以通过定义一组有着同样前缀(prefixes)的变量来模拟数据结构。使用时,你可以用嵌套的变量引用( ${} )来查找组内变量。例如,下述脚本将会输出 “John Smith lives at 123 Fake St.”:
set(JOHN_NAME "John Smith")
set(JOHN_ADDRESS "123 Fake St")
set(PERSON "JOHN")
message("${${PERSON}_NAME} lives at ${${PERSON}_ADDRESS}.")
你甚至可以在set命令中使用变量引用。例如,如果 PERSON 变量的值仍然是 “JOHN”,下述代码将会把变量 JOHN_NAME 的值置为 “John Goodman”:
set(${PERSON}_NAME "John Goodman")
每条声明都是一条命令
在CMake中,每条声明都是一条接收一列字符串参数并且无返回值的命令。参数间由空格隔开。正如我们已经见到的, set 命令在文件中(file scope)定义了一个变量。
另一个例子是CMake中执行算术运算的 math 命令。math命令的第一个参数必须是 EXPR , 第二个参数是被赋值变量的名字,第三个参数是被求值表达式,这三个参数均为字符串。注意以下第三行代码,CMake在将参数传入 math 命令前先把字符串 MY_SUM 的值替换掉。
math(EXPR MY_SUM "1 + 1") # Evaluate 1 + 1; store result in MY_SUM
message("The sum is ${MY_SUM}.")
math(EXPR DOUBLE_SUM "${MY_SUM} * 2") # Multiply by 2; store result in DOUBLE_SUM
message("Double that is ${DOUBLE_SUM}.")
几乎所有你需要做的事情都有一个对应的CMake 命令(https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html)。 string 命令可以帮助你完成正则表达式替换等高级字符串操作。file 命令能读写文件或者操作文件系统路径。
控制流命令
甚至控制流语句也是命令。if/endif 命令让你根据条件执行被包围的命令。控制流中的空格不影响功能,但通常还是会缩进条件命令间的命令以提高可读性。如下的命令检查 CMake 的内建变量 WIN 是否被赋值:
if(WIN32)
message("You‘re running CMake on Windows.")
endif()
CMake 还有 while/endwhile 命令来在条件为真时重复执行命令。如下循环将打印所有1000000以下的斐波那契数字:
set(A "1")
set(B "1")
while(A LESS "1000000")
message("${A}") # Print A
math(EXPR T "${A} + ${B}") # Add the numeric values of A and B; store result in T
set(A "${B}") # Assign the value of B to A
set(B "${T}") # Assign the value of T to B
endwhile()
CMake 中的 if 和 while 条件的用法和其他编程语言不同。如上所示,要执行数值比较,你必须指定 LESS 作为一个字符串参数。如下文档(https://cmake.org/cmake/help/latest/command/if.html)详细解释了如何正确编写条件语句。
if 和 while 命令有些特殊-如果指定的变量没有引号包围,if 和 while 将直接使用这些变量的值。在以上的代码中,我利用了这一点。第三行写作 while(A LESS “1000000”)而不是 while (${A} LESS "1000000"),虽然两者是等价的。其他命令不会主动对变量求值。
列表(Lists)只是分号分隔的字符串
CMake 对没有用引号的参数有特别的替换规则。如果整个参数是一个没有引号的变量引用,而且变量的值中包含分号,那么 CMake 将在分号处分隔该参数并作为多个参数传入包含该变量的命令。例如,如下代码将向 math 命令传入三个参数:
set(ARGS "EXPR;T;1 + 1")
math(${ARGS}) # Equivalent to calling math(EXPR T "1 + 1")
另一方面,引号包围的参数不会在被替换后被分号分隔。CMake 总是将引号字符串作为单独的参数传入,保持分号的完整性:
set(ARGS "EXPR;T;1 + 1")
message("${ARGS}") # Prints: EXPR;T;1 + 1
如果两个以上的参数被传入 set 命令,它们将会被分号连接起来,然后被赋值给指定变量。这实际上是用参数创建了一个列表(list):
set(MY_LIST These are separate arguments)
message("${MY_LIST}") # Prints: These;are;separate;arguments
你能通过 list 命令来操作列表:
set(MY_LIST These are separate arguments)
list(REMOVE_ITEM MY_LIST "separate") # Removes "separate" from the list
message("${MY_LIST}") # Prints: These;are;arguments
foreach/endforeach 命令接收多个参数并迭代除第一个参数以外的参数:
foreach(ARG These are separate arguments)
message("${ARG}") # Prints each word on a separate line
endforeach()
你可以通过传入一个没有引号的变量引用给 foreach 来进行迭代一个列表。就像其他命令一样, CMake将以分号分隔该变量的值:
foreach(ARG ${MY_LIST}) # Splits the list; passes items as arguments
message("${ARG}") # Prints each item on a separate line
endforeach()
函数有作用域;宏没有
在 CMake 中,你可以用 function/endfunction 命令来定义一个函数。以下代码定义了一个将参数的数值翻倍并打印的函数 doubleIt:
function(doubleIt VALUE)
math(EXPR RESULT "${VALUE} * 2")
message("${RESULT}")
endfunction()
doubleIt("4") # Prints: 8
函数在自己的作用域中运行。函数中定义的局部变量不会污染调用者的作用域。如果你想要返回值,你可以传入你想要返回的变量,然后用特殊参数 PARENT_SCOPE 调用 set 命令:
function(doubleIt VARNAME VALUE)
math(EXPR RESULT "${VALUE} * 2")
set(${VARNAME} "${RESULT}" PARENT_SCOPE) # Set the named variable in caller‘s scope
endfunction()
doubleIt(RESULT "4") # Tell the function to set the variable named RESULT
message("${RESULT}") # Prints: 8
类似的,macro/endmacro 命令可定义一个宏。但是和函数不同,宏和它们的调用者在同一作用域工作。因此,宏中定义的所有变量在调用者的作用域中被set。我们可以用以下宏替代上述函数:
macro(doubleIt VARNAME VALUE)
math(EXPR ${VARNAME} "${VALUE} * 2") # Set the named variable in caller‘s scope
endmacro()
doubleIt(RESULT "4") # Tell the macro to set the variable named RESULT
message("${RESULT}") # Prints: 8
函数和宏都可以接受任意数量的参数。未命名参数通过一个特殊变量 ARGN 作为列表暴露给函数。以下函数将所有接收的参数翻倍,并将每个参数分行打印:
function(doubleEach)
foreach(ARG ${ARGN}) # Iterate over each argument
math(EXPR N "${ARG} * 2") # Double ARG‘s numeric value; store result in N
message("${N}") # Print N
endforeach()
endfunction()
doubleEach(5 6 7 8) # Prints 10, 12, 14, 16 on separate lines
包含其他脚本
CMake 变量都定义在文件域。include 命令在 调用文件的作用域 执行其他 CMake 脚本。这和 C/C++ 中的 #include 预处理命令很相似。include 命令通常用来调用其他脚本中定义一些常用的函数或者宏,此命令用 CMAKE_MODULE_PATH 变量中的值作为搜索路径。
find_package 命令搜索形如 Find*.cmake 的脚本,并且在同一作用域中运行这些脚本。这些脚本通常用来帮助寻找外部库。例如,如果在搜索路径中存在一个叫做 FindSDL2.cmake 的文件, find_package(SDL2)等价于 include(FindSDL2.cmake)。(注意,此处只是 find_package 多种用法中的一种)
另一方面,add_subdirectory 命令会创建一个新的作用域,然后在新作用域中运行指定文件夹下的CMakeLists.txt 脚本。此命令通常用来添加另一个 CMake 子项目,例如一个库或者是一个可执行文件,到本项目。如果不特别指定的话,子项目中定义的目标文件会被添加到构建流水线(build pipeline)中。子项目脚本中的变量不会污染本项目的作用域-除非 set 命令中使用了 PARENT_SCOPE 选项。
例如,在Turf(https://github.com/preshing/turf)项目中,运行 CMake 时如下脚本会被用到:
读取和设置属性
CMake 使用 add_executable, add_library 或者 add_custom_target 命令来定义目标文件。目标文件被创建好之后,它们就有了属性(properties),你可以用 get_property 和 set_property 命令来操作这些属性。不像变量,目标文件在所有作用域中都是可见的,即使它们是在子项目中定义的。目标文件的所有属性也都是字符串。
add_executable(MyApp "main.cpp") # Create a target named MyApp
# Get the target‘s SOURCES property and assign it to MYAPP_SOURCES
get_property(MYAPP_SOURCES TARGET MyApp PROPERTY SOURCES)
message("${MYAPP_SOURCES}") # Prints: main.cpp
常见的目标文件属性还包括 LINK_LIBRARIES, INCLUDE_DIRECTORIES 和 COMPILE_DEFINITIONS。这些属性会被 target_link_libraries ,target_include_directories 和 target_compile_definitions 命令间接修改。在脚本结束的时候,CMake会用这些目标文件属性来生成构建流水线。
其他 CMake 实体(entities)也有自己的属性。每个文件作用域都有一个目录属性(directory properties)的集合。还有一个所有脚本都可见的全局属性(global properties)集合。每个 C/C++ 源文件也有一个源文件属性(source file properties)集合。
原文链接:https://preshing.com/20170522/learn-cmakes-scripting-language-in-15-minutes/
标签:ems comm 博文 sse build reference varname spl uil
原文地址:https://www.cnblogs.com/42-Curry/p/9752027.html