CMake 样式指南
我们预计所有 CMake 脚本均:
- 在
scripts/
目录中,或 - 在
vcpkg-*
端口中
应遵循本文档中规定的准则。 现有脚本可能尚未遵循这些准则:预计我们将继续更新旧脚本,使其符合这些准则。
这些准则旨在保证脚本的稳定性。 我们希望其能够简化前向兼容性和后向兼容性。
准则
除了输出参数以外,我们始终使用
cmake_parse_arguments()
而不是函数参数或引用${ARG<N>}
。这不一定需要后跟“脚本本地帮助程序函数”
- 在这种情况下,位置参数应放在函数声明中(而不是使用
${ARG<N>}
),并应根据本地规则命名(即snake_case
)。 - 例外:在检查
ARGC
后,可选的位置参数应通过set(argument_name "${ARG<N>}")
指定名称。
- 在这种情况下,位置参数应放在函数声明中(而不是使用
输出参数应该是函数的第一个参数。 示例:
function(format out_var) cmake_parse_arguments(PARSE_ARGV 1 "arg" ...) # ... set(buffer "output") set("${out_var}" "${buffer}" PARENT_SCOPE) endfunction()
没有未解析或未使用的参数。 始终检查
ARGN
或arg_UNPARSED_ARGUMENTS
。 如果可能则为FATAL_ERROR
;如果需要后向兼容性则为WARNING
。所有
cmake_parse_arguments
必须使用PARSE_ARGV
。所有
foreach
循环都必须使用IN LISTS
、IN ITEMS
或RANGE
。除非在对用户有帮助的信息中,否则变量
${ARGV}
和${ARGN}
均未引用。- (即
message(FATAL_ERROR "blah was passed extra arguments: ${ARGN}")
)
- (即
我们始终使用函数,而不是宏或顶级代码。
- 例外:“script-local 帮助程序 宏”。 定义一个小宏有时会很有帮助。 应谨慎执行此操作,并且应首选函数。
- 例外:
vcpkg.cmake
的find_package
。
脚本树中的脚本不应预期在正常运行过程中需要可观测的更改。
- 示例冲突:
vcpkg_acquire_msys()
有硬编码的包和版本,由于 MSYS 项目删除了旧包,因此需要随着时间的推移进行更新。 - 例外示例:
vcpkg_from_sourceforge()
有一个需要维护的镜像列表,但对调用方没有可观察到的行为影响。
- 示例冲突:
引用规则:CMake 中有三种类型的参数 - 不带引号 (
foo(BAR)
)、带引号 (foo("BAR")
) 和带括号 (foo([[BAR]])
)。 遵循以下规则正确引用:如果参数包含变量扩展
${...}
,则必须将其加引号。例外:"splat" 变量扩展,当一个变量作为多个参数传递给函数时。 在这种情况下,参数应仅为
${foo}
:vcpkg_list(SET working_directory) if(DEFINED "arg_WORKING_DIRECTORY") vcpkg_list(SET working_directory WORKING_DIRECTORY "${arg_WORKING_DIRECTORY}") endif() # calls do_the_thing() if NOT DEFINED arg_WORKING_DIRECTORY, # else calls do_the_thing(WORKING_DIRECTORY "${arg_WORKING_DIRECTORY}") do_the_thing(${working_directory})
此外,如果参数包含并非
\\
、\"
或\$
的任何转义序列,则该参数必须是带引号的参数。- 例如:
"foo\nbar"
必须带引号。
- 例如:
此外,如果参数包含
\
、"
或$
,则应将该参数括起来。示例:
set(x [[foo\bar]]) set(y [=[foo([[bar\baz]])]=])
此外,如果参数包含的字符并非字母数字或
_
,则应将该参数加引号。否则,该参数应不加引号。
例外:
<variable|string>
类型的if()
参数应始终加引号:比较运算符的两个参数 -
EQUAL
、STREQUAL
、VERSION_LESS
等。MATCHES
和IN_LIST
的第一个参数示例:
if("${FOO}" STREQUAL "BAR") # ... if("${BAZ}" EQUAL "0") # ... if("FOO" IN_LIST list_variable) # ... if("${bar}" MATCHES [[a[bcd]+\.[bcd]+]]) # ...
对于单个表达式和不采用
<variable|string>
的其他类型的谓词,请使用常规规则。
除了简单的输出参数之外,没有任何“指针”或“输入输出”参数(用户传递变量名称而不是内容)。
变量不会假定为空。 如果打算局部使用该变量,则必须将其显式初始化为空:如果是字符串变量则使用
set(foo "")
,如果列表变量则使用vcpkg_list(SET foo)
。不应使用
set(var)
。 使用unset(var)
取消设置变量,使用set(var "")
将其设置为空字符串,使用vcpkg_list(SET var)
将其设置为空列表。 注意:空字符串和空列表是相同的值;这是表示法差异,而不是结果的差异应记录预期跨 API 边界从父作用域继承的所有变量(即并非文件局部函数)。 三元组文件中提及的所有变量均视为已记录。
输出参数仅在
PARENT_SCOPE
中设置且永不读取。 另请参阅帮助程序z_vcpkg_forward_output_variable()
,以通过函数作用域转发参数。CACHE
变量仅用于强耦合函数内部共享的全局变量和单个函数中的内部状态,以避免重复工作。 这些变量应非常谨慎地使用,并应使用Z_VCPKG_
前缀以避免与任何其他代码定义的任何局部变量相冲突。- 示例:
vcpkg_cmake_configure
的Z_VCPKG_CMAKE_GENERATOR
z_vcpkg_get_cmake_vars
的Z_VCPKG_GET_CMAKE_VARS_FILE
- 示例:
include()
仅允许在ports.cmake
或vcpkg-port-config.cmake
中使用。foreach(RANGE)
的参数必须始终为自然数,并且<start>
必须始终小于或等于<stop>
。这一点必须通过如下内容进行检查:
if("${start}" LESS_EQUAL "${end}") foreach(RANGE "${start}" "${end}") ... endforeach() endif()
所有基于端口的脚本都必须使用
include_guard(GLOBAL)
以避免多次包含。
需要的 CMake 版本
- 除
vcpkg.cmake
以外的所有 CMake 脚本都可能采用ports.cmake
的cmake_minimum_required
中存在的 CMake 版本。- 每次将新版本的 CMake 添加到
vcpkgTools.xml
时都应升级此cmake_minimum_required
,就像在所有帮助程序CMakeLists.txt
文件中的cmake_minimum_required
一样。
- 每次将新版本的 CMake 添加到
vcpkg.cmake
采用的 CMake 版本通常必须回到 3.7.2- 特定函数和选项可能采用更高的 CMake 版本;如果是这种情况,请确保使用所需的 CMake 版本注释该函数或选项。
更改现有函数
- 切勿删除非内部函数中的参数;如果这些参数不再有效,只需将其视为如常,并在使用时发出警告。
- 切勿添加新的强制参数。
命名变量
cmake_parse_arguments
:将前缀设置为"arg"
局部变量使用
snake_case
命名内部全局变量名采用
Z_VCPKG_
作为前缀。外部试验性全局变量名采用
X_VCPKG_
作为前缀。内部函数采用
z_vcpkg_
作为前缀- 单个函数的内部函数(即帮助程序函数)命名为
[z_]<func>_<name>
,其中<func>
是其作为帮助程序的函数的名称,而<name>
是帮助程序函数的作用。- 如果
<func>
没有z_
,应在其前面添加z_
,但不要将帮助程序函数命名为z_z_foo_bar
。
- 如果
- 单个函数的内部函数(即帮助程序函数)命名为
公共全局变量命名为
VCPKG_
。