维护人员指南

本文档列出了在添加或更新端口方案时应应用的一组策略。 它旨在充当 Debian 的策略手册Homebrew 的维护者准则Homebrew 的配方指南

总体注册表设计目标

当前基线中的端口必须同时安装

我们希望能够向特选注册表中的库的下游用户表明,我们发布的任何给定基线中的库组合都经过了测试,至少在某些配置中可以协同工作。 允许端口相互排除会破坏测试此类配置的能力,因为此类测试所需的生成数量将随着 2^number_of_such_cases 增长。 此外,安装其他依赖项始终被视为“安全”:端口或最终用户无法断言依赖项是否在其要求中安装。

如果你希望为用户呈现这种替代情况,请考虑描述某人如何创建覆盖端口,并在 portfile.cmake 中添加注释来实现替代表单,而不是尝试添加从未在特选注册表的持续集成中构建的额外端口。 例如,请参阅 glad@0.1.36

在推出注册表之前,我们接受了几个未经测试的端口作为替代方案(如 boringssl),它们可以使编写覆盖端口更容易。 这已不再被接受,因为注册表允许在不修改特选注册表的情况下发布这些未经测试的端口。

对十六进制数字字符串使用小写

vcpkg 中的许多功能都依赖于比较十六进制数字的字符串。 一些示例包括但不限于 SHA512 哈希、Git 提交 ID 和树对象哈希。

在内部,vcpkg 使用小写规范化来比较大小写无关的此类值。 但是,在 vcpkg 的基础结构之上构建的工具可能不会考虑相同的注意事项。 因此,我们需要十六进制字符串

在以下情况下,为了保持一致性,将小写化:

  • SHA512 vcpkg 帮助程序函数中的参数。
  • REF vcpkg 帮助程序函数中的参数(当值为十六进制字符串时)。
  • git-tree 版本数据库文件中的对象。
  • sha512文件中的对象scripts/vcpkg-tools.json
  • 十六进制字符串的大小写不重要的其他位置。

PR 结构

为每个端口发出单独的拉取请求

尽可能将更改分为多个 PR。 这样更容易进行审查,并可防止一组更改的问题妨碍所有其他更改。

避免未改动文件中的细微更改

例如,避免重新设置端口文件中变量的格式或将其重命名,这样手头的问题便没有理由对其进行修改。 但是,如果出于 PR 的主要目的(更新库)需要修改该文件,那么我们将非常感谢你进行明显有益的更改,如修复打字错误!

针对其他存储库检查名称

端口名称应尝试明确端口安装的包。 理想情况下,在搜索引擎中搜索端口的名称应该会快速引导你完成相应的项目。 Repology 是一项可以一次性检查多个存储库中的多个包名称的优秀服务。

具有短名称或以常用词命名的项目可能需要消除歧义,尤其是在没有与给定单词有强关联的项目时。 例如,无法接受名称为 ip 的端口,因为可能有多个项目会以类似的方式命名。

良好的消歧器的示例包括:

  • 存储库的所有者用户名或组织:google-cloud-cpp
  • 项目所属的一组库的名称:boost-dll

C++ 和开放源代码项目所使用的常见前缀和后缀是无效的消歧器,一些示例包括但不限于:

  • cpp,
  • free,
  • lib,
  • open,
  • 数字

例如,在比较 ip-cpplibipip5 端口名称以及删除无效的消歧器时,它们都会减少到相同的词干(ip),因此被视为具有相同的名称。

对于与单个项目强烈关联的名称,不适用此准则。 例如:libpngopensslzlib

为避免用户混淆,我们可能会限制将端口添加到公共注册表后可以重命名的频率。 我们目前的政策是不允许每年多重命名一次。

使用 GitHub Draft PR

GitHub Draft PR 是获取尚未准备好合并的工作 CI 或人工反馈的一种好方法。 大多数新的 PR 应作为草稿打开,并在 CI 通过后转换为普通 PR。

有关 GitHub Draft PR 的详细信息,请参阅 草稿拉取请求简介

vcpkg 团队可能会在审核过程中将 PR 转换为草稿。 通常,请求对代码或注释进行更改,指示何时将 PR 标记为“准备审阅”。

