PEP 711 – PyBI:分发 Python 二进制文件的标准格式

猫勺猫勺 03-18 162 阅读 0 评论

抽象

“就像轮子一样,但它不是一个预先构建的 Python 包,而是一个 预构建的 Python 解释器”

赋予动机

最终目标:Pypi.org 为所有 Python 版本提供了预构建的包 流行的平台,因此自动化工具可以轻松抓取其中任何一个和 设置它。尝试 Python 预发行版、固定版变得快速而简单 CI中的Python版本,制作临时环境重现bug 报告仅发生在特定的 Python 单点版本上,等等。

第一步(此 PEP):定义一个标准的打包文件格式来保存预构建的 Python 解释器,它重用现有的 Python 打包标准,以及 可能。

例子

pybi.vorpus.org 提供了 pybi 构建示例。它们是 zip 文件,因此您可以解压缩它们并戳 如果你想感受一下它们的布局,可以在里面。

您还可以查看我用于创建它们的工具。

规范

文件名

文件名:{distribution}-{version}[-{build tag}]-{platform tag}.pybi

这与 PEP 427 中定义的 wheel 文件格式匹配,只是删除了 and 并更改了 → 中的扩展名。{python tag}{abi tag}.whl.pybi

例如:

  • cpython-3.9.3-manylinux_2014.pybi

  • cpython-3.10b2-win_amd64.pybi

就像轮子一样,如果 pybi 支持多个平台,您可以 用点将它们分开,形成一个“压缩标签集”:

  • cpython-3.9.5-macosx_11_0_x86_64.macosx_11_0_arm64.pybi

(尽管在实践中,这可能不会被大量使用,例如上面的 filename 更习惯地写成 .)cpython-3.9.5-macosx_11_0_universal2.pybi

文件内容

文件是一个 zip 文件,可以直接解压缩到 任意位置,然后用作独立的 Python 环境。 没有目录或安装方案密钥,因为 Python 环境知道它正在使用哪种安装方案,因此它可以 把事情放在正确的地方开始。.pybi.data

“任意位置”部分很重要:pybi 不能包含任何 硬编码的绝对路径。特别是,任何预安装的脚本都必须 不要在他们的 shebang 线中嵌入绝对路径。

