kmpkg CMake 编码风格指南
我们要求以下所有 CMake 脚本均需遵循本文档规定的准则:
scripts/目录下的脚本- 所有
kmpkg-*端口中的脚本
现有脚本可能尚未遵循这些准则;我们计划持续更新旧脚本,使其符合本指南要求。
制定这些准则的目的是保证脚本的稳定性,同时简化向前和向后兼容性的维护工作。
核心准则
-
除输出参数外,始终使用
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}")
- 示例(合法引用):
-
优先使用函数(function),禁止使用宏(macro)或顶层代码。
- 例外:“脚本本地辅助宏”——偶尔定义小型宏会更便捷,但应尽量少用,优先选择函数。
- 例外:
kmpkg.cmake中的find_package逻辑。
-
scripts目录下的脚本,在正常运行过程中不应出现需要人工修改的可观测变更。- 违规示例:
kmpkg_acquire_msys()中硬编码的包名和版本号,因 MSYS 项目移除旧包而需频繁更新。 - 合法例外:
kmpkg_from_sourceforge()中的镜像列表虽需维护,但不会对调用方产生可观测的行为影响。
- 违规示例:
-
变量引用的引号规则:CMake 中有三种参数形式——无引号(
foo(BAR))、带引号(foo("BAR"))、括号包裹式(foo([[BAR]]))。需遵循以下规则正确使用引号:- 若参数包含变量展开
${...},必须加引号。- 例外:“展开式变量替换”(单个变量需作为多个参数传递给函数),此时直接使用
${foo}即可:kmpkg_list(SET working_directory)
if(DEFINED "arg_WORKING_DIRECTORY")
kmpkg_list(SET working_directory WORKING_DIRECTORY "${arg_WORKING_DIRECTORY}")
endif()
# 若未定义 arg_WORKING_DIRECTORY,则调用 do_the_thing();
# 否则调用 do_the_thing(WORKING_DIRECTORY "${arg_WORKING_DIRECTORY}")
do_the_thing(${working_directory})
- 例外:“展开式变量替换”(单个变量需作为多个参数传递给函数),此时直接使用
- 若参数包含除
\\、\"、\$外的其他转义序列,必须使用带引号的形式。- 示例:
"foo\nbar"必须加引号。
- 示例:
- 若参数包含
\、"或$,应使用括号包裹式。- 示例:
set(x [[foo\bar]])
set(y [=[foo([[bar\baz]])]=])
- 示例:
- 若参数包含非字母、数字或
_的字符,应加引号。 - 其他情况,使用无引号形式。
- 例外:
if()语句中类型为<variable|string>的参数必须加引号:- 比较运算符(
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 "")显式初始化为空; - 列表变量需通过
kmpkg_list(SET foo)显式初始化为空。
- 字符串变量需通过
-
禁止使用
set(var)。- 取消变量定义:
unset(var); - 设置为空字符串:
set(var ""); - 设置为空列表:
kmpkg_list(SET var)。
注:空字符串和空列表在 CMake 中值相同;上述写法仅为语义上的区分,无结果差异。
- 取消变量定义:
-
所有跨 API 边界(即非文件本地函数)从父作用域继承的变量均需文档说明。 三元组文件中提及的所有变量视为已完成文档说明。
-
输出参数仅在
PARENT_SCOPE中赋值,且禁止读取输出参数。 可使用辅助函数z_kmpkg_forward_output_variable()实现输出参数跨函数作用域的传递。 -
CACHE变量仅用于以下场景:- 强耦合函数间共享的全局变量;
- 单个函数内避免重复计算的内部状态。
此类变量应极少使用,且需添加
Z_KMPKG_前缀,避免与其他代码定义的本地变量冲突。 - 示例:
kmpkg_cmake_configure中的Z_KMPKG_CMAKE_GENERATOR;z_kmpkg_get_cmake_vars中的Z_KMPKG_GET_CMAKE_VARS_FILE。
-
仅允许在
ports.cmake或kmpkg-port-config.cmake中使用include()。 -
foreach(RANGE)的参数必须满足:- 所有参数均为自然数;
<start>必须小于等于<stop>。- 需通过如下方式检查:
if("${start}" LESS_EQUAL "${end}")
foreach(RANGE "${start}" "${end}")
...
endforeach()
endif()
-
所有基于端口的脚本必须使用
include_guard(GLOBAL),避免被重复包含。
要求的 CMake 版本
-
除
kmpkg.cmake外,所有 CMake 脚本可假定使用ports.cmake中cmake_minimum_required指定的 CMake 版本。- 每当有新版本 CMake 加入
kmpkgTools.xml,需同步提升ports.cmake及所有辅助CMakeLists.txt文件中的cmake_minimum_required版本。
- 每当有新版本 CMake 加入
-
kmpkg.cmake需兼容至 CMake 3.7.2 版本。- 特定函数或选项可依赖更高版本的 CMake;若如此,需在该函数/选项处注释说明所需的最低 CMake 版本。
修改现有函数的规则
-
非内部函数禁止移除参数;若参数不再生效,仍需正常接收该参数,并在使用时抛出警告。
-
禁止新增必选参数。
变量命名规则
cmake_parse_arguments:前缀统一设为"arg"。- 本地变量:使用蛇形命名法(snake_case)。
- 内部全局变量:前缀为
Z_KMPKG_。 - 实验性外部全局变量:前缀为
X_KMPKG_。 - 内部函数:前缀为
z_kmpkg_。- 仅作用于单个函数的内部辅助函数:命名格式为
[z_]<func>_<name>,其中<func>为所属主函数名,<name>为辅助函数的功能描述。- 若
<func>无z_前缀,需为辅助函数添加z_;但禁止命名为z_z_foo_bar形式。
- 若
- 仅作用于单个函数的内部辅助函数:命名格式为
- 公共全局变量:前缀为
KMPKG_。