关闭未激活的 PR

为了避免积累未处理的过时 PR,vcpkg 团队可能会关闭那些等待贡献者行动超过 60 天的 PR。 此倒计时从 vcpkg 维护者上次发出更改或反馈请求开始。如果在 60 天内未观察到任何活动,PR 将被视为不活跃,并可能根据 vcpkg 团队的酌情决定而关闭。

端口文件

避免使用已弃用的帮助程序函数

目前,以下帮助程序已弃用:

一些替换帮助程序函数位于“工具端口”中,允许使用者在特定版本固定其行为,以便锁定特定版本中帮助程序的行为。 工具端口需要添加到端口的 "dependencies",如下所示:

{
  "name": "vcpkg-cmake",
  "host": true
},
{
  "name": "vcpkg-cmake-config",
  "host": true
}

避免端口文件中的注释过多

理想情况下,端口文件应简短、简单且尽可能具有声明性。 在提交 PR 之前,移除 create 命令引入的所有样板注释。

端口不得依赖于路径

端口不得根据已安装的端口更改其行为,以免更改端口安装的内容。 例如,给定:

> vcpkg install a
> vcpkg install b
> vcpkg remove a

> vcpkg install b

b 安装的文件必须相同,而不考虑 a 以前安装文件的影响。 这意味着,在采取一些操作之前,端口不得尝试检测安装树中是否有其他端口提供的内容。 下面的“定义功能时,显式控制依赖项”中介绍了此类“路径依赖”行为具体和常见原因。

唯一端口属性规则

在整个 vcpkg 系统中,用户预计并发使用的两个端口不会提供相同的文件。 如果端口尝试安装另一个文件已提供的文件,安装将失败。 例如,如果端口希望对标头使用极常见的名称,则应将这些标头放在子目录中而不是 include 中。

此属性由尝试在注册表中安装所有端口的连续集成运行定期检查,如果两个端口提供相同的文件,则会以 FILE_CONFLICTS 失败。

在非官方命名空间中添加 CMake 导出

vcpkg 的核心设计理念是不要为用户造成“锁定”。 在构建系统中,根据系统中的库和 vcpkg 中的库,不应有区别。 为此,我们避免将 CMake 导出或目标添加到具有“明显名称”的现有库,以允许上游添加自己的官方 CMake 导出,而不会与 vcpkg 冲突。

为此,端口导出的任何 CMake 配置(不在上游库中)都应将 unofficial- 作为前缀。 任何其他目标都应位于 unofficial::<port>:: 命名空间中。

这意味着用户应看到:

  • find_package(unofficial-<port> CONFIG) 作为获取 unique-to-vcpkg 包的途径
  • unofficial::<port>::<target> 作为从该端口导出的目标。

示例:

  • brotli 创建 unofficial-brotli 包,生成目标 unofficial::brotli::brotli

每个端口都必须在文件夹 copyright 中提供一个名为 ${CURRENT_PACKAGES_DIR}/share/${PORT} 的文件。 如果包的许可证内容在其源文件中可用,则应通过调用 vcpkg_install_copyright() 创建此文件。 如有必要,vcpkg_install_copyright 还可捆绑多个版权文件。

vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")

手动创建此文件的旧方法是使用 CMake 的内置 file 命令。 不建议在改用 vcpkg_install_copyright 的新端口中使用此命令,但仍允许这样做。

file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright)

如果上游源文件中的许可证内容并非文本格式(例如 PDF 文件),则 copyright 应包含用户如何找到许可证要求的说明。 如果可能,该文件还应包含指向原始源文件的链接来表明这一点,以便用户可以检查其是否最新。

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
]])

端口中的版本约束

通常应避免端口内的版本约束,因为它们可能会阻碍项目的独立演变。 只有在有充分证据的情况下,才允许添加此类约束,例如证明与特定早期版本不兼容。 不应仅使用这些约束来保持与独立项目的奇偶校验。

中的 MAYBE_UNUSED_VARIABLES 变量必须应用于至少一个配置

