kmpkg 维护者指南
本文档列出了添加或更新端口脚本时应遵循的一系列规则,旨在起到类似以下文档的作用:
注册表整体设计目标
当前基线中的端口必须可同时安装
我们希望向官方维护注册表中库的下游用户表明,我们发布的任何给定基线中的库组合,至少在某些配置下已经过兼容性测试。允许端口相互排斥会破坏此类配置的测试能力,因为所需的构建次数会按 2^排斥情况数 的规模增长。此外,安装额外依赖项应始终被视为“安全操作”:端口或最终用户无法声明其需求中“不包含”某个依赖项。
若需为用户提供此类替代方案,建议在 portfile.cmake 中添加注释,说明如何创建覆盖端口来实现该替代形式,而非尝试添加未在官方维护注册表持续集成中构建的额外端口。例如,可参考 glad@0.1.36。
在注册表功能引入之前,我们曾接受过多个“作为替代方案的未测试端口”(如 boringssl),这类端口可简化覆盖端口的编写。但现在不再接受此类端口,因为注册表功能允许在不修改官方维护注册表的情况下发布这些未测试端口。
十六进制字符串使用小写形式
kmpkg 的许多功能依赖于十六进制字符串的比较,例如 SHA512 哈希、Git 提交 ID、树对象哈希等。
kmpkg 内部会对这类大小写无关的十六进制字符串进行小写归一化处理,但基于 kmpkg 基础设施构建的工具可能未考虑这一点。因此,要求在以下场景中统一使用小写十六进制字符串,以保证一致性:
- kmpkg 辅助函数中的
SHA512参数。 - kmpkg 辅助函数中的
REF参数(当值为十六进制字符串时)。 - 版本数据库文件中的
git-tree对象。 scripts/kmpkg-tools.json文件中的sha512对象。- 其他十六进制字符串大小写无关的场景。
拉取请求(PR)结构
每个端口单独创建拉取请求
尽可能将变更拆分到多个 PR 中,这会显著简化审核流程,并避免某一组变更的问题阻碍其他所有变更的合并。
避免在未修改的文件中进行无关变更
例如,不要在与当前问题无关的端口文件中进行格式化或变量重命名。但如果为了 PR 的主要目的(如更新库版本)需要修改该文件,那么修复拼写错误等明显有益的变更则是受欢迎的!
检查端口名称与其他仓库的冲突
端口名称应明确指向其对应的包,理想情况下,在搜索引擎中搜索端口名称应能快速定位到对应的项目。Repology 是一个很好的工具,可同时检查多个仓库中的包名是否存在冲突。
名称较短或基于常用词汇的项目可能需要添加区分标识,尤其是当该词汇与多个项目关联时。例如,仅命名为 ip 的端口是不可接受的,因为很可能存在多个同名项目。
有效的区分标识示例:
- 仓库所有者用户名或组织名:
google-cloud-cpp。 - 项目所属的库套件名称:
boost-dll。
C++ 和开源项目中常用的前缀和后缀不能作为有效区分标识,例如:
cppfreelibopen- 数字
例如,比较以下端口名称:ip-cpp、libip 和 ip5,移除无效区分标识后,它们的核心名称均为 ip,因此被视为同名端口。
例外情况:若名称与某个单一项目存在强关联,则可保留。例如:libpng、openssl 和 zlib。
为避免用户混淆,端口添加到公共注册表后,我们可能会限制其重命名频率。当前政策为每年最多允许一次重命名。
使用 GitHub 草稿拉取请求(Draft PR)
GitHub 草稿 PR 是获取 CI 或人工反馈的理想方式,适用于尚未准备好合并的工作。大多数新 PR 应先以草稿形式创建,待 CI 验证通过后再转换为正式 PR。
有关 GitHub 草稿 PR 的更多信息,请参阅 Introducing draft pull requests。
kmpkg 团队可能会在审核过程中将你的 PR 转换为草稿,通常会附带代码修改请求或说明何时可标记为“准备审核”(Ready for Review)。
关闭长期未活跃的 PR
为避免积累过期 PR,kmpkg 团队可能会关闭等待贡献者操作超过 60 天的 PR。倒计时从 kmpkg 维护者最后一次提出修改请求或反馈开始计算,若 60 天内无任何活动,该 PR 将被视为过期,kmpkg 团队可自行决定关闭。
端口文件(Portfiles)
避免使用已弃用的辅助函数
目前以下辅助函数已弃用:
kmpkg_extract_source_archive_ex()应替换为支持的kmpkg_extract_source_archive()重载(带ARCHIVE参数)。- 不带
ARCHIVE参数的已弃用kmpkg_extract_source_archive()重载应替换为带ARCHIVE参数的支持版本。 kmpkg_apply_patches()应替换为“提取”类辅助函数的PATCHES参数(例如kmpkg_from_github())。kmpkg_build_msbuild()应替换为kmpkg_install_msbuild()。kmpkg_copy_tool_dependencies()应替换为kmpkg_copy_tools()。kmpkg_configure_cmake应移除PREFER_NINJA后替换为kmpkg_cmake_configure()。kmpkg_build_cmake应替换为kmpkg_cmake_build()。kmpkg_install_cmake应替换为kmpkg_cmake_install()。kmpkg_fixup_cmake_targets应替换为kmpkg_cmake_config_fixup。
部分替代辅助函数位于“工具端口”中,允许使用者将其行为固定在特定版本。工具端口需添加到端口的 "dependencies" 中,示例如下:
{
"name": "kmpkg-cmake",
"host": true
},
{
"name": "kmpkg-cmake-config",
"host": true
}
避免在端口文件中添加过多注释
理想情况下,端口文件应简洁、简单且尽可能声明式。提交 PR 前,应移除 create 命令生成的任何模板注释。
端口不得依赖路径
端口的行为不应基于已安装的其他端口而改变,即不应影响自身安装的内容。例如,以下两种场景中,b 安装的文件必须完全一致:
> kmpkg install a
> kmpkg install b
> kmpkg remove a
和
> kmpkg install b
这意味着端口不应尝试检测其他端口是否在安装目录中提供了某个资源,再决定是否执行某项操作。此类“路径依赖”行为的一个常见原因将在下文“定义功能时,显式控制依赖项”中说明。
唯一端口归属规则
在整个 kmpkg 系统中,用户可能同时使用的任何两个端口不得提供相同的文件。若某个端口尝试安装另一个端口已提供的文件,安装将失败。若端口需要使用极其常见的头文件名,应将这些头文件放置在子目录中,而非直接放在 include 目录下。
持续集成会定期检查这一规则,尝试安装注册表中的所有端口。若两个端口提供相同文件,将触发 FILE_CONFLICTS 错误。
在 unofficial- 命名空间中添加 CMake 导出
kmpkg 的核心设计理念之一是避免对用户造成“锁定”。在构建系统中,依赖系统库与依赖 kmpkg 库应无差异。因此,我们避免使用“明显名称”为现有库添加 CMake 导出或目标,以便上游项目自行添加官方 CMake 导出时不会产生冲突。
为此,端口导出的所有非上游自带的 CMake 配置文件应添加 unofficial- 前缀,所有额外目标应位于 unofficial::<port>:: 命名空间中。
这意味着用户应通过以下方式使用 kmpkg 专属的包和目标:
find_package(unofficial-<port> CONFIG):获取 kmpkg 专属包。unofficial::<port>::<target>:该包导出的目标。
示例:
brotli创建unofficial-brotli包,并导出目标unofficial::brotli::brotli。
安装版权文件
每个端口必须在 ${CURRENT_PACKAGES_DIR}/share/${PORT} 目录下提供一个名为 copyright 的文件。若包的许可证内容可在源代码文件中获取,应通过调用 kmpkg_install_copyright() 创建该文件。kmpkg_install_copyright 还支持捆绑多个版权文件。
kmpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")
较旧的方法是使用 CMake 内置的 file 命令手动创建该文件,新端口不建议使用此方法,但仍允许保留现有用法:
file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright)
若上游源代码中的许可证内容并非文本形式(例如 PDF 文件),copyright 文件应说明用户如何获取许可证要求。若可能,还应包含指向原始源代码的链接,以便用户检查是否为最新版本。
file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/copyright" [[截至 2023 年 7 月 25 日,根据
https://github.com/GPUOpen-LibrariesAndSDKs/display-library/blob/master/Public-Documents/README.md#end-user-license-agreement
本软件受 "SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT"(SDK 许可协议)约束,该协议 PDF 位于
https://github.com/GPUOpen-LibrariesAndSDKs/display-library/blob/master/Public-Documents/ADL%20SDK%20EULA.pdf
]])
端口中的版本约束
端口中的版本约束应尽量避免,因为它们可能阻碍项目的独立演进。仅在有充分文档说明的情况下(例如已证实与特定早期版本不兼容),才允许添加此类约束。不得仅为与独立项目保持一致而添加版本约束。
MAYBE_UNUSED_VARIABLES 中的变量必须适用于至少一种配置
当向 MAYBE_UNUSED_VARIABLES 添加新变量以消除 CMake 配置步骤中的警告时,必须添加注释说明该变量适用的场景。若变量在任何配置中均不适用,则很可能存在潜在错误(例如变量名拼写错误),添加该变量对构建无实际影响。
kmpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
FEATURES
windowsfeature WINDOWS_OPTION
)
kmpkg_configure_cmake(
SOURCE_PATH "${SOURCE_PATH}"
OPTIONS
${FEATURE_OPTIONS}
MAYBE_UNUSED_VARIABLES
# 仅适用于 Windows 配置
WINDOWS_OPTION
)
功能(Features)
不得使用功能实现替代方案
功能必须被视为累加性功能。若 port[featureA] 可安装且 port[featureB] 可安装,则 port[featureA,featureB] 也必须可安装。此外,若第二个端口依赖 [featureA] 且第三个端口依赖 [featureB],安装这两个端口时应能满足其依赖需求。
此类场景中的库必须在 kmpkg 中选择一个可用选项,需要不同设置的用户目前必须使用覆盖端口。
以下现有示例因向后兼容而保留,但当前不再接受类似新端口:
libgit2、libzip、open62541均提供用于选择 TLS 或加密后端的功能;curl虽有不同的加密后端选项,但支持运行时选择,因此符合上述原则。darknet提供opencv2、opencv3功能,用于控制其依赖的 OpenCV 版本。
功能可启用预览或测试版功能
尽管有上述规定,但如果存在预览分支或类似分支,且预览功能不太可能破坏非预览功能(例如无 API 移除),则允许使用功能来控制该设置。
示例:
- Azure SDK 系列(形式为
azure-Xxx)提供public-preview功能。 imgui提供experimental-docking功能,用于启用其预览版 docking 分支(每个正式版本均附带该分支的合并提交)。
默认功能不得添加新 API
上游构建系统默认启用的功能,并不意味着应将其添加到
default-features中。default-features的设计目的并非模拟上游的决策,而是为 经典模式 用户提供便捷。
默认功能的目的是确保不了解功能机制的用户安装库时,能获得具备基本可用性的构建版本。例如,libarchive 通过功能启用现有通用接口的压缩算法支持;若未启用任何此类功能,该库可能无法发挥作用。
必须谨慎考虑是否将某个功能设为默认启用,因为禁用默认功能的操作较为复杂。
作为“传递依赖”的消费者,禁用默认功能需要:
- 所有用户通过
"default-features": false显式禁用默认功能,或在命令行的功能列表中包含[core]。 - 在
kmpkg install命令行中指定该传递依赖,或在顶层清单中将其声明为直接依赖。
在 kmpkg 官方维护注册表中,若某个功能会添加额外的 API、可执行文件或其他二进制文件,则该功能必须默认禁用。若存在疑问,不应将其标记为默认功能。
不得使用功能控制已发布接口中的替代方案
若某个端口的消费者仅依赖其核心功能,则启用该端口的其他功能不应破坏其现有功能。当替代方案不受消费者直接控制(例如由 /std:c++17 / -std=c++17 等编译器设置控制)时,这一点尤为重要。
以下现有示例因向后兼容而保留,但当前不再接受类似新端口:
redis-plus-plus[cxx17]控制一个兼容层,但未将该设置嵌入安装目录。ace[wchar]将所有 API 改为接受const wchar_t*而非const char*。
功能可使用别名替换兼容层(需嵌入安装目录)
尽管有上述规定,若满足以下条件,端口可通过功能移除兼容层:
- 启用功能后,兼容层被替换为被兼容实体的别名。
- 兼容层的状态已嵌入安装的头文件中,降低 ABI 不匹配导致的运行时错误风险。
- 端口的消费者能够编写在两种模式下均能工作的代码(例如通过使用条件编译的类型定义)。
示例:
abseil[cxx17]将absl::string_view替换为std::string_view;相关 补丁 实现了嵌入要求。
推荐解决方案
若必须暴露底层替代方案,建议在构建时输出提示信息,指导用户将端口复制到私有覆盖端口:
set(USING_DOG 0)
message(STATUS "此版本的 LibContoso 使用 Kittens 后端。若需使用 Dog 后端,请创建该端口的覆盖端口,将 USING_DOG 设为 1,并将 `kittens` 依赖替换为 `dog`。")
message(STATUS "此脚本路径:${CMAKE_CURRENT_LIST_DIR}")
message(STATUS "覆盖端口文档:https://github.com/kumose/kmpkg/blob/master/docs/specifications/ports-overlay.md")
构建技术
不得使用嵌入式依赖(Vendored Dependencies)
不得使用嵌入式库副本,所有依赖项应单独拆分并打包,以便进行更新和维护。
嵌入式依赖会带来多项与 kmpkg 目标(提供可靠、一致、可维护的包管理系统)冲突的挑战:
- 更新困难:嵌入式库副本难以跟踪和应用上游项目的更新(包括安全补丁),可能导致生态系统中存在安全风险和过时依赖。
- 符号冲突:当多个包包含同一库的不同版本时,可能引发符号冲突。 例如:若包 A 嵌入式包含库 X(1 版本),包 B 嵌入式包含库 X(2 版本),则链接这两个包的应用可能出现运行时错误或未定义行为。 通过单独打包依赖项,kmpkg 确保所有包使用同一版本的库,从而消除此类冲突。
- 许可证合规性:嵌入式依赖可能掩盖嵌入式库的许可证信息,可能违反许可证条款或导致兼容性问题。
- 维护负担增加:保持嵌入式依赖与上游版本同步需要大量人工工作,且往往导致跨包的重复劳动。
优先使用 CMake
当存在多种构建系统时,优先使用 CMake。此外,在合适的情况下,使用 file(GLOB) 指令将其他构建系统重写为 CMake 可能更简单且更易于维护。
示例:abseil
选择静态或动态二进制文件
构建 CMake 库时,kmpkg_cmake_configure() 会根据用户请求的变体,传递正确的 BUILD_SHARED_LIBS 值。
可通过 string(COMPARE EQUAL "${KMPKG_LIBRARY_LINKAGE}" ...) 计算其他配置参数:
# portfile.cmake
string(COMPARE EQUAL "${KMPKG_LIBRARY_LINKAGE}" "static" KEYSTONE_BUILD_STATIC)
string(COMPARE EQUAL "${KMPKG_LIBRARY_LINKAGE}" "dynamic" KEYSTONE_BUILD_SHARED)
kmpkg_cmake_configure(
SOURCE_PATH ${SOURCE_PATH}
OPTIONS
-DKEYSTONE_BUILD_STATIC=${KEYSTONE_BUILD_STATIC}
-DKEYSTONE_BUILD_SHARED=${KEYSTONE_BUILD_SHARED}
)
若库未提供选择构建变体的配置选项,则必须通过补丁修改构建流程。补丁时应尽量提高端口的未来可维护性,通常意味着最小化修复问题所需修改的代码行数。
示例:补丁 CMake 库以避免构建不需要的变体
例如,补丁基于 CMake 的库时,只需为不需要的目标添加 EXCLUDE_FROM_ALL,并将 install(TARGETS ...) 调用包裹在 if(BUILD_SHARED_LIBS) 中即可。这比包裹或删除提及不需要变体的每一行代码更简洁。
假设项目 CMakeLists.txt 包含以下内容:
add_library(contoso SHARED contoso.c)
add_library(contoso_static STATIC contoso.c)
install(TARGETS contoso contoso_static EXPORT ContosoTargets)
install(EXPORT ContosoTargets
FILE ContosoTargets
NAMESPACE contoso::
DESTINATION share/contoso)
只需补丁 install(TARGETS) 行:
add_library(contoso SHARED contoso.c)
add_library(contoso_static STATIC contoso.c)
if(BUILD_SHARED_LIBS)
set_target_properties(contoso_static PROPERTIES EXCLUDE_FROM_ALL 1)
install(TARGETS contoso EXPORT ContosoTargets)
else()
set_target_properties(contoso PROPERTIES EXCLUDE_FROM_ALL 1)
install(TARGETS contoso_static EXPORT ContosoTargets)
endif()
install(EXPORT ContosoTargets
FILE ContosoTargets
NAMESPACE contoso::
DESTINATION share/contoso)
定义功能时,显式控制依赖项
定义捕获可选依赖的功能时,需确保该功能未显式启用时,依赖项不会被意外使用。
set(CMAKE_DISABLE_FIND_PACKAGE_ZLIB ON)
set(CMAKE_REQUIRE_FIND_PACKAGE_ZLIB OFF)
if ("zlib" IN_LIST FEATURES)
set(CMAKE_DISABLE_FIND_PACKAGE_ZLIB OFF)
set(CMAKE_REQUIRE_FIND_PACKAGE_ZLIB ON)
endif()
kmpkg_cmake_configure(
SOURCE_PATH ${SOURCE_PATH}
OPTIONS
-DCMAKE_DISABLE_FIND_PACKAGE_ZLIB=${CMAKE_DISABLE_FIND_PACKAGE_ZLIB}
-DCMAKE_REQUIRE_FIND_PACKAGE_ZLIB=${CMAKE_REQUIRE_FIND_PACKAGE_ZLIB}
)
以下使用 kmpkg_check_features() 的代码片段功能等效:
kmpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
FEATURES
"zlib" CMAKE_REQUIRE_FIND_PACKAGE_ZLIB
INVERTED_FEATURES
"zlib" CMAKE_DISABLE_FIND_PACKAGE_ZLIB
)
kmpkg_cmake_configure(
SOURCE_PATH ${SOURCE_PATH}
OPTIONS
${FEATURE_OPTIONS}
)
代码片段中的 ZLIB 区分大小写。有关更多信息,请参阅 CMAKE_DISABLE_FIND_PACKAGE_<PackageName> 和 CMAKE_REQUIRE_FIND_PACKAGE_<PackageName> 文档。
将冲突库放置在 manual-link 目录中
若库存在以下情况,则视为冲突库:
- 定义
main函数。 - 定义
malloc函数。 - 定义其他库中也声明的符号。
冲突库通常是设计使然,并非缺陷。由于某些构建系统会链接 lib 目录中的所有库,因此应将这些冲突库移至名为 manual-link 的子目录中。
安装预构建二进制文件
允许但强烈不建议安装预构建(仅二进制)制品,前提是它们不会有效阻止其他端口的版本更新。优先从源代码构建,因为这样可以尊重所有 kmpkg 中影响编译器或编译标志的设置。
若端口满足以下所有条件,将被拒绝:
- 安装预构建二进制文件而非从源代码构建。
- 这些二进制文件(或运行时)依赖官方维护注册表中的其他端口。
- 安装的制品进入 kmpkg 的“已发布链接域”——即安装下游端口或用户项目预期链接的库/头文件/CMake 或 pkg-config 元数据。
理由:这种组合实际上将依赖图的 ABI 锁定到预构建制品生成时使用的版本。kmpkg 无法安全更新(例如)zlib、openssl 等依赖项,否则可能导致链接预构建库的消费者出现潜在的 ODR(单一定义规则)/ABI 兼容性破坏,且用户可能错过关键安全补丁。
“进入已发布链接域”通常指以下任何一种情况:
- 安装供消费者链接的
.lib、.a、.so、.dylib或导入库。 - 提供直接或通过内联/模板代码引用其他 kmpkg 端口符号、类型或宏的头文件。
- 安装在
find_dependency()/Requires:中引用其他 kmpkg 端口的 CMake 配置文件/pkg-config 文件。
允许(但仍不建议)的场景:
- 仅用于构建时的宿主辅助工具(可执行文件),其输出被消费但本身不被依赖端口链接,且要么私下捆绑依赖项,要么仅依赖普遍存在的系统运行时库。
- 完全自包含的预构建库,静态链接所有开源依赖项,且不通过安装的头文件或导出接口暴露其符号或类型(消费者无法观察或依赖传递 ABI)。
- 仅包含数据、固件或资源的包,不链接到用户代码。
禁止的示例:
- 预构建的
libfoo,安装lib/libfoo.lib及包含<zlib.h>的头文件,且编译时依赖特定版本的zlib;消费者链接libfoo时预期兼容性。 - 预构建的 SDK,安装调用
find_dependency(OpenSSL)的 CMake 包文件,但二进制文件编译时依赖较旧的 OpenSSL 版本。
缓解措施/替代方案:
- 使用上游脚本提供从源代码构建的方式,或添加轻量级 CMake 包装器。
- 要求上游发布基于源代码的版本或可重现的构建说明;在
portfile.cmake或kmpkg.json的注释中链接上游相关议题/PR。 - 对于无法满足这些规则的组织特定预构建制品,使用 覆盖端口 或私有注册表。
版本控制
遵循 "version" 字段的通用约定
创建新端口时,遵循包作者使用的版本控制约定;更新端口时,继续使用相同约定,除非上游明确说明变更。有关约定的完整说明,请参阅 版本控制文档。
若上游已长时间未发布版本,不应将端口的版本控制方案改为 version-date 以获取最新变更(这些提交可能包含非生产就绪的修改)。应要求上游仓库发布新版本。
更新所有修改端口的清单文件中的 "port-version" 字段
kmpkg 使用该字段判断某个端口是否过时,当端口行为发生变更时,必须更新该字段。
约定:"port-version" 字段用于标记未变更上游版本的端口修改;当更新上游版本时,需将 "port-version" 重置为 0。
示例:
- Zlib 的包版本当前为
1.2.1,未显式指定"port-version"(等效于"port-version": 0)。 - 你发现部署了错误的版权文件,并在端口文件中修复了该问题。
- 应将清单文件中的
"port-version"字段更新为1。
有关更多信息,请参阅 版本控制文档。
更新所有修改端口在 versions/ 中的版本文件
kmpkg 使用一组元数据文件支持版本控制功能,这些文件位于以下位置:
${KMPKG_ROOT}/versions/baseline.json(所有端口共用)。${KMPKG_ROOT}/versions/${端口名首字母}-/${端口名}.json(每个端口单独一个文件)。
例如,zlib 对应的版本文件为:
${KMPKG_ROOT}/versions/baseline.json${KMPKG_ROOT}/versions/z-/zlib.json
每次更新端口时,必须同步更新其版本文件。
推荐的更新方法是运行 x-add-version 命令,例如:
kmpkg x-add-version zlib
若同时更新多个端口,可运行以下命令一次性更新所有修改端口的文件:
kmpkg x-add-version --all
补丁(Patching)
kmpkg 是一个打包解决方案,而非所部署组件的最终所有者。但在某些情况下,我们需要应用补丁以提高组件与平台的兼容性,或组件之间的兼容性。
- 应避免以下类型的补丁:
- 上游可能不同意的补丁。
- 导致漏洞或崩溃的补丁。
- 我们无法在 upstream 版本更新后维护的补丁。
- 规模大到可能与 kmpkg 仓库本身产生许可证纠缠的补丁。
为与上游相关的补丁通知上游所有者
若补丁可能对上游有用,必须将补丁内容通知上游(与上游无关的 kmpkg 特定行为补丁,如移除嵌入式依赖,无需通知)。
为避免上游不同意补丁的情况,此类补丁需等待至少 30 天后再应用。
若高度确信变更正确,可跳过等待期。高度确信的补丁示例包括(但不限于):
- 上游已接受的补丁(例如,从上游已合并的 PR 中反向移植特定变更)。
- 添加缺失的
#include指令。 - 小型且明显的产品代码修复(例如,初始化未初始化的变量)。
- 禁用 kmpkg 中无关的构建组件(如测试或示例)。
优先使用选项而非补丁
在调用 kmpkg_configure_xyz() 时设置选项,比直接补丁设置更可取。
可避免补丁的常见选项:
- [MSBUILD] 项目文件中的
<PropertyGroup>设置可通过/p:参数覆盖。 - [CMAKE] CMake 脚本中的
find_package(XYz)调用可通过-DCMAKE_DISABLE_FIND_PACKAGE_XYz=ON禁用。 - [CMAKE] 缓存变量(声明为
set(VAR "value" CACHE STRING "文档")或option(VAR "文档" "默认值"))可通过命令行传递-DVAR:STRING=Foo覆盖。值得注意的例外是,若set()调用使用了FORCE参数。有关更多信息,请参阅 CMakeset文档。
优先下载已批准的补丁,而非将其检入端口
若可从上游获取已批准或已合并的补丁文件,端口应尝试下载并应用这些补丁,而非将其作为端口文件的一部分检入。
这种方式更优,原因如下:
- 确认上游已接受补丁变更。
- 将审核责任转移给上游,简化 kmpkg 的审核流程。
- 减少未使用该补丁的用户的 kmpkg 仓库体积。
- 避免与 kmpkg 仓库产生许可证冲突。
补丁应从稳定的端点下载,以避免 SHA 冲突。从 GitHub 和 GitLab 的拉取请求或提交中下载补丁文件时,应在下载 URL 后添加 ?full_index=1 参数。
示例:
https://github.com/google/farmhash/pull/40.diff?full_index=1https://github.com/linux-audit/audit-userspace/commit/f8e9bc5914d715cdacb2edc938ab339d5094d017.patch?full_index=1https://gitlab.kitware.com/paraview/paraview/-/merge_requests/6375.diff?full_index=1
优先补丁而非覆盖 KMPKG_<VARIABLE> 值
部分 KMPKG_<VARIABLE> 前缀的变量有对应的 CMAKE_<VARIABLE>,但并非所有变量都会传递到内部包构建中(参见实现:Windows 工具链)。
考虑以下示例:
set(KMPKG_C_FLAGS "-O2 ${KMPKG_C_FLAGS}")
set(KMPKG_CXX_FLAGS "-O2 ${KMPKG_CXX_FLAGS}")
使用 kmpkg 内置工具链时,这一写法有效,因为 KMPKG_<LANG>_FLAGS 的值会转发到对应的 CMAKE_LANG_FLAGS 变量。但不了解 kmpkg 变量的自定义工具链不会进行转发。
因此,设置 CMAKE_<LANG>_FLAGS 时,更建议直接补丁构建系统。
最小化补丁
修改库时,应尽量减小最终的差异。这意味着修改某个区域时,不应重新格式化上游源代码;禁用条件语句时,最好在条件中添加 AND FALSE 或 && 0,而非删除条件中的每一行;若需禁用大范围区域,可在该区域周围添加 if(0) 或 #if 0,而非删除每一行。
若端口已过时,且更新到较新的已发布版本可解决相同问题,则不应添加补丁。kmpkg 更倾向于更新端口而非补丁过时版本。
这有助于减小 kmpkg 仓库体积,并提高补丁对未来代码版本的适用性。
不得在补丁中实现功能
kmpkg 中补丁的目的是实现与编译器、库和平台的兼容性,而非替代正确的开源流程(提交议题/PR 等)来实现新功能。
默认不构建测试/文档/示例
提交新端口时,检查是否存在 BUILD_TESTS、WITH_TESTS 或 POCO_ENABLE_SAMPLES 等选项,并确保禁用这些额外二进制文件的构建。这可最小化普通用户的构建时间和依赖项。
可选地,可添加 test 功能以启用测试构建,但该功能不应包含在 Default-Features 列表中。
支持现有库用户迁移到 kmpkg
不得添加 CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS
除非库的作者已在使用该功能,否则不应使用此 CMake 功能,因为它与 C++ 模板交互不良,且会破坏某些编译器功能。未提供 .def 文件且未使用 __declspec() 声明的库,在 Windows 上不支持动态构建,应明确标记:
if(KMPKG_TARGET_IS_WINDOWS)
kmpkg_check_linkage(ONLY_STATIC_LIBRARY)
endif()
不得重命名上游指定的二进制文件
这意味着,若上游库的发布版和调试版名称不同(如 libx 与 libxd),则不应将调试版重命名为 libx;反之,若上游库的发布版和调试版名称相同,也不应引入新名称。
重要说明:
- 静态和动态变体通常应重命名为统一格式,以便消费者使用统一名称,而无需关注下游链接方式。这是安全的,因为我们每次仅提供一种变体。
若库生成 CMake 集成文件(foo-config.cmake),必须通过补丁 CMake 构建本身来重命名,而非简单地对输出归档文件/LIB 文件调用 file(RENAME)。
最后,Windows 上的 DLL 文件绝不应在构建后重命名,因为这会破坏生成的 LIB 文件。
清单文件格式化
要求清单文件必须格式化。使用以下命令格式化所有清单文件:
> kmpkg format-manifest --all
三元组(Triplets)
目前不接受添加非社区三元组的请求。社区三元组升级为正式三元组的主要依据是测试该三元组的硬件预算,且将根据 kmpkg 提交的指标来决定,以最大化实际使用场景的测试覆盖率。
若满足以下条件,将添加社区三元组:
- 能证明用户确实会使用该社区三元组。
- 已知该三元组可用(无明显问题)。
例如,我们未在 https://github.com/kumose/kmpkg/pull/29034 中添加三元组,因为作者仅为“完善集合”而非实际使用;我们在实现使结果可重定位的 patchelf 解决方案后,才添加了 linux-dynamic 三元组。
实用实现说明
端口文件以脚本模式运行
尽管 portfile.cmake 和 CMakeLists.txt 共享通用语法和核心 CMake 语言结构(即“脚本命令”),但端口文件以“脚本模式”运行,而 CMakeLists.txt 文件以“项目模式”运行。两种模式的最主要区别是,“脚本模式”没有“工具链”“语言”和“目标”的概念。任何依赖这些概念的行为(例如 CMAKE_CXX_COMPILER、CMAKE_EXECUTABLE_SUFFIX、CMAKE_SYSTEM_NAME)都将无法正常工作。
端口文件可直接访问三元组文件中设置的变量,但 CMakeLists.txt 不能(尽管通常会有转换机制——如 KMPKG_LIBRARY_LINKAGE 与 BUILD_SHARED_LIBS)。
端口文件和端口调用的项目构建在不同进程中运行,概念上如下:
+----------------------------+ +------------------------------------+
| CMake.exe | | CMake.exe |
+----------------------------+ +------------------------------------+
| 三元组文件 | ====> | 工具链文件 |
| (x64-windows.cmake) | | (scripts/buildsystems/kmpkg.cmake) |
+----------------------------+ +------------------------------------+
| 端口文件 | ====> | CMakeLists.txt |
| (ports/foo/portfile.cmake) | | (buildtrees/../CMakeLists.txt) |
+----------------------------+ +------------------------------------+
在端口文件中判断宿主环境时,可使用标准 CMake 变量(CMAKE_HOST_WIN32)。
在端口文件中判断目标环境时,应使用 kmpkg 三元组变量(KMPKG_CMAKE_SYSTEM_NAME)。
有关可能的设置的完整列表,请参阅 三元组文档。