是什么CMake语法设置和使用变量?

我问这个问题是为了提醒自己下次使用CMake。它从来没有坚持过,谷歌的结果也不是很好。

在CMake中设置和使用变量的语法是什么?

245462 次浏览

这里有几个基本的例子可以让你快速上手。

一项变量

设置变量:

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}")

CMake docs on variables

在编写CMake脚本时,你需要了解很多关于语法以及如何在CMake中使用变量的知识。

的语法

使用set()的字符串:

  • set(MyString "Some Text")
  • set(MyStringWithVar "Some other Text: ${MyString}")
  • set(MyStringWithQuot "Some quote: \"${MyStringWithVar}\"")

或者使用string():

  • string(APPEND MyStringWithContent " ${MyString}")

使用set()列出:

  • set(MyList "a" "b" "c")
  • set(MyList ${MyList} "d")

或者更好地使用list():

  • list(APPEND MyList "a" "b" "c")
  • list(APPEND MyList "d")

文件名称列表:

  • set(MySourcesList "File.name" "File with Space.name")
  • list(APPEND MySourcesList "File.name" "File with Space.name")
  • add_excutable(MyExeTarget ${MySourcesList})

的文档

Scope或“我的变量有什么值?”

首先是“正常变量”;关于它们的范围你需要知道的事情:

  • 普通变量对于它们所在的CMakeLists.txt以及从那里调用的所有内容(add_subdirectory()include()macro()function())都是可见的。
  • add_subdirectory()function()命令是特殊的,因为它们打开了自己的作用域。
  • 这意味着变量set(...)只在那里可见,它们复制了调用它们的作用域级别的所有正常变量(称为父作用域)。
  • 因此,如果你在子目录或函数中,你可以用set(... PARENT_SCOPE)修改父作用域中已经存在的变量
  • 你可以利用这一点,例如在函数中通过将变量名作为函数参数传递。一个例子是function(xyz _resultVar)正在设置set(${_resultVar} 1 PARENT_SCOPE)
  • 另一方面,你在include()macro()脚本中设置的所有内容都将直接在调用它们的范围内修改变量。

其次是“全局变量缓存”。关于缓存你需要知道的事情:

  • 如果当前作用域中没有定义具有给定名称的普通变量,CMake将查找匹配的缓存项。

  • 缓存值存储在二进制输出目录中的CMakeCache.txt文件中。

  • 缓存中的值在生成之前可以在CMake的GUI应用程序中修改。因此,与普通变量相比,它们具有typedocstring。我通常不使用GUI,所以我使用set(... CACHE INTERNAL "")来设置我的全局值和持久化值。

    请注意,INTERNAL缓存变量类型确实包含FORCE

  • 在CMake脚本中,如果你使用set(... CACHE ... FORCE)语法,你只能改变现有的缓存项。这种行为是由CMake本身使用的,因为它通常不强制缓存条目本身,因此你可以用另一个值预先定义它。

  • 你可以使用命令行设置缓存中的条目,语法为cmake -D var:type=value,只是cmake -D var=valuecmake -C CMakeInitialCache.cmake

  • 你可以用unset(... CACHE)在Cache中设置项。

缓存是全局的,你可以在CMake脚本的任何地方设置它们。但是我建议你在使用缓存变量时三思(它们是全局的并且是持久化的)。我通常更喜欢用set_property(GLOBAL PROPERTY ...)set_property(GLOBAL APPEND PROPERTY ...)语法来定义我自己的非持久化全局变量。

变量陷阱和“如何调试变量更改?”

为了避免陷阱,你应该了解以下关于变量的知识:

  • 如果局部变量和缓存变量的名称相同,则局部变量会隐藏缓存的变量
  • find_...命令-如果成功-确实将它们的结果写入缓存变量"以便没有调用将再次搜索"
  • CMake中的列表只是带分号分隔符的字符串,因此引号很重要
  • set(MyVar a b c)"a;b;c"set(MyVar "a b c")"a b c"
  • 建议您总是使用引号,只有当您想将列表作为列表时例外
  • 通常倾向于list()命令来处理列表
  • 上面描述的整个范围问题。特别建议使用functions()而不是macros(),因为你不希望你的局部变量出现在父范围内。
  • CMake使用的许多变量都是通过project()enable_language()调用设置的。因此,在使用这些命令之前设置一些变量可能非常重要。
  • 环境变量可能与CMake生成make环境的位置和使用make文件的时间不同。
  • 环境变量的更改不会重新触发生成过程。
  • 特别是生成的IDE环境可能与您的命令行不同,因此建议将环境变量转换为缓存的内容。

有时只有调试变量才有帮助。以下建议可能会对你有所帮助:

  • 简单地使用message()命令来使用旧的printf调试风格。还有一些可以使用CMake自带的模块:CMakePrintHelpers.cmakeCMakePrintSystemInformation.cmake
  • 在二进制输出目录中查找CMakeCache.txt文件。即使实际生成make环境失败,也会生成此文件。
  • 使用variable_watch ()来查看变量被读取/写入/删除的位置。
  • 查看目录属性CACHE_VARIABLES变量
  • 调用cmake --trace ...查看CMake的完整解析过程。这是最后的储备,因为它产生了大量的输出。

特殊的语法

  • 环境变量
  • 你可以读$ENV{...}和写set(ENV{...} ...)环境变量
  • 生成器表达式
  • 生成器表达式$<...>只在CMake的生成器写入make环境时才会被求值(它与正常的被替换的“就地”变量相比;通过解析器)
  • 非常方便,例如在编译器/链接器命令行和多配置环境中
  • 参考文献
  • 使用${${...}},你可以在变量中给出变量名并引用它的内容。
  • 通常在给出变量名作为函数/宏参数时使用。
  • 常量值(参见if()命令)
  • 使用if(MyVariable)可以直接检查变量的真/假(这里不需要使用${...})
  • 如果常量为1ONYESTRUEY或非零数,则为True。
  • 如果常量为0OFFNOFALSENIGNORENOTFOUND,空字符串,或以后缀-NOTFOUND结尾,则为False。
  • 这种语法通常用于if(MSVC)之类的东西,但对于不知道这种语法快捷方式的人来说,它可能会令人困惑。
  • 递归的替换
  • 可以使用变量构造变量名。在CMake替换变量之后,它将再次检查结果是否是变量本身。这是CMake本身使用的非常强大的功能,例如作为模板set(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")为false,因为它的值为if("MSVC" STREQUAL "1")
    • 所以这里最好的解决方案是-见上面-直接检查if(MSVC)
  • 好消息是,在CMake 3.1中通过引入政策CMP0054修复了这个问题。我建议始终将cmake_policy(SET CMP0054 NEW)设置为“仅将if()参数解释为未引用的变量或关键字”。
  • option()命令
  • 主要是缓存的字符串只能是ONOFF,它们允许一些特殊处理,例如依赖关系
  • 但是请注意,不要将optionset命令混淆。赋给option的值实际上只是“初始值”。(在第一个配置步骤中转移到缓存一次),随后将由用户通过CMake的GUI进行更改。

参考文献

$ENV{FOO}用于使用,其中FOO是从环境变量中提取的。否则使用${FOO},其中FOO是其他变量。对于设置,SET(FOO "foo")将在CMake中使用。