在 CMake 配置步骤中添加新变量 MAYBE_UNUSED_VARIABLES 以静音警告时,必须添加注释来解释新变量应用时的情况。 如果变量不适用于任何配置,则很可能存在潜在的错误(例如,拼写错误的变量名称),并且添加它对构建没有实际影响。

vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
  FEATURES
    windowsfeature WINDOWS_OPTION
)

vcpkg_configure_cmake(
  SOURCE_PATH "${SOURCE_PATH}"
  OPTIONS
    ${FEATURE_OPTIONS}
  MAYBE_UNUSED_VARIABLES
    # Applies only on Windows
    WINDOWS_OPTION
)

功能

请勿使用功能实现备选方案

功能必须被视为附加功能。 如果安装了 port[featureA]port[featureB],则必须安装 port[featureA,featureB]。 此外,如果第二个端口依赖于 [featureA] 而第三个端口依赖于 [featureB],则同时安装第二个和第三个端口应满足其依赖项。

在这种情况下,库必须选择 vcpkg 中表示的可用选项之一,并且想要其他设置的用户此时必须使用覆盖端口。

保留我们目前不接受的现有示例以实现后向兼容性:

  • libgit2libzipopen62541 都有选择 TLS 或加密后端的功能。 curl 有不同的加密后端选项,但允许在运行时在这些选项之间进行选择,这意味着维持上述原则。
  • darknetopencv2opencv3,即控制其依赖项使用哪个版本 opencv 的功能。

可能涉及预览版或 beta 版功能一项功能

尽管有上述功能,但如果预览分支或类似的分支,预览功能很有可能不会中断非预览功能(例如,没有 API 移除),因此可接受使用某一功能对此设置进行建模。

示例:

  • Azure SDK(形式为 azure-Xxx)具有 public-preview 功能。
  • imgui 具有 experimental-docking 功能,后者使用附加到每个公共编号版本的合并提交来参与其预览停靠分支。

默认功能不得添加 API

注释

上游生成系统默认启用的功能并不意味着应将该功能添加到 default-features 条目。 根据预期目的,default-features 并不是为了对上游做出的决策进行建模,而是为了为 经典模式 用户提供便利。

默认功能旨在确保为不知道库使用库的客户安装合理的功能生成。 如果他们不知道他们正在使用库,则他们不知道列出功能。 例如, libarchive 向现有泛型接口公开启用压缩算法的功能;如果未生成任何此类功能,则库可能没有实用工具。

必须仔细考虑默认是否应启用某个功能,因为禁用默认功能很复杂。

将默认功能禁用为“可传递”使用者需要:

  • 所有客户通过命令行的功能列表中显式禁用或包括"default-features": false默认功能[core]
  • 将命令行上的 vcpkg install 可传递依赖项命名为顶级清单中的直接依赖项

在 vcpkg 的特选注册表中,如果该功能添加了其他 API、可执行文件或其他二进制文件,则默认情况下它必须关闭。 如有疑问,请勿将功能标记为默认值。

不要使用功能来控制已发布接口中的备选方案

如果端口的使用者仅依赖于该端口的核心功能,则启用该功能可能不会破坏这些功能。 当备选方案并非由使用者直接控制,而是由编译器设置(例如 /std:c++17 / -std=c++17)控制时,这一点更为重要。

保留我们目前不接受的现有示例以实现后向兼容性:

  • redis-plus-plus[cxx17] 控制 polyfill,但不会将设置嵌入到已安装的树中。
  • ace[wchar] 更改所有 API 以接受 const wchar_t* 而不是 const char*

某项功能可以用别名替换 polyfill,前提是替换已嵌入已安装的树中

尽管有上述功能,但端口可能会移除包含某项功能的 polyfill,只要满足以下条件:

  1. 启用该功能会将 polyfill 更改为已填充实体的别名
  2. polyfill 的状态将嵌入已安装的标头中,以使 ABI 不太可能不匹配“不可能”运行时错误
  3. 端口使用者可以使用这两种模式编写代码,例如,使用已填充或未填充的 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://github.com/microsoft/vcpkg/blob/master/docs/specifications/ports-overlay.md")

构建技术

不要使用供应商的依赖项

