供应商政策¶
供应商库 **必须** 除非为了成功将它们供应商而需要,否则不得修改。
供应商库 **必须** 是 PyPI 上可用的库的发布版本。
供应商库 **必须** 在允许将它们集成到
pip
的许可证下可用,pip
在 MIT 许可证下发布。供应商库 **必须** 附带 LICENSE 文件。
在 pip 中供应商的库版本 **必须** 反映在
pip/_vendor/vendor.txt
中。供应商库 **必须** 在没有任何构建步骤的情况下运行,例如
2to3
或 C 代码的编译,实际上这限制为单个源 2.x/3.x 和纯 Python。对库进行的任何修改 **必须** 在
pip/_vendor/README.rst
中注明,并且相应的补丁 **必须** 包含在tools/vendoring/patches
中。供应商库应该在
pip/_vendor/__init__.py
中有相应的vendored()
条目。
基本原理¶
从历史上看,pip 除 setuptools
本身以外没有其他依赖项,而是选择实现其需要的任何功能,以防止需要依赖项。但是,从 pip 1.5 开始,我们开始用 PyPI 上的可重用库替换在 pip 内部实现的代码。这带来了重用库而不是重新发明轮子的典型好处,例如更高质量和经过更多测试的代码,集中修复错误(尤其是安全性敏感的错误)以及更少的工作量获得更好/更多功能。
但是,以传统方式(通过 install_requires
)让 pip 依赖其他库有一些问题。这些问题是
- 脆弱性
当 pip 依赖另一个库来运行时,如果由于任何原因该库未安装或安装了不兼容的版本,那么 pip 将停止运行。这对于所有 Python 应用程序都是如此,但是对于除了 pip 之外的所有应用程序,解决方法是重新运行 pip。显然,当 pip 无法运行时,你不能使用 pip 来修复 pip,所以你只能手动解决依赖项并手动安装它们。
- 使其他库无法安装
pip 目前的依赖项之一是
requests
库,pip 需要相当新的版本才能运行。如果 pip 以传统方式依赖requests
,那么我们要么必须维护与曾经存在(并且会永远存在)的每个requests
版本的兼容性,要么允许 pip 使某些版本的requests
无法安装。(第二个问题,尽管从技术上讲对任何 Python 应用程序都是如此,但由于 pip 的普遍性而被放大;pip 默认安装在 Python、pyvenv
和virtualenv
中。)- 安全性
乍一看这可能令人费解,因为供应商倾向于使安全更新依赖项变得复杂,而这对于 pip 也是如此。但是,鉴于避免依赖项的其他原因,另一种方法是让 pip 自己重新发明轮子。这是 pip 在历史上所做的事情。它迫使 pip 重新实现自己的 HTTPS 验证例程,以解决 Python 标准库缺少 SSL 验证的问题,这导致了
requests
和urllib3
中的验证例程中出现类似的错误,只是它们必须独立发现和修复。即使我们正在供应商,重用库也通过依赖于我们依赖项的出色工作,以及通过简单地引入依赖项的新版本来实现更快、更轻松的安全修复,从而使 pip 更安全。- 引导
目前,大多数流行的安装 pip 的方法依赖于 pip 自包含的特性来安装 pip 本身。这些工具通过捆绑 pip 的副本,将其添加到
sys.path
中,然后执行该 pip 副本来工作。这样做是为了避免实现“迷你安装程序”(以减少重复);pip 已经知道如何安装 Python 包,并且比任何“迷你安装程序”都要经过更多测试。
许多下游分发者有关于这种捆绑的政策,而是选择修补他们分发的软件以解除捆绑,使其依赖于他们已经打包的软件的全局版本(这些软件可能应用了自己的补丁)。我们(pip 团队)希望 pip不要以这种方式解除捆绑,因为上述原因,我们希望 pip 保持原样。
从长远来看,如果有人有可移植的解决方案来解决上述问题,而不是我们目前使用的捆绑方法,并且不会带来不合理的额外问题,那么我们将很乐意考虑,并可能切换到该方法。此解决方案必须在所有我们期望 pip 使用的环境中正常运行,并且不要强制执行某些外部机制,例如操作系统包。
修改¶
setuptools
被完全剥离,只保留pkg_resources
。pkg_resources
已被修改为从pip._vendor
导入其依赖项,并使用供应商的platformdirs
副本,而不是appdirs
。packaging
已被修改为从pip._vendor
导入其依赖项。CacheControl
已被修改为从pip._vendor
导入其依赖项。requests
已被修改为从pip._vendor
导入其其他依赖项,并且不加载simplejson
(所有平台)和pyopenssl
(Windows)。platformdirs
已被修改为从pip._vendor.platformdirs
导入其子模块。
自动供应商¶
供应商是通过 vendoring 工具来自 pip/_vendor/vendor.txt
的内容和 tools/vendoring/patches
中的不同补丁自动完成的。通过 vendoring sync . -v
启动它(需要 vendoring>=0.2.2
)。工具配置通过 pyproject.toml
完成。
管理本地补丁¶
vendoring
工具会自动应用我们的本地补丁,但更新时,补丁有时不再干净地应用。在这种情况下,更新将失败。要解决此问题,请执行以下步骤
还原 revendoring 分支中的任何不完整的更改,以确保你有一个干净的起点。
再次运行库的重新销售操作,并再次遇到问题:
nox -s vendoring -- --upgrade <library_name>
。这将再次失败,但您将在工作目录中拥有原始源代码。查看针对源代码的现有补丁,并修改补丁以反映源代码的新版本。如果您
git add
了销售所做的更改,您可以修改源代码以反映补丁文件,然后使用git diff
生成新的补丁。现在,恢复所有内容,除了补丁文件更改。将修改后的补丁文件保留在工作树中,但不要将其暂存。
重新运行销售。这次,它应该拾取更改的补丁文件并将其干净地应用。补丁文件更改将与重新销售一起提交,因此新提交应该准备好进行测试并发布为 PR。
解绑¶
如理由中所述,我们 pip 团队更希望 pip 不要解绑(除了可选的 pip/_vendor/requests/cacert.pem
),并且 pip 保持完整。但是,如果您坚持这样做,我们有一个半支持的方法(我们在 CI 中没有测试它),并且需要您在解决上述问题方面付出额外的努力。
删除
pip/_vendor/
中的所有内容,除了pip/_vendor/__init__.py
和pip/_vendor/vendor.txt
。使用您修补过的这些库的副本,为 pip 的每个依赖项(以及它们的任何依赖项)生成轮子。这些必须放置在 pip 可以访问的文件系统上的某个位置(
pip/_vendor
是默认假设)。修改
pip/_vendor/__init__.py
,使DEBUNDLED
变量为True
。在安装时,pip 自身
dist-info
目录中的INSTALLER
文件应设置为除pip
之外的其他内容,以便 pip 可以检测到它不是使用自身安装的。(可选) 如果您将轮子放在了
pip/_vendor/
以外的位置,则修改pip/_vendor/__init__.py
,使WHEEL_DIR
变量指向您放置它们的位置。(可选) 更新
pip_self_version_check
逻辑以使用确定 pip 最新可用版本的适当逻辑,并提示用户显示正确的升级消息。
请注意,不支持部分解绑。您需要为所有依赖项准备轮子才能成功解绑。