${a}
似乎与 "${a}"
有不同的含义。例如,这里:Passing a list to a CMake macro 我应该在什么时候添加引号,以及更重要的基本原则是什么?
${a}
似乎与 "${a}"
有不同的含义。CMake有两个原则必须牢记:
示例
set(_my_text "A B C")
与message("${_my_text}")
将会输出A B C
set(_my_list A B C)
与message("${_my_list}")
将会输出A;B;C
set(_my_list "A" "B" "C")
与message("${_my_list}")
将会输出A;B;C
set(_my_list "A" "B" "C")
与message(${_my_list})
将会输出ABC
一些基本规则
以下是一些需要考虑的基本规则:
a) 当你的变量包含文本——尤其是可能包含分号的文本时——你应该添加引号。
原因:分号是CMake中列表元素的分隔符。因此,对于希望作为一个单一元素的文本,请在其周围加上引号(这在任何地方都有效,并且在我个人看来,使用CMake语法高亮更好)。
编辑:感谢@schieferstapel的提示
b) 更精确地说:已经有引号的包含空格的变量会保留这些引号作为变量内容的一部分。这也适用于未引用的情况(普通或用户定义的函数参数),但不包括if()
调用,因为CMake在变量扩展后重新解释未引用变量的内容(请参见基本规则#3 和策略CMP0054: 仅在未引用时将if()
参数解释为变量或关键字)。
示例:
set(_my_text "A B C")
,并使用message(${_my_text})
命令会得到A B C
set(_my_text "A;B;C")
,并使用if (${_my_text} STREQUAL "A;B;C")
命令会得到如果给出参数:"A" "B" "C" "STREQUAL" "A;B;C"指定了未知参数
如果您的变量包含列表,则通常不需要添加引号。
原因:如果您向CMake命令提供类似文件列表的东西,它通常希望得到一个字符串列表而不是一个包含列表的字符串。您可以在foreach()
命令中看到这种区别,它接受ITEMS
或LISTS
。
if()
语句是一种特殊情况,在这种情况下,您通常甚至不需要放置括号。
原因:一个字符串经过扩展后可能再次评估为一个变量名。为了防止这种情况发生,建议只命名要比较内容的变量(例如if (_my_text STREQUAL "A B C")
)。
set(_my_text "A B C")
和 COMMAND "${CMAKE_COMMAND}" -E echo "${_my_text}"
在 VS/Windows 上会执行 cmake.exe -E echo "A B C"
,在 GCC/Ubuntu 上会执行 cmake -E echo A\ B\ C
,输出为 A B C
set(_my_text "A B C")
和 COMMAND "${CMAKE_COMMAND}" -E echo "${_my_text}" VERBATIM
在 VS/Windows 上会执行 cmake.exe -E echo "A B C"
,在 GCC/Ubuntu 上会执行 cmake -E echo "A B C"
,输出为 A B C
set(_my_list A B C)
和 COMMAND "${CMAKE_COMMAND}" -E echo "${_my_list}"
会执行 cmake.exe -E echo A;B;C
,输出为 A
, B: command not found
, C: command not found
set(_my_list A B C)
和 COMMAND "${CMAKE_COMMAND}" -E echo "${_my_list}" VERBATIM
会执行 cmake.exe -E echo "A;B;C"
,输出为 A;B;C
set(_my_list "A" "B" "C")
和 COMMAND "${CMAKE_COMMAND}" -E echo "${_my_list}" VERBATIM
会执行 cmake.exe -E echo "A;B;C"
,输出为 A;B;C
set(_my_list "A" "B" "C")
和 COMMAND "${CMAKE_COMMAND}" -E echo ${_my_list} VERBATIM
会执行 cmake.exe -E echo A B C
,输出为 A B C
set(_my_list "A + B" "=" "C")
和 COMMAND "${CMAKE_COMMAND}" -E echo ${_my_list} VERBATIM
会执行 cmake.exe -E echo "A + B" = C
,输出为 A + B = C
add_custom_target()
/add_custom_command()
/execute_process()
的一些经验法则
在使用COMMAND
调用变量时,应该考虑以下几点:
a) 对于包含文件路径(例如第一个参数包含可执行文件本身)的参数,请使用引号。
原因:它可能包含空格,并且可能会被重新解释为COMMAND
调用的单独参数。
b) 与上述情况类似,如果变量set()
包括引号,则仍然适用。
仅在您想要将某些内容连接到要传递给可执行文件的单个参数中时使用引号。
原因:当使用引号时,变量可能包含参数列表,这些参数列表不会被正确提取(分号而不是空格)。
始终在add_custom_target()
/add_custom_command()
中添加VERBATIM
选项。
原因:否则跨平台行为是未定义的,您可能会收到有关引号字符串的惊喜。
参考资料
更多信息,请参见Craig Scott的这篇文章,其中讨论了有关列表和命令参数、生成器表达式以及if()
命令引号考虑事项。
${a}
看起来与 "${a}"
有着不同的含义。if(...)
)。
变量引用的文档可以在这里找到。请注意,CMake中的命令包括函数和宏(相关文档)。简而言之,如果您在不传递--warn-uninitialized
的情况下引用不存在的变量,则CMake将将引用评估为空字符串。这就是为什么上面的第一个message
调用会打印“foo:”,以及为什么对set(foo ${foo} abc)
的第一次调用(其中第二个参数引用了foo
)不会出错(只要您不使用--warn-uninitialized
)。
关于引用参数的部分:
引用参数内容包括在开头和结尾引号之间的所有文本。同时会对转义序列和变量引用进行求值。引用参数总是作为一个参数传递给命令调用。
关于非引用参数的部分:
非引用参数内容包括在允许或转义字符的连续块中的所有文本。同时会对转义序列和变量引用进行求值。结果值按照列表划分为元素。每个非空元素都作为一个参数传递给命令调用。因此,非引用参数可以作为零个或多个参数传递给命令调用。
这就是为什么我说“[quote variables] when you need to”。
在调用命令时,您需要引用什么以获得所需的行为,其余决定因素将取决于命令体如何处理传递给它的参数(在 CMake 完成取消转义、变量引用评估等操作后)。有关详细信息,请参阅特定命令的文档或函数/宏的文档或实现(还请参阅 function arguments、macro arguments 和 macro argument caveats 的相关文档)。
if(...)
命令好的,那么我为什么还说“当你觉得做这件事并且从技术上讲没有任何区别时”呢?因为 if(...)
命令具有额外的行为。
对于 if(...)
"子命令" 签名中的任何位置,您看到 "<condition>
",请参考 条件语法文档,该文档说明:
if(<constant>)
如果常量是1
,ON
,YES
,TRUE
,Y
, 或一个非零数(包括浮点数),返回真。如果常量是0
,OFF
,NO
,FALSE
,N
,IGNORE
,NOTFOUND
, 空字符串,或以后缀-NOTFOUND
结尾,则返回假。命名布尔常量不区分大小写。如果参数不是这些特定常量之一,则将其视为变量或字符串(请参见下面的变量扩展),并适用以下两种形式之一。
if(<variable>)
给定一个被定义为非 false 常量值的变量,则返回真。否则返回假,包括变量未定义的情况。注意,宏参数不是变量。环境变量也不能以此方式测试,例如,if(ENV{some_var})
总是计算为假。
if(<string>)
除非:
该字符串的值是真常量之一,或者
否则,带引号的字符串始终计算为假。
此外,if(...)
命令的许多签名/“子命令”接受形式为 <variable|string>
的参数。
请注意,这些不是关于变量引用!只是变量名称,可能会导致人们发现意外的怪异结果,例如在 CMake 中仅限数字的变量名称 中所见。
如果您希望确保在未加引号的字符串中按名称“自动引用”变量的 if(...)
命令中的某些内容实际上是字符串,则通过将其括起来来防止发生这种情况。因此,这涵盖了两个原因:一是“在需要时”,二是“当你觉得这样做并且在技术上没有任何区别时”(我喜欢在这些情况下引用我打算作为字符串的东西,以获得心理安慰)。
if(...)
特有的。您同样可以编写一个执行此类操作的函数(例如${${ARGV0}}
(先解除引用以获取参数值,然后再次解除引用以将该值视为另一个变量的名称,或者首先执行if(DEFINED "${ARGV0}")
来检查是否首先定义了这样的变量)。因此,如果您想要安全,请始终阅读文档(我只是鼓励通常阅读文档)。
func(${X})
,不带引号),并且X
包含空格,那么X
仍然只是一个参数,不会像你所说的那样被扩展/评估。如果引用每个可能包含带空格路径的变量的话,这将会过于容易出错(就像在 POSIX shell 中一样)。 - schieferstapeladd_custom_command()
部分(虽然我当时测试过,但我会再次检查)。 - Florian