不要使用库的嵌入副本。 所有依赖项都应单独拆分和打包,以便可以进行更新和维护。

vcpkg 旨在提供可靠、一致且易于维护的包管理系统,而捆绑的依赖项会带来若干与这些目标相冲突的挑战:

更新的难度:库的嵌入副本使得从上游项目跟踪和应用更新(包括安全修补程序)变得更加困难。 这会导致生态系统中潜在的安全风险和过时的依赖项。

符号冲突:当多个包包含同一库的不同版本时,引入的依赖项可能会导致符号冲突。

例如:如果包 A 供应商库 X(版本 1)和包 B 供应商库 X(版本 2),链接这两个包的应用程序可能会遇到运行时错误或由于符号冲突而未定义的行为。

通过单独打包依赖项,vcpkg 可确保在所有包中使用单个版本的库,从而消除此类冲突。

许可合规:供应商提供的依赖项可能会混淆嵌入库的许可,从而可能违反其条款或导致兼容性问题。

增加维护负担:使供应商的依赖项与其上游版本保持同步需要大量的手动工作,并且通常会导致包中的重复工作。

首选使用 CMake

当有多个构建系统可用时,首选使用 CMake。 此外,在适当的时候,使用 file(GLOB) 指令将备选构建系统重写为 CMake 可能更容易且更易于维护。

示例:abseil

选择静态或共享的二进制文件

在生成 CMake 库时,vcpkg_cmake_configure() 将根据用户请求的变体传递 BUILD_SHARED_LIBS 的正确值。

可以使用 string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" ...) 计算替代配置参数。

# portfile.cmake

string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" KEYSTONE_BUILD_STATIC)
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" KEYSTONE_BUILD_SHARED)

vcpkg_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()

vcpkg_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}
)

下面使用 vcpkg_check_features() 的代码段是等效的。

vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
  FEATURES
    "zlib"    CMAKE_REQUIRE_FIND_PACKAGE_ZLIB
  INVERTED_FEATURES
    "zlib"    CMAKE_DISABLE_FIND_PACKAGE_ZLIB
)

vcpkg_cmake_configure(
    SOURCE_PATH ${SOURCE_PATH}
    OPTIONS
      ${FEATURE_OPTIONS}
)

代码段中的 ZLIB 区分大小写。 有关详细信息,请参阅 CMAKE_DISABLE_FIND_PACKAGE_<PackageName>CMAKE_REQUIRE_FIND_PACKAGE_<PackageName> 文档。

如果库执行以下任一操作,则被视为冲突:

  • 定义 main
  • 定义 malloc
  • 定义其他库中也声明的符号

冲突库通常是有意设计的,不会被视为缺陷。 由于某些构建系统与 lib 目录中的所有内容链接,因此应将这些系统移动到名为 manual-link 的子目录中。

安装预生成的二进制文件

允许安装预生成(仅二进制)项目的端口,但强烈建议不要这样做,前提是它们不会有效地阻止更改其他端口的版本。 首选从源代码构建,因为这支持所有更改编译器或标志的 vcpkg 设置。

我们将拒绝满足以下所有条件的端口:

  1. 安装预生成的二进制文件,而不是从源生成,以及
  2. 这些二进制文件在运行时具有或需要由特选注册表中的其他端口提供的依赖项。
  3. 已安装的项目进入 vcpkg 的已发布链接域,即安装下游端口或用户项目应链接到的库/标头/CMake 或 pkg-config 元数据。

理由:此组合有效地将依赖关系图的 ABI 锁定到生成上游预构建时所使用的版本。 vcpkg 无法安全地更新(例如 zlibopenssl 或类似的依赖项),否则会给与链接预生成库的使用者带来细小的 ODR/ABI 中断风险,并且用户可能会错过关键的安全更新。

“输入已发布的链接域”通常表示以下任一类型:

  • 安装.lib.a.so.dylib或导入供使用者链接的库。
  • 从其他 vcpkg 端口引用(直接或通过内联/模板代码)符号、类型或宏的传送标头。
  • 安装 CMake 配置文件/pkg-config 文件,这些文件会在其他 vcpkg 端口上调用 find_dependency() / Requires:

