下次我使用 CMake 时,我问这个是为了提醒自己。它永远不会坚持下去,而且谷歌的结果也不是很好。
在 CMake 中设置和使用变量的语法是什么?
在编写 CMake 脚本时,您需要了解很多关于语法以及如何在 CMake 中使用变量的知识。
语法
使用 set()
的字符串:
设置(MyString“一些文本”)
set(MyStringWithVar "其他一些文本:${MyString}")
set(MyStringWithQuot "一些引用:\"${MyStringWithVar}\"")
或使用 string()
:
字符串(附加 MyStringWithContent “${MyString}”)
使用 set()
的列表:
设置(我的列表“a”“b”“c”)
设置(我的列表 ${我的列表}“d”)
或者使用 list()
更好:
列表(追加我的列表“a”“b”“c”)
列表(追加我的列表“d”)
文件名列表:
set(MySourcesList "File.name" "File with Space.name")
list(APPEND MySourcesList "File.name" "File with Space.name")
add_excutable(MyExeTarget ${MySourcesList})
文档
CMake/语言语法
CMake:变量列表字符串
CMake:有用的变量
CMake set() 命令
CMake string()命令
CMake list() 命令
Cmake:生成器表达式
范围或“我的变量有什么值?”
首先是“正常变量”以及您需要了解的有关其范围的事情:
普通变量对设置它们的 CMakeLists.txt 以及从那里调用的所有内容都是可见的(add_subdirectory()、include()、macro() 和 function())。
add_subdirectory() 和 function() 命令很特别,因为它们打开了自己的作用域。含义变量 set(...) 仅在那里可见,并且它们复制了它们被调用的范围级别的所有普通变量(称为父范围)。因此,如果您在子目录或函数中,您可以使用 set(... PARENT_SCOPE) 修改父作用域中已经存在的变量。您可以通过将变量名称作为函数参数传递来在函数中使用它,例如。一个例子是 function(xyz _resultVar) is setting set(${_resultVar} 1 PARENT_SCOPE)
含义变量 set(...) 仅在那里可见,并且它们复制了它们被调用的范围级别的所有普通变量(称为父范围)。
因此,如果您在子目录或函数中,您可以使用 set(... PARENT_SCOPE) 修改父范围中已经存在的变量
您可以通过将变量名称作为函数参数传递来利用它,例如在函数中。一个例子是 function(xyz _resultVar) is setting set(${_resultVar} 1 PARENT_SCOPE)
另一方面,您在 include() 或 macro() 脚本中设置的所有内容都将直接在调用它们的范围内修改变量。
其次是“全局变量缓存”。关于缓存你需要知道的事情:
如果在当前范围内没有定义具有给定名称的普通变量,CMake 将寻找匹配的缓存条目。
缓存值存储在二进制输出目录中的 CMakeCache.txt 文件中。
缓存中的值可以在生成之前在 CMake 的 GUI 应用程序中进行修改。因此,与普通变量相比,它们具有类型和文档字符串。我通常不使用 GUI,所以我使用 set(... CACHE INTERNAL "") 来设置我的全局和持久值。请注意,内部缓存变量类型确实意味着 FORCE
在 CMake 脚本中,如果您使用 set(... CACHE ... FORCE) 语法,您只能更改现有的缓存条目。这种行为例如由 CMake 本身使用,因为它通常不会强制缓存条目本身,因此您可以使用另一个值预定义它。
您可以使用命令行在缓存中设置条目,语法为 cmake -D var:type=value,只需 cmake -D var=value 或使用 cmake -C CMakeInitialCache.cmake。
您可以使用 unset(... CACHE) 取消设置缓存中的条目。
缓存是全局的,您几乎可以在 CMake 脚本中的任何位置设置它们。但我建议您三思而后行,在哪里使用缓存变量(它们是全局的并且是持久的)。我通常更喜欢 set_property(GLOBAL PROPERTY ...)
和 set_property(GLOBAL APPEND PROPERTY ...)
语法来定义我自己的非持久性全局变量。
变量陷阱和“如何调试变量更改?”
为避免陷阱,您应该了解以下有关变量的知识:
如果两者具有相同的名称,则局部变量会隐藏缓存的变量
find_... 命令 - 如果成功 - 将它们的结果写入缓存变量“这样就不会再次搜索”
CMake 中的列表只是带有分号分隔符的字符串,因此引号很重要 set(MyVar abc) is "a;b;c" and set(MyVar "ab c") is "ab c" 建议您始终当您想将列表作为列表时使用引号,但一个例外 通常更喜欢使用 list() 命令来处理列表
set(MyVar abc) 是 "a;b;c" 并且 set(MyVar "ab c") 是 "ab c"
建议您始终使用引号,但当您想将列表作为列表给出时除外
通常更喜欢使用 list() 命令来处理列表
上面描述的整个范围问题。特别推荐使用 functions() 而不是 macros() 因为你不希望你的局部变量出现在父作用域中。
CMake 使用的许多变量是通过 project() 和 enable_language() 调用设置的。因此,在使用这些命令之前设置一些变量可能很重要。
环境变量可能与 CMake 生成 make 环境的位置以及使用 make 文件的时间不同。环境变量的更改不会重新触发生成过程。尤其是生成的 IDE 环境可能与您的命令行不同,因此建议将您的环境变量传输到缓存中。
环境变量的更改不会重新触发生成过程。
尤其是生成的 IDE 环境可能与您的命令行不同,因此建议将您的环境变量传输到缓存中。
有时只有调试变量会有所帮助。以下内容可能对您有所帮助:
只需使用 message() 命令即可使用旧的 printf 调试样式。 CMake 本身还附带了一些现成的模块:CMakePrintHelpers.cmake、CMakePrintSystemInformation.cmake
查看二进制输出目录中的 CMakeCache.txt 文件。如果您的 make 环境的实际生成失败,甚至会生成此文件。
使用 variable_watch() 查看您的变量在哪里被读取/写入/删除。
查看目录属性 CACHE_VARIABLES 和 VARIABLES
调用 cmake --trace ... 查看 CMake 的完整解析过程。这是最后的储备,因为它产生了大量的输出。
特殊语法
环境变量您可以读取 $ENV{...} 并编写 set(ENV{...} ...) 环境变量
您可以读取 $ENV{...} 并写入 set(ENV{...} ...) 环境变量
生成器表达式 生成器表达式 $<...> 仅在 CMake 的生成器编写 make 环境时进行评估(它与由解析器“就地”替换的普通变量进行比较)非常方便,例如在编译器/链接器命令行和多- 配置环境
生成器表达式 $<...> 仅在 CMake 的生成器编写 make 环境时进行评估(它与由解析器“就地”替换的普通变量进行比较)
非常方便,例如在编译器/链接器命令行和多配置环境中
引用 使用 ${${...}} 您可以在变量中指定变量名称并引用其内容。通常在将变量名称作为函数/宏参数时使用。
使用 ${${...}} 您可以在变量中指定变量名称并引用其内容。
通常在将变量名称作为函数/宏参数时使用。
常量值(参见 if() 命令) 使用 if(MyVariable),您可以直接检查变量的真/假(此处不需要封闭的 ${...}) 如果常量为 1、ON、YES、TRUE,则为 True , Y 或非零数。如果常量为 0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串或以后缀 -NOTFOUND 结尾,则为 False。这种语法通常用于 if(MSVC) 之类的东西,但对于不知道这种语法快捷方式的人来说,它可能会造成混淆。
使用 if(MyVariable) 您可以直接检查变量的真/假(此处不需要封闭的 ${...})
如果常数为 1、ON、YES、TRUE、Y 或非零数字,则为真。
如果常量为 0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串或以后缀 -NOTFOUND 结尾,则为 False。
这种语法通常用于 if(MSVC) 之类的东西,但对于不知道这种语法快捷方式的人来说,它可能会造成混淆。
递归替换 您可以使用变量构造变量名称。在 CMake 替换了变量后,它会再次检查结果是否是变量本身。这是 CMake 本身使用的非常强大的功能,例如作为模板集的一种(CMAKE_${lang}_COMPILER ...)但请注意,这可能会让您在 if() 命令中头疼。以下是 CMAKE_CXX_COMPILER_ID 为“MSVC”且 MSVC 为“1”的示例: if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 为真,因为它的计算结果为 if("1" STREQUAL "1") if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 为假,因为它的计算结果为 if("MSVC" STREQUAL "1") 所以这里最好的解决方案是 - 见上文 - 直接检查 if(MSVC) 好消息是这已修复CMake 3.1 引入了策略 CMP0054。我建议始终将 cmake_policy(SET CMP0054 NEW) 设置为“仅在未引用时将 if() 参数解释为变量或关键字”。
您可以使用变量构造变量名称。在 CMake 替换了变量后,它会再次检查结果是否是变量本身。这是 CMake 本身使用的非常强大的功能,例如作为模板集的一种(CMAKE_${lang}_COMPILER ...)
但请注意,这会使您在 if() 命令中感到头疼。以下是 CMAKE_CXX_COMPILER_ID 为“MSVC”且 MSVC 为“1”的示例: if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 为真,因为它的计算结果为 if("1" STREQUAL "1") if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 是假的,因为它的计算结果为 if("MSVC" STREQUAL "1") 所以这里最好的解决方案是 - 见上文 - 直接检查 if(MSVC)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 为真,因为它的计算结果为 if("1" STREQUAL "1")
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 为假,因为它的计算结果为 if("MSVC" STREQUAL "1")
所以这里最好的解决方案是 - 见上文 - 直接检查 if(MSVC)
好消息是这个问题在 CMake 3.1 中通过引入策略 CMP0054 得到了修复。我建议始终将 cmake_policy(SET CMP0054 NEW) 设置为“仅在未引用时将 if() 参数解释为变量或关键字”。
option() 命令 主要只是缓存的字符串,它们只能是 ON 或 OFF,它们允许一些特殊的处理,例如依赖关系。但是请注意,不要将选项与 set 命令混淆。赋予 option 的值实际上只是“初始值”(在第一个配置步骤中传输一次到缓存),然后由用户通过 CMake 的 GUI 进行更改。
主要只是缓存的字符串,只能打开或关闭,它们允许一些特殊处理,例如依赖项
但请注意,不要将选项与 set 命令混淆。赋予 option 的值实际上只是“初始值”(在第一个配置步骤中传输一次到缓存),然后由用户通过 CMake 的 GUI 进行更改。
参考
如何使用 CMake?
cmake,迷失在全局变量的概念中(以及 PARENT_SCOPE 或 add_subdirectory 替代方案)
循环遍历字符串列表
如何存储 CMake 构建设置
CMake与STREQUAL比较空字符串失败
我什么时候应该引用变量?
这里有几个基本示例,可以快速入门。
一项变量
设置变量:
SET(INSTALL_ETC_DIR "etc")
使用变量:
SET(INSTALL_ETC_CROND_DIR "${INSTALL_ETC_DIR}/cron.d")
多项目变量(即列表)
设置变量:
SET(PROGRAM_SRCS
program.c
program_utils.c
a_lib.c
b_lib.c
config.c
)
使用变量:
add_executable(program "${PROGRAM_SRCS}")
$ENV{FOO}
用于使用,其中 FOO
是从环境变量中提取的。否则用作 ${FOO}
,其中 FOO
是一些其他变量。对于设置,SET(FOO "foo")
将在 CMake 中使用。
if ("${MyString}" ...)
时,我看到了警告:Policy CMP0054 is not set: Only interpret if() arguments as variables or keywords when unquoted
。例如,参见 Build 367。有任何想法吗?${MyString}
会导致 “if given arguments ...” 出现一堆错误,例如 CMake error near if: “if given arguments” followed by parantheses, “NOT”, “EQUALS” and similar。MyString
确实包含一个变量名,然后将再次取消引用该变量名。我相信没有人真正想要这种OLD
行为。因此,从我的角度来看,它完全安全且向后兼容,只需将策略CMP0054
设置为NEW
(参见讨论 here)。if (MyString ...)
(如果您的代码发出警告)。${MyString}
并将其替换为MyString
(或者我相信我们已将它们全部删除)。仍然没有喜悦:Build 372。废话甚至不是来自我们的代码。它似乎来自 CMake。第 283 行是if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
。