跳到主要内容

kmpkg CMake 编码风格指南

我们要求以下所有 CMake 脚本均需遵循本文档规定的准则:

  • scripts/ 目录下的脚本
  • 所有 kmpkg-* 端口中的脚本

现有脚本可能尚未遵循这些准则;我们计划持续更新旧脚本,使其符合本指南要求。

制定这些准则的目的是保证脚本的稳定性,同时简化向前和向后兼容性的维护工作。

核心准则

  1. 除输出参数外,始终使用 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()
  2. 不允许存在未解析或未使用的参数。 需始终检查 ARGNarg_UNPARSED_ARGUMENTS

    • 尽可能抛出致命错误(FATAL_ERROR);
    • 若为兼容旧版本确有必要,可抛出警告(WARNING)。
  3. 所有 cmake_parse_arguments 调用必须使用 PARSE_ARGV 参数。

  4. 所有 foreach 循环必须使用 IN LISTSIN ITEMSRANGE 语法。

  5. 除非在面向用户的提示信息中,否则禁止引用 ${ARGV}${ARGN} 变量。

    • 示例(合法引用):message(FATAL_ERROR "blah was passed extra arguments: ${ARGN}")
  6. 优先使用函数(function),禁止使用宏(macro)或顶层代码。

    • 例外:“脚本本地辅助宏”——偶尔定义小型宏会更便捷,但应尽量少用,优先选择函数。
    • 例外:kmpkg.cmake 中的 find_package 逻辑。
  7. scripts 目录下的脚本,在正常运行过程中不应出现需要人工修改的可观测变更。

    • 违规示例:kmpkg_acquire_msys() 中硬编码的包名和版本号,因 MSYS 项目移除旧包而需频繁更新。
    • 合法例外:kmpkg_from_sourceforge() 中的镜像列表虽需维护,但不会对调用方产生可观测的行为影响。
  8. 变量引用的引号规则: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> 的参数必须加引号:
      • 比较运算符(EQUALSTREQUALVERSION_LESS 等)的两个参数;
      • MATCHESIN_LIST 的第一个参数。
      • 示例:
        if("${FOO}" STREQUAL "BAR") # ...
        if("${BAZ}" EQUAL "0") # ...
        if("FOO" IN_LIST list_variable) # ...
        if("${bar}" MATCHES [[a[bcd]+\.[bcd]+]]) # ...
      • 对于单个表达式,或不接收 <variable|string> 类型参数的其他谓词,遵循常规引号规则。
  9. 禁止使用“指针”型或“输入输出”型参数(用户传递变量名而非变量内容),仅允许使用简单的输出参数。

  10. 不假设变量默认为空。 若变量仅用于本地作用域:

    • 字符串变量需通过 set(foo "") 显式初始化为空;
    • 列表变量需通过 kmpkg_list(SET foo) 显式初始化为空。
  11. 禁止使用 set(var)

    • 取消变量定义:unset(var)
    • 设置为空字符串:set(var "")
    • 设置为空列表:kmpkg_list(SET var)

    注:空字符串和空列表在 CMake 中值相同;上述写法仅为语义上的区分,无结果差异。

  12. 所有跨 API 边界(即非文件本地函数)从父作用域继承的变量均需文档说明。 三元组文件中提及的所有变量视为已完成文档说明。

  13. 输出参数仅在 PARENT_SCOPE 中赋值,且禁止读取输出参数。 可使用辅助函数 z_kmpkg_forward_output_variable() 实现输出参数跨函数作用域的传递。

  14. CACHE 变量仅用于以下场景:

    • 强耦合函数间共享的全局变量;
    • 单个函数内避免重复计算的内部状态。 此类变量应极少使用,且需添加 Z_KMPKG_ 前缀,避免与其他代码定义的本地变量冲突。
    • 示例:
      • kmpkg_cmake_configure 中的 Z_KMPKG_CMAKE_GENERATOR
      • z_kmpkg_get_cmake_vars 中的 Z_KMPKG_GET_CMAKE_VARS_FILE
  15. 仅允许在 ports.cmakekmpkg-port-config.cmake 中使用 include()

  16. foreach(RANGE) 的参数必须满足:

    • 所有参数均为自然数;
    • <start> 必须小于等于 <stop>
    • 需通过如下方式检查:
      if("${start}" LESS_EQUAL "${end}")
      foreach(RANGE "${start}" "${end}")
      ...
      endforeach()
      endif()
  17. 所有基于端口的脚本必须使用 include_guard(GLOBAL),避免被重复包含。

要求的 CMake 版本

  1. kmpkg.cmake 外,所有 CMake 脚本可假定使用 ports.cmakecmake_minimum_required 指定的 CMake 版本。

    • 每当有新版本 CMake 加入 kmpkgTools.xml,需同步提升 ports.cmake 及所有辅助 CMakeLists.txt 文件中的 cmake_minimum_required 版本。
  2. kmpkg.cmake 需兼容至 CMake 3.7.2 版本。

    • 特定函数或选项可依赖更高版本的 CMake;若如此,需在该函数/选项处注释说明所需的最低 CMake 版本。

修改现有函数的规则

  1. 非内部函数禁止移除参数;若参数不再生效,仍需正常接收该参数,并在使用时抛出警告。

  2. 禁止新增必选参数。

变量命名规则

  1. cmake_parse_arguments:前缀统一设为 "arg"
  2. 本地变量:使用蛇形命名法(snake_case)。
  3. 内部全局变量:前缀为 Z_KMPKG_
  4. 实验性外部全局变量:前缀为 X_KMPKG_
  5. 内部函数:前缀为 z_kmpkg_
    • 仅作用于单个函数的内部辅助函数:命名格式为 [z_]<func>_<name>,其中 <func> 为所属主函数名,<name> 为辅助函数的功能描述。
      • <func>z_ 前缀,需为辅助函数添加 z_;但禁止命名为 z_z_foo_bar 形式。
  6. 公共全局变量:前缀为 KMPKG_