允许(但仍不鼓励)的方案:

  • 在构建时使用的仅限主机的辅助工具(可执行文件)其输出被使用,但它们本身不会被依赖端口链接,前提是它们要么私自捆绑依赖项,要么仅依赖于普遍存在的系统运行时库。
  • 完全自包含的预生成库,静态链接所有 OSS 依赖项,并且不会通过已安装的标头或导出接口公开其符号或类型(使用者无法观察或依赖可传递的 ABI)。
  • 仅数据、固件或资产包未链接到用户代码。

禁止的示例:

  • 预生成的 libfoo 用于安装 lib/libfoo.lib,并包含加标头(包括 <zlib.h>)且是针对特定 zlib 版本编译的。然后,使用者会链接到 libfoo,预期具有兼容性。
  • 预构建的 SDK,该 SDK 可以在二进制文件针对较旧的 OpenSSL 版本编译时,安装并调用 CMake 包文件 find_dependency(OpenSSL)

缓解措施/替代方法:

  • 使用上游脚本提供源生成或添加精简 CMake 包装器。
  • 要求上游发布基于源代码的版本或可重复构建的指令,在注释 portfile.cmakevcpkg.json 中链接上游的问题/PR。
  • 对无法满足这些规则的组织特定的预生成使用 覆盖端口 或专用注册表。

版本控制

遵循 "version" 字段的通用约定

创建新端口时,请遵循包作者所使用的版本控制约定。 更新端口时,除非上游另有说明,否则,请继续使用同一约定。 有关约定的完整说明,请参阅我们的版本控制文档

如果上游有一段时间未发布版本,请不要将端口的版本控制方案更改为 version-date 来获取最新更改。 这些提交可能包括尚未准备好用于生产的更改。 改为要求上游存储库发布新版本。

更新任何修改端口清单文件中的 "port-version" 字段

vcpkg 使用此字段来确定给定端口是否过期,并在端口行为变更时应进行更改。

我们的约定是使用 "port-version" 字段更改不更改上游版本的端口,并在对上游版本进行更新时将 "port-version" 重置回零。

例如:

  • Zlib 的包版本目前为 1.2.1,没有显式 "port-version"(相当于 "port-version"0)。
  • 你已发现已部署的错误版权文件,并在端口文件中修复了该文件。
  • 应将清单文件中的 "port-version" 字段更新为 1

有关详细信息,请参阅版本控制文档

更新任何修改端口的 versions/ 中的版本文件

vcpkg 使用一组元数据文件为其版本控制功能提供支持。 这些文件位于以下位置:

  • ${VCPKG_ROOT}/versions/baseline.json,(此文件对所有端口通用)和
  • ${VCPKG_ROOT}/versions/${first-letter-of-portname}-/${portname}.json(每个端口一个)。

例如,对于 zlib,相关文件包括:

  • ${VCPKG_ROOT}/versions/baseline.json
  • ${VCPKG_ROOT}/versions/z-/zlib.json

我们预计每次更新端口时,你也会更新其版本文件。

更新这些文件的建议方法是运行 x-add-version 命令,例如:

vcpkg x-add-version zlib

如果要同时更新多个端口,可以改为运行:

vcpkg x-add-version --all

一次更新所有已修改端口的文件。

有关详细信息,请参阅 版本控制参考注册表 文章。

修补

vcpkg 是一种打包解决方案,而不是我们所部署组件的最终所有者。 在某些情况下,我们需要应用修补程序,以提高组件与平台的兼容性,或相互兼容性。

  • 我们希望避免出现如下修补程序:
    • 上游不同意
    • 导致漏洞或崩溃
    • 我们无法跨上游版本更新进行维护
    • 足够大,导致与 vcpkg 存储库本身的许可证纠缠

通知上游所有者上游相关修补程序

如果修补程序可能对上游有用,则必须通知上游该修补程序的内容。 (应用与上游无关的 vcpkg 特定行为的修补程序,例如开发依赖项,不需要通知。)

为了避免上游与修补程序不一致的情况,我们将等待至少 30 天来应用此类修补程序。

