维护指南
本文档列出了添加或更新端口配方时应应用的一组策略。
它的目的是服务于以下角色: Debian 的政策手册, Homebrew 的维护者指南,以及 Homebrew 的配方食谱。
总体注册表设计目标
当前基线中的端口必须可同时安装
如果您希望向用户展示这种替代情况,请考虑描述如何有人可以创建一个 overlay port 来实现替代方案
在portfile.cmake中添加注释,而不是尝试添加从未内置的其他端口策划的注册表的持续集成。例如,参见
glad@0.1.36。
在引入 registries 之前,我们接受了一些未经测试的端口替代方案,例如boringssl,可能会使创作覆盖端口
更轻松。这不再被接受,因为注册表允许发布这些未经测试的端口无需修改策划的注册表。
PR 结构
为每个端口发出单独 的拉取请求
尽可能将更改分为多个 PR。 这样更容易进行审查,并可防止一组更改的问题妨碍所有其他更改。
避免对未触及的文件进行微小的更改
例如,避免重新格式化或重命名端口文件中的变量,否则没有理由针对当前问题进行修改。但是,如果您需要出于 PR 的主要目的(更新库)修改文件, 那么显然有益的改变,如修复拼写错误,将受到赞赏!
对照其他存储库检查名称
端口名称应尽量明确端口是哪个包安装。理想情况下,在搜索引擎中搜索端口名称应该很快 引导您到相应的项目。服务很好,可以检查很多包裹一次跨多个存储库的名称是Repology。
名称简称或以常用词命名的项目可能需要消除歧义,特别是当没有具有强关联的项目时 到给定的单词。例如,名称为“ip”的端口是不可接受的因为多个项目很可能会被类似地命名。
好的消歧器的例子有:
- 存储库的所有者用户名或组织:
google-cloud-cpp。 - 该项目所属的一组库的名称:
boost-dll。
C++ 和开源项目使用的常见前缀和后缀无效 消歧符,一些示例包括但不限于:
cpp,free,lib,open,- numbers
例如,在比较以下端口名称时:ip-cpp、libip 和ip5 并删除无效的消歧符,它们都被简化为相同的
干(ip),因此被认为具有相同的名称。
对于强关联的名称,此准则有一个例外与单个项目。例如:libpng、openssl和zlib。
Portfiles
避免弃用的辅助函数
目前,以下助手已被弃用:
- 不推荐使用的不带“ARCHIVE”的“kmpkg_extract_source_archive()”重载应替换为支持的带“ARCHIVE”的重载。
kmpkg_copy_tool_dependency()应替换为kmpkg_copy_tools()- 删除“PREFER_NINJA”后,“kmpkg_configure_cmake”应替换为
kmpkg_cmake_configure() kmpkg_build_cmake应替换为kmpkg_cmake_build()kmpkg_install_cmake应替换为kmpkg_cmake_install()kmpkg_fixup_cmake_targets应替换为kmpkg_cmake_config_fixup
一些替换帮助程序功能位于“工具端口”中,以允许消费者固定他们的特定版本的行为,以允许将帮助程序的行为锁定在特定版本 版本。工具端口需要添加到端口的“依赖项”中,如下所示:
{
"name": "kmpkg-cmake",
"host": true
},
{
"name": "kmpkg-cmake-config",
"host": true
}
避免端口文件中过多的注释
理想情况下, 端口文件应该简短、简单并且尽可能具有声明性。在提交 PR 之前,删除“create”命令引入的所有样板注释。
端口不得依赖于路径
端口不得根据已安装的端口来更改其行为,从而改变端口安装的内容。例如,给定:
> kmpkg install a
> kmpkg install b
> kmpkg remove a
and
> kmpkg install b
无论先前安装的a的影响如何,b安装的文件必须相同。这意味着端口在采取某些操作之前不得尝试检测另一个端口是否在已安装的树中提供了某些内容。下面的“定义功能时,显式控制依赖关系”中描述了这种“路径依赖”行为的具体且常见的原因。
唯一端口属性规则
在整个 kmpkg 系统中,用户预计同时使用的两个端口不可能提供相同的文件。如果端口尝试安装另一个文件已提供的文件,安装将失败。例如,如果端口想要对标头使用极其常见的名称,则应该将这些标头放置在子目录中而不是include中。
通过持续集成运行定期检查此属性,该运行尝试在注册表中安装所有端口,如果两个端口提供相同的文件,则会因FILE_CONFLICTS而失败。
在非官方命名空间中添加 CMake 导出
kmpkg 的核心设计理念是不要为用户造成“锁定”。 在构建系统中,根据系统中的库和 kmpkg 中的库,不应有区别。 为此,我们避免将 CMake 导出或目标添加到具有“明显名称”的现有库,以允许上游添加自己的官方 CMake 导出,而不会与 kmpkg 冲突。
为此,端口导出的任何 CMake 配置(不在上游库中)都应将 unofficial- 作为前缀。 任何其他目标都应位于 unofficial::<port>:: 命名空间中。
这意味着用户应看到:
find_package(unofficial-<port> CONFIG)作为获取unique-to-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命令。在新端口中不鼓励这样做,有利于kmpkg_install_copyright,但仍然允许。
file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright)
如果上游源文件中的许可证内容不是文本形式(例如 PDF 文件), 则“版权”应包含用户如何找到许可证要求的说明。如果可能的话,它还应该包含指向原始源文件的链接来表明这一点,以便用户可以检查它是否是最新的。
file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/copyright" [[As of 2023-07-25, according to
https://github.com/GPUOpen-LibrariesAndSDKs/display-library/blob/master/Public-Documents/README.md#end-user-license-agreement
this software is bound by the "SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT" PDF located at
https://github.com/GPUOpen-LibrariesAndSDKs/display-library/blob/master/Public-Documents/ADL%20SDK%20EULA.pdf
]])
端口中的版本限制
通常应该避免端口内的版本限制,因为它们会阻碍项目的独立发展。仅当有充分记录的理由时才允许添加此类约束,例如已证明与特定早期版本不兼容。这些限制不应仅仅用于维持与独立项目的平等。
功能
请勿使用功能实现备选方案
功能必须被视为附加功能。如果安装了port[featureA]并且安装了port[featureB],则必须安装port[featureA,featureB]。
此外,如果第二个端口依赖于[featureA]而第三个端口依赖于[featureB],则安装第二个和第三个端口应该满足它们的依赖关系。
在这种情况下,库必须选择可用选项之一(以 kmpkg 表示),并且想要不同设置的用户此时必须使用覆盖端口。
为了向后兼容,我们今天不接受保留的现有示例:
libgit2、libzip、open62541都具有选择 TLS 或加密后端的功能。curl有不同的加密后端选项,但允许在运行时在它们之间进行选择,这意味着上述原则得以维持。darknet具有opencv2、opencv3功能来控制其依赖项使用哪个版本的 opencv。
某个功能可能涉及预览版或测试版功能
尽管有上述规定,如果存在预览分支或类似分支,其中预览功能很可能不会破坏非预览功能(例如,没有 API 删除),则可以接受一个功能来对此设置进行建模。
示例:
- Azure SDK(形式为“azure-Xxx”)具有“公共预览”功能。 -“imgui”具有“实验对接”功能,该功能参与其预览对接分支,该分支使用附加到每个公共编号版本的合并提交。
默认功能应该启用行为,而不是 API
如果消费者直接依赖于库,他们可以轻松列出任何所需的功能(library[feature1,feature2])。但是,如果消费者_不知道_他们正在使用库,
他 们就无法列出这些功能。如果该隐藏库类似于libarchive,其中功能向现有通用接口添加额外的压缩算法(以及行为),
则默认功能提供了一种方法来确保构建合理功能的传递库,即使最终使用者没有命名直接它。
如果该功能添加了额外的 API(或可执行文件或库二进制文件)并且不修改现有 API 的行为,则默认情况下应将其保留。这是因为任何可能想要使用这些 API 的消费者都可以通过直接引用轻松地要求它。
如有疑问,请勿将某个功能标记为默认功能。
不要使用功能来控制已发布接口中的替代方案
如果端口的使用者仅依赖于该端口的核心功能,那么打开该功能很可能不会破坏它们。当替代方案不是由消费者直接控制,而是由编译器设置(如/std:c++17”/“-std=c++17)控制时,这一点甚至更为重要。
为了向后兼容,我们今天不接受保留的现有示例:
redis-plus-plus[cxx17]控制一个polyfill,但不会将设置烘焙到已安装的树中。ace[wchar]更改所有 API 以接受const wchar_t*而不是const char*。
某个功能可以用别名替换 polyfill,前提是替换已烘焙到已安装的树中
尽管有上述规定,端口可以删除带有某个功能的 Polyfill,只要:
1.打开该功能会将polyfill更改为polyfill实体的别名 2. Polyfill 的状态被烘焙到已安装的标头中,这样就不太可能出现 ABI 不匹配“不可能”的运行时错误 3. 端口的使用者可以编写在两种模式下工作的代码,例如通过使用 polyfilled 或非 polyfilled 的 typedef
例子:
abseil[cxx17]将absl::string_view更改为替换或std::string_view; 补丁实现了烘焙要求。
推荐解决方案
如果公开底层替代方案至关重要,我们建议在构建时提供消息来指导用户如何将端口复制到私有覆盖中:
set(USING_DOG 0)
message(STATUS "This version of LibContoso uses the Kittens backend. To use the Dog backend instead, create an overlay port of this with USING_DOG set to 1 and the `kittens` dependency replaced with `dog`.")
message(STATUS "This recipe is at ${CMAKE_CURRENT_LIST_DIR}")
message(STATUS "See the overlay ports documentation at https://gitee.com/kumo-pub/kmpkg/blob/master/docs/specifications/ports-overlay.md")
构建技术
不要使用供应商的依赖项
不要使用库的嵌入副本。 所有依赖项都应分开并单独打包,以便可以更新和维护。
首选使用 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目录中
如果库执行以下任何操作,则视为冲突:
- Define
main - Define malloc
- Define symbols that are also declared in other libraries
冲突的库通常是设计使然,不被视为缺陷。 由于某些构建系统链接 lib 目录中的所有内容,因此应将它们移动到名为manual-link的子目录中。
版本控制
遵循 "version" 字段的通用约定
创建新端口时,请遵循包作者使用的版本控制约定。更新端口时,继续使用相同的约定,除非上游另有说明。有关我们约定的完整说明,请参阅我们的版本控制文档。
如果上游一段时间没有发布版本,请勿将端口的版本控制方案更改为“version-date”以获得最新更改。这些提交可能包括尚未准备好生产的更改。相反,要求上游存储库发布新版本。
更新任何修改端口清单文件中的 "port-version" 字段
kmpkg 使用此字段来确定给定端口是否已过时,并且在端口行为发生变化时应进行更改。
我们的约定是使用“port-version”字段来更改端口而不更改上游版本,并在上游版本更新时将“port-version”重置为零制成。
例如:
- Zlib 的软件包版本当前为
1.2.1,没有显式的"port-version"(相当于"port-version"为0)。 - 您 发现部署了错误的版权文件,并在端口文件中修复了该问题。
- 您应该将清单文件中的
"port-version"字段更新为"1"。
有关更多信息,请参阅版本控制文档。
更新任何修改端口的 versions/ 中的版本文件
kmpkg 使用一组元数据文件来支持其版本控制功能。 这些文件位于以下位置:
${KMPKG_ROOT}/versions/baseline.json,(此文件对于所有端口都是通用的)和${KMPKG_ROOT}/versions/${first-letter-of-portname}-/${portname}.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
立即更新所有已修改端口的文件。
这些命令要求在运行之前已将更改提交到端口。 原因是这些版本文件中需要端口目录的 Git SHA。 但不必担心,如果本地更改尚未提交,x-add-version 命令将发出警告
修补
kmpkg 是一个打包解决方案,而不是我们部署的组件的最终所有者。在某些情况下,我们确实需要应用补丁来提高组件与平台的兼容性,或者组件之间的兼容性。
- 我们希望避免出现以下情况的补丁:
- 上游不同意
- 导致漏洞或崩溃
- 我们无法维护跨上游版本的更新
- 足够大,足以导致与 kmpkg 存储库本身的许可证纠缠
通知上游所有者上游相关修补程序
如果修补程序可能对上游有用,则必须通知上游该修补程序的内容。 (应用与上游无关的 kmpkg 特定行为的修补程序,例如开发依赖项,不需要通知。)
为了避免上游与修补程序不一致的情况,我们将等待至少 30 天来应用此类修补程序。
如果我们对更改正确充满信心,我们将跳过此等待期。 高置信度修补程序示例包括,但不限于:
- 上游接受作 为修补程序(例如,从上游的拉取请求向后移植特定更改已合并)。
- 添加缺失的
#include。 - 小而明显的产品代码修复(例如,初始化未初始化的变量)。
- 禁用构建的
irrelevant-in-kmpkg组件,例如测试或示例。
首选选项优先于修补
最好在调用kmpkg_configure_xyz()时设置选项,而不是直接修补设置。
允许您避免修补的常见选项:
- [MSBUILD] 项目文件中的
<PropertyGroup>设置可以通过/p:参数覆盖 - [CMAKE] 可以通过
-DCMAKE_DISABLE_FIND_PACKAGE_XYz=ON禁用对 CMake 脚本中的find_package(XYz)的调用 - [CMAKE] 缓存变量(声明为
set(VAR "value" CACHE STRING "Documentation")或option(VAR "Documentation" "Default Value"))只需在命令行中传递即可覆盖它们如-DVAR:STRING=Foo。一个值得注意的例外是“FORCE”参数是否传递给set()。有关更多信息,请参阅 CMakeset文档