与 wheels 的目录类似,pybi 存档 必须包含名为 的顶级目录。(基本原理:改为调用它以确保工具不会混淆 关于他们正在查看哪种元数据;省略该部件是可以的,因为只能将一个 pybi 安装到 给定目录。该目录至少包含以下内容 文件:<package>-<version>.dist-infopybi-info/pybi-infodist-info{name}-{version}pybi-info/

  • .../PYBI:关于存档本身的元数据,在同一个 RFC822-ish 格式和文件:METADATAWHEEL

Pybi-Version: 1.0Generator: {name} {version}Tag: {platform tag}Tag: {another platform tag}Tag: {...and so on...}Build: 1   # optional
  • .../RECORD:与轮子相同,但请参阅有关 符号链接,如下。

  • .../METADATA:格式与当前核心中描述的格式相同 元数据规范,但以下键是被禁止的,因为 它们没有意义:

  • Requires-Dist

  • Provides-Extra

  • Requires-Python

此外,还有一些新的必需密钥,如下所述。

特定于 Pybi 的核心元数据

在我们提供完整详细信息之前,下面是新字段的示例:METADATA

Pybi-Environment-Marker-Variables: {"implementation_name": "cpython", "implementation_version": "3.10.8", "os_name": "posix", "platform_machine": "x86_64", "platform_system": "Linux", "python_full_version": "3.10.8", "platform_python_implementation": "CPython", "python_version": "3.10", "sys_platform": "linux"}
Pybi-Paths: {"stdlib": "lib/python3.10", "platstdlib": "lib/python3.10", "purelib": "lib/python3.10/site-packages", "platlib": "lib/python3.10/site-packages", "include": "include/python3.10", "platinclude": "include/python3.10", "scripts": "bin", "data": "."}
Pybi-Wheel-Tag: cp310-cp310-PLATFORM
Pybi-Wheel-Tag: cp310-abi3-PLATFORM
Pybi-Wheel-Tag: cp310-none-PLATFORM
Pybi-Wheel-Tag: cp39-abi3-PLATFORM
Pybi-Wheel-Tag: cp38-abi3-PLATFORM
Pybi-Wheel-Tag: cp37-abi3-PLATFORM
Pybi-Wheel-Tag: cp36-abi3-PLATFORM
Pybi-Wheel-Tag: cp35-abi3-PLATFORM
Pybi-Wheel-Tag: cp34-abi3-PLATFORM
Pybi-Wheel-Tag: cp33-abi3-PLATFORM
Pybi-Wheel-Tag: cp32-abi3-PLATFORM
Pybi-Wheel-Tag: py310-none-PLATFORM
Pybi-Wheel-Tag: py3-none-PLATFORM
Pybi-Wheel-Tag: py39-none-PLATFORM
Pybi-Wheel-Tag: py38-none-PLATFORM
Pybi-Wheel-Tag: py37-none-PLATFORM
Pybi-Wheel-Tag: py36-none-PLATFORM
Pybi-Wheel-Tag: py35-none-PLATFORM
Pybi-Wheel-Tag: py34-none-PLATFORM
Pybi-Wheel-Tag: py33-none-PLATFORM
Pybi-Wheel-Tag: py32-none-PLATFORM
Pybi-Wheel-Tag: py31-none-PLATFORM
Pybi-Wheel-Tag: py30-none-PLATFORM
Pybi-Wheel-Tag: py310-none-any
Pybi-Wheel-Tag: py3-none-any
Pybi-Wheel-Tag: py39-none-any
Pybi-Wheel-Tag: py38-none-any
Pybi-Wheel-Tag: py37-none-any
Pybi-Wheel-Tag: py36-none-any
Pybi-Wheel-Tag: py35-none-any
Pybi-Wheel-Tag: py34-none-any
Pybi-Wheel-Tag: py33-none-any
Pybi-Wheel-Tag: py32-none-any
Pybi-Wheel-Tag: py31-none-any
Pybi-Wheel-Tag: py30-none-any

规范:

  • Pybi-Environment-Marker-Variables:所有 PEP 508 的值 环境标记变量,这些变量在安装中是静态的 Pybi,作为 JSON 字典。例如:

  • python_version将始终存在,因为 Python 3.10 包 总是有 .python_version == "3.10"

  • platform_version一般不会存在,因为它会给 有关运行 Python 的操作系统的详细信息,例如:

  • #60-Ubuntu SMP Thu May 6 07:46:32 UTC 2021

  • platform_release有类似的问题。

  • platform_machine通常会存在,但 macOS universal2 除外 pybis:这些可以在 x86-64 或 arm64 模式下运行,我们 在实际调用解释器之前不知道哪个,所以我们不能 将其记录在静态元数据中。

理由:在许多情况下,这应该允许在 Linux 上运行解析器 计算 Windows 上 Python 环境的包引脚,反之亦然, 只要解析器可以访问目标平台的 .pybi 文件。(注 可以使用该值检查约束。虽然我们必须省略一些关键 有时,它们要么相当无用 (, ),要么可以由解析器重建 ().Requires-Pythonpython_full_versionplatform_versionplatform_releaseplatform_machine

标记也只是一般有用的信息 访问。例如,如果你有一个 pybi,并且你 想知道支持哪个版本的 Python 语言,那么 这记录在标记中。pypy3-7.3.2python_version

(注意:我们可能想要弃用/删除 和 ?它们有问题,我无法弄清楚任何情况 它们有用的地方。但这超出了这个特定 PEP 的范围。platform_versionplatform_release

  • Pybi-Paths:安装轮子所需的安装路径(相同的键 as ),作为从根开始的相对路径 的 zip 文件,作为 JSON 字典。sysconfig.get_paths()

这些路径必须以 Unix 格式编写,使用正斜杠作为 分隔符,而不是反斜杠。

必须可以通过运行 来调用 Python 解释器。如果有替代口译员 入口点(例如 对于 Windows GUI 应用程序),则它们 也应该在该目录中以它们的常规名称,与 未附加版本号。(如果你愿意,你也可以有一个符号链接;没有规则禁止这样做。只是它必须存在和工作。{paths["scripts"]}/pythonpythonwpython3.11python

基本原理:和 s(见下文)是 一起足以让安装人员选择轮子并将它们安装到 解压缩 pybi 环境,无需调用 Python。此外,我们需要写 在翻译位置的某个地方,所以它是两只鸟一石。Pybi-PathsPybi-Wheel-Tag

Pybi-Wheel-Tag:此解释器支持的 wheel 标签,在 优先顺序(最优先优先,最不优先最后),但 特殊平台标签应替换任何 取决于最终安装系统的平台标记。PLATFORM

讨论:™ 如果安装人员可以计算 pybi 的 提前对应的车轮标签,以便他们安装 轮子进入解压缩的 pybi,而无需实际调用 python 解释器来查询其标签 - 既是为了效率,也是为了 允许更多奇特的用例,例如设置 Windows 环境 从 Linux 主机。

但不幸的是,不可能计算出完整的 提前安装 Python 支持的平台标记, 因为它们可以依赖于最终系统:

  • 标记的 pybi 总是可以使用轮子 标记为 .它也可能能够 使用带标签的轮子,但前提是最终 安装系统有 glibc 2.17+。manylinux_2_12_x86_64manylinux_2_12_x86_64manylinux_2_17_x86_64

  • 标记的 pybi (= x86-64 + arm64 支持 在同一个二进制文件中)可能能够使用标记为 的轮子,但前提是它安装在“Apple Silicon“机器,并在 arm64 模式下运行。macosx_11_0_universal2macosx_11_0_arm64

在这两种情况下,安装工具仍然可以解决 通过计算本地平台标签来获得适当的轮子标签集, 从 中获取 wheel 标签模板,然后交换 在实际支持的平台上代替魔术字符串。Pybi-Wheel-TagPLATFORM

但是,还有其他更复杂的情况:

  • 通常,您可以在 64 位 Windows 上运行 32 位和 64 位应用。所以一个pybi

安装程序可能会在当前 platform 设置为 [, ]。但你不能就这样接受它 设置并将其交换到 pybi 的 wheel 标签模板中,否则您将无稽之谈:win32win_amd64

[
  "cp39-cp39-win32",
  "cp39-cp39-win_amd64",
  "cp39-abi3-win32",
  "cp39-abi3-win_amd64",
  ...]


为了解决这个问题,安装程序需要以某种方式了解 pybi 可以使用轮子 只要这些都是当前机器上的有效标签,但 pybi 不能使用轮子,即使两者都是 当前计算机上的有效标记。manylinux_2_12_x86_64manylinux_2_17_x86_64win32win_amd64

标记的 pybi 可能能够使用 标记为 的轮子,但前提是它是 安装在 x86-64 计算机上或安装在 ARM 上 机器和解释器被调用 告诉 macOS 以 x86-64 模式运行二进制文件的咒语。所以 安装程序计划如何调用 pybi 也很重要!macosx_11_0_universal2macosx_11_0_x86_64

因此,实际使用值并不那么简单 可能看起来,它们可能只对 精密的工具。但是,智能 pybi 安装程序已经拥有 了解很多这些平台兼容性问题 选择一个有效的 pybi,以及 固定/环境搭建案例,用户可潜在提供 需要任何信息来消除确切的平台歧义 他们正在瞄准。因此,它仍然足够有用,可以包含在 PyBI 中 元数据 – 发现它没有用的工具可以简单地忽略它。Pybi-Wheel-Tag

您可以通过在 内置解释器:

import packaging.markersimport packaging.tagsimport sysconfigimport os.pathimport jsonimport sysmarker_vars = packaging.markers.default_environment()# Delete any keys that depend on the final installationdel marker_vars["platform_release"]del marker_vars["platform_version"]# Darwin binaries are often multi-arch, so play it safe and# delete the architecture marker. (Better would be to only# do this if the pybi actually is multi-arch.)if marker_vars["sys_platform"] == "darwin":
    del marker_vars["platform_machine"]# Copied and tweaked version of packaging.tags.sys_tagstags = []interp_name = packaging.tags.interpreter_name()if interp_name == "cp":
    tags += list(packaging.tags.cpython_tags(platforms=["xyzzy"]))else:
    tags += list(packaging.tags.generic_tags(platforms=["xyzzy"]))tags += list(packaging.tags.compatible_tags(platforms=["xyzzy"]))# Gross hack: packaging.tags normalizes platforms by lowercasing them,# so we generate the tags with a unique string and then replace it# with our special uppercase placeholder.str_tags = [str(t).replace("xyzzy", "PLATFORM") for t in tags](base_path,) = sysconfig.get_config_vars("installed_base")# For some reason, macOS framework builds report their# installed_base as a directory deep inside the framework.while "Python.framework" in base_path:
    base_path = os.path.dirname(base_path)paths = {key: os.path.relpath(path, base_path).replace("\\", "/") for (key, path) in sysconfig.get_paths().items()}json.dump({"marker_vars": marker_vars, "tags": str_tags, "paths": paths}, sys.stdout)

这会在 stdout 上发出一个 JSON 字典,每组 特定于 pybi 的标记。

符号链接

目前,所有 Unix Python 安装中都默认使用符号链接(例如 )。此外,符号链接需要 将 macOS 框架构建存储在文件中。因此,与车轮文件不同,我们 绝对必须支持文件中的符号链接,以便它们在 都。bin/python3 -> bin/python3.9.pybi.pybi

在 zip 文件中表示符号链接

在 zip 文件中表示符号链接的事实标准是 Info-Zip 符号链接扩展,其工作原理如下:

  • 符号链接的目标路径将像文件内容一样存储

  • Unix 权限字段的前 4 位设置为 , 即:0xapermissions & 0xf000 == 0xa000

  • 反过来,Unix 权限字段存储为 “外部属性”字段。

因此,如果使用 Python 的模块,您可以通过执行以下操作来检查 a 是否代表符号链接:zipfileZipInfo

(zip_info.external_attr >> 16) & 0xf000 == 0xa000

或者,如果使用 Rust 的 crate,等效的检查是:zip

fn is_symlink(zip_file: &zip::ZipFile) -> bool {    match zip_file.unix_mode() {        Some(mode) => mode & 0xf000 == 0xa000,        None => false,    }}

如果你使用的是 Unix,你的 和 命令可能会理解这一点 格式化。zipunzip

表示 RECORD 文件中的符号链接

通常,文件会列出每个文件 + 其哈希值 + 长度:RECORD

my/favorite/file,sha256=...,12345

对于符号链接,我们改为:

name/of/symlink,symlink=path/to/symlink/target,

也就是说:我们使用一个特殊的“哈希函数”,称为 ,然后 将实际的符号链接目标存储为“哈希值”。长度是 留空。symlink

基本原理:我们已经提交了包含 对主存档中的所有内容进行冗余检查,因此对于符号链接,我们至少 需要存储某种哈希值,加上某种标志来指示这是 符号链接。鉴于符号链接目标字符串的大小与 哈希值,我们不妨直接存储它们。这也使符号链接 对于不了解 Info-Zip 的工具,更容易访问的信息 symlink 扩展,使无损解压缩和重新打包 Unix 成为可能 pybi 在 Windows 系统上,有人可能会在某个时候觉得很方便。RECORD

在文件中存储符号链接

当 pybi 创建者存储符号链接时,他们必须同时使用 上面定义的机制:直接将其存储在 zip 存档中 Info-Zip 表示形式,并将其记录在文件中。RECORD

Pybi 使用者应该验证存档和文件中的符号链接是否彼此一致。RECORD

我们还考虑仅使用该文件来存储符号链接, 但是这样一来,香草工具就无法解开它们的包装,并且 这将使从 shell 脚本安装 pybi 变得困难。RECORDunzip

局限性

符号链接会带来很多潜在的混乱。把事情控制在 控制,我们施加以下限制:

符号链接不得用于面向 Windows 或其他 缺少一流符号链接支持的平台。.pybi

符号链接不得在目录内使用。 (理由:没有必要,它使事情变得更简单 需要从中没有提取信息的解析器 解压缩整个存档。pybi-infopybi-info

符号链接目标必须是相对路径,并且必须位于 pybi 内部 目录。

如果在存档中记录为符号链接,则有 不得是存档中名为 的任何其他条目。A/B/...A/B/.../C

例如,如果存档有一个符号链接,然后 稍后在存档中,有一个名为 , 然后,一个幼稚的解包程序可能最终会编写一个名为 .不要天真。foo -> barfoo/blah.pybar/blah.py

拆包人员必须验证是否遵守了这些规则,因为没有 攻击者可以创建像 or + 这样的邪恶符号链接并造成严重破坏。foo -> /etc/passwdfoo -> ../../../../../etcfoo/passwd -> ...

非规范性意见

为什么不直接使用conda呢?

这实际上并不在这个 PEP 的范围内,但由于 conda 是一种流行的方式 分发二进制 Python 解释器,这是一个自然的问题。

简单的答案是:conda很棒!但是,有很多 python 用户 不是 conda 用户,他们也应该得到好东西。这个 PEP 只是给了他们 另一种选择。

更深层次的答案是:将包上传到 PyPI 的维护者是 Python 生态系统的骨干。他们是 Python 的第一批受众 包装工具。他们想要的一件事是上传一次包,并拥有 它可以通过 Python 部署的所有不同方式访问:在 Debian 和 Fedora 和 Homebrew 以及 FreeBSD,在 Conda 环境中,在大公司的 monorepos, in Nix, in Blender plugins, in RenPy games, .....你明白了。

所有这些环境都有自己的工具和管理策略 包和依赖项。因此,PyPI 和轮子的特别之处在于 它们旨在以标准、抽象的方式描述依赖关系,即 所有这些下游系统都可以使用并转换为其本地 约定。这就是为什么包维护者使用特定于 Python 的元数据和 上传到 PyPI:因为它可以让他们解决所有这些系统的问题 同时。每次为 conda 构建 Python 包时,都会有一个 生成的中间轮,因为轮是通用语言 Python 包构建系统和 conda 可以使用它来相互通信。

但是,如果你是一个发布 sdist+wheels 的维护者,那么你自然而然地 想要测试您正在发布的内容,这可能取决于任意 PyPI 包 和版本。因此,您需要直接从 PyPI 和 conda 从根本上不是为了做到这一点而设计的。所以 conda 和 pip 是 两者都适用于不同的情况,而这个提案恰好是针对性的 该等式的点值。

SDISTS(或非)

为 pybis 提供一个“sdist”等价物可能会很酷,即一些 Python 源代码版本的格式,结构化程度足够高 让工具自动获取并将其构建到 pybi 中,用于平台 预构建的 pybis 不可用。但是,这不是必需的 MVP 并打开了一罐蠕虫,所以让我们稍后再担心它。

pybi 中应该捆绑哪些包?

Pybi 构建者有权挑选里面到底有什么。为 例如,您可以在 pybi 的目录中包含一些预安装的包,或者删除 stdlib 中不包含的部分 要。我们无法阻止你!虽然如果你预装软件包,那么它是 强烈建议同时包含正确的元数据(等), 这样 Pip 或其他工具就可以了解正在发生的事情。site-packages.dist-info

对于我的原型“通用”pybi,我选择的是:

确保为空。site-packages

基本原理:适用于面向的传统独立 python 安装程序 在最终用户中,您可能希望至少包括 ,以避免 引导问题 (PEP 453)。但 pybis 不同:它们被设计出来 由“智能”工具安装,这些工具将 pybi 作为某些工具的一部分使用 一种更大的自动化部署过程。对于这些安装人员来说更容易 从一张白纸开始,然后添加他们需要的任何东西,而不是让他们 从一些他们可能想要或可能不需要的预安装包开始。(和 此外,您仍然可以运行。pippython -m ensurepip

包括完整的 stdlib,但 .test

基本原理:顶级模块包含 CPython 自己的测试 套房。它很大(没有 CPython 是 ~37 MB,然后在此基础上再添加 ~25 MB!),并且基本上从未被 常规用户代码。此外,作为先例,官方的 nuget 包, 官方的 manylinux 镜像,以及多个 Linux 发行版 把它排除在外,这并没有造成任何重大问题。testtesttest

因此,这似乎是平衡广泛兼容性的最佳方式 合理的下载/安装大小。

我没有发送任何文件。它们占用了空间 下载,可以以最低的成本在最终系统上生成,并且 删除它们会消除位置依赖性的来源。( files 存储相应文件的绝对路径和 将其包含在回溯中;但是,pybis 是可重新定位的,所以正确的 直到安装后才知道路径。.pyc.pyc.py

向后兼容性

没有向后兼容性注意事项。

安全隐患

没有安全隐患,除了任何接受它的人 自己要分发二进制文件,就必须想出一个计划来管理他们的 安全性(例如,他们是否在 OpenSSL CVE 删除后滚动新版本)。但 总的来说,我们核心 Python 人员已经在为所有人维护二进制构建 主要平台(macOS + Windows 到 python.org,Linux 构建通过 官方的 manylinux 镜像),所以即使我们开始发布官方的 CPython 建立在 PyPI 上,它并没有真正引发任何新的安全问题。

如何教这个

这不是针对最终用户的;他们的经验就是这样,例如 他们的 pyenv 或 tox 调用神奇地变得更快、更可靠(如果那些 项目的维护者决定利用这个 PEP)。

版权

文档被置于公共领域或位于 CC0-1.0-通用许可证,以更宽松的许可证为准。

The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/67.html

上一篇 下一篇

相关阅读

发表评论

访客 访客
快捷回复: 表情:
评论列表 (暂无评论,162人围观)

还没有评论,来说两句吧...

取消
微信二维码
微信二维码
支付宝二维码