如果我们对更改正确充满信心,我们将跳过此等待期。 高置信度修补程序示例包括,但不限于:

  • 上游接受作为修补程序(例如,从上游的拉取请求向后移植特定更改已合并)。
  • 添加缺失的 #include
  • 小而明显的产品代码修复(例如,初始化未初始化的变量)。
  • 禁用构建的 irrelevant-in-vcpkg 组件,例如测试或示例。

首选选项优先于修补

最好在调用 vcpkg_configure_xyz() 时设置选项,而不是直接修补设置。

可以避免修补的常用选项:

  • [MSBUILD] 项目文件内的 <PropertyGroup> 设置可以通过/p: 参数覆盖
  • [CMAKE] 通过 find_package(XYz) 可以禁用对 CMake 脚本中 -DCMAKE_DISABLE_FIND_PACKAGE_XYz=ON 的调用
  • [CMAKE] 缓存变量(声明为 set(VAR "value" CACHE STRING "Documentation")option(VAR "Documentation" "Default Value"))可通过仅在命令行中作为 -DVAR:STRING=Foo 传入进行覆盖。 一个值得注意的例外是将 FORCE 参数传递给 set() 的情况。 有关详细信息,请参阅 CMake set 文档

更喜欢下载已批准的修补程序,而不是将其签入端口

如果可以从上游获取已批准或合并的修补程序文件,则端口应尝试下载并应用它们,而不是将它们作为端口文件的一部分。 此过程优先,因为它:

  • 确认上游已接受修补程序更改
  • 通过转移责任上游来简化审阅过程
  • 减少未使用修补程序的用户的 vcpkg 存储库大小
  • 避免与 vcpkg 存储库发生许可证冲突

应从稳定终结点下载修补程序,以避免 SHA 冲突。 从 GitHub 和 GitLab 的拉取请求或提交中下载补丁文件时,?full_index=1 参数应追加到下载 URL。

示例:

  • https://github.com/google/farmhash/pull/40.diff?full_index=1
  • https://github.com/linux-audit/audit-userspace/commit/f8e9bc5914d715cdacb2edc938ab339d5094d017.patch?full_index=1
  • https://gitlab.kitware.com/paraview/paraview/-/merge_requests/6375.diff?full_index=1

修补优先于覆盖 VCPKG_<VARIABLE>

前缀为 VCPKG_<VARIABLE> 的一些变量具有等效 CMAKE_<VARIABLE>。 但是,并非所有变量都传递到内部包构建(请参阅实现:Windows 工具链)

请考虑以下示例:

set(VCPKG_C_FLAGS "-O2 ${VCPKG_C_FLAGS}")
set(VCPKG_CXX_FLAGS "-O2 ${VCPKG_CXX_FLAGS}")

使用 vcpkg 内置的工具链可以正常工作,因为 VCPKG_<LANG>_FLAGS 的值将转发给相应的 CMAKE_LANG_FLAGS 变量。 但是,不知道 vcpkg 变量的自定义工具链不会进行转发。

因此,首选在设置 CMAKE_<LANG>_FLAGS 时直接修补构建系统。

尽量减少修补程序

对库进行更改时,尽量减少最终差异。 这意味着,在进行更改影响区域时,不应重新设置上游源代码格式。 禁用条件时,最好向条件添加 AND FALSE&& 0,而不是删除条件的每一行。 如果需要禁用大型区域,则在区域周围添加 if(0)#if 0 比删除补丁中的每一行更短。

如果端口已过时,不要添加修补程序,将端口更新为较新的发布版本可以解决相同的问题。 vcpkg 更喜欢更新端口,而不是修补过时的版本。

这有助于缩小 vcpkg 存储库的大小,并提高修补程序将应用于未来代码版本的可能性。

请勿在修补程序中实现功能

在 vcpkg 中修补的目的是实现与编译器、库和平台的兼容性。 其并非要实现新功能取代以下适当的开放源代码过程(提交问题/PR/等)。

默认情况下不要构建测试/文档/示例

提交新端口时,请检查 BUILD_TESTSWITH_TESTSPOCO_ENABLE_SAMPLES 等任何选项,并确保禁用其他二进制文件。 这将最大限度地缩短普通用户的构建时间和减少依赖项。

(可选)可以添加一项 test 功能来构建测试,但这不应该出现在 Default-Features 列表中。

使库的现有用户能够切换到 vcpkg

不要添加 CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS

除非库的作者已经在使用,否则我们不应使用此 CMake 功能,因为它与 C++ 模板交互不佳,并破坏了某些编译器功能。 不提供 .def 文件且不使用 __declspec() 声明的库根本不支持 Windows 的共享版本,应标记为:

if(VCPKG_TARGET_IS_WINDOWS)
    vcpkg_check_linkage(ONLY_STATIC_LIBRARY)
endif()

不要在上游给定的名称之外重命名二进制文件

这意味着,如果上游库在发布和调试时具有不同的名称(libx 与 libxd),则不应将调试库重命名为 libx。 反之亦然,如果上游库在发布和调试时具有相同的名称,则不应引入新名称。

重要注意事项:

  • 静态和共享变体通常应重命名为通用方案。 这使使用者能够使用公用名并忽略下游链接。 这是安全的,因为我们一次只提供一个。

如果库构建 CMake 集成文件 (foo-config.cmake),则必须通过修补 CMake 构建本身来完成重命名,而不是简单地在输出存档/库上调用 file(RENAME)

最后,Windows 上的 DLL 文件不应在构建后重命名,因为这会破坏构建的 LIB。

清单

我们需要格式化清单文件。 使用以下命令设置所有清单文件的格式:

> vcpkg format-manifest --all

三元组

我们目前不接受添加非社区三元组的请求。 从社区提升到完全三元状态主要基于硬件测试此类三重体的预算,并将由 vcpkg 提交的指标驱动,以最大程度地提高人们实际使用的可能性。从社区升级到完整三元组状态主要基于测试此类三元组的硬件预算,并将由 vcpkg 提交的指标驱动,以最大限度地提高人们实际使用的内容得到充分测试的可能性。

如果出现以下情况,我们将添加社区三元组:

  • 事实证明,人们实际上会使用该社区三元组;以及
  • 我们不知道这样的三元组是否被破坏。

例如,我们没有在 https://github.com/microsoft/vcpkg/pull/29034 中添加三元组,因为作者只是试图“完成集合”而不是表明其实际上会使用这样的东西,而我们直到创建了使结果可重定位的 patchelf 解决方案后再添加 linux-dynamic 。

有用的实现说明

端口文件在脚本模式下运行

虽然 portfile.cmakeCMakeLists.txt 共享通用语法和核心 CMake 语言构造(即“脚本命令”),但端口文件在“脚本模式”中运行,而 CMakeLists.txt 文件在“项目模式”中运行。 这两种模式之间最重要的区别是,“脚本模式”没有“工具链”、“语言”和“目标”的概念。 依赖于这些构造(例如,CMAKE_CXX_COMPILERCMAKE_EXECUTABLE_SUFFIXCMAKE_SYSTEM_NAME)的任何行为(包括脚本命令)都不正确。

端口文件可以直接访问三元组文件中设置的变量,但 CMakeLists.txt 不能(尽管经常会发生转换 - VCPKG_LIBRARY_LINKAGEBUILD_SHARED_LIBS)。

端口文件和端口文件调用的项目构建在不同的进程中运行。 概念:

+----------------------------+       +------------------------------------+
| CMake.exe                  |       | CMake.exe                          |
+----------------------------+       +------------------------------------+
| Triplet file               | ====> | Toolchain file                     |
| (x64-windows.cmake)        |       | (scripts/buildsystems/vcpkg.cmake) |
+----------------------------+       +------------------------------------+
| Portfile                   | ====> | CMakeLists.txt                     |
| (ports/foo/portfile.cmake) |       | (buildtrees/../CMakeLists.txt)     |
+----------------------------+       +------------------------------------+

要确定端口文件中的主机,标准 CMake 变量足以 (CMAKE_HOST_WIN32)。

要确定端口文件中的目标,应使用 vcpkg 三元组变量 (VCPKG_CMAKE_SYSTEM_NAME)。

另请参阅我们的三元组文档,了解可能设置的完整枚举。