Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

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

作者:
Nathaniel J. Smith <njs at pobox.com>
PEP 委托人:
待定
讨论邮件列表:
Discourse 帖子
状态:
草稿
类型:
标准跟踪
主题:
打包
创建日期:
2023年4月6日
修订历史:
2023年4月6日

目录

摘要

“类似于 wheel,但它不是预编译的 Python 包,而是一个预编译的 Python 解释器”

动机

最终目标:Pypi.org 为所有 Python 版本和所有流行平台提供预编译的包,因此自动化工具可以轻松获取任何包并进行设置。这使得尝试 Python 预发布版本、在 CI 中固定 Python 版本、创建临时环境来重现仅在特定 Python 版本中出现的错误报告等操作变得快速而简单。

第一步(本 PEP):定义一个标准的打包文件格式来保存预编译的 Python 解释器,并尽可能地重用现有的 Python 打包标准。

示例

示例 pybi 构建可在 pybi.vorpus.org 获取。它们是 zip 文件,因此您可以解压它们并在其中查看,以了解它们的布局方式。

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

规范

文件名

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

这与 PEP 427 中定义的 wheel 文件格式相匹配,除了删除了 {python tag}{abi tag} 以及将扩展名从 .whl 更改为 .pybi

例如

  • cpython-3.9.3-manylinux_2014.pybi
  • cpython-3.10b2-win_amd64.pybi

与 wheel 一样,如果 pybi 支持多个平台,则可以使用点将它们分隔开以创建“压缩标签集”

  • cpython-3.9.5-macosx_11_0_x86_64.macosx_11_0_arm64.pybi

(但在实践中这可能不会经常使用,例如,上面文件名更惯用的写法是 cpython-3.9.5-macosx_11_0_universal2.pybi。)

文件内容

.pybi 文件是一个 zip 文件,可以直接解压到任意位置,然后用作自包含的 Python 环境。没有 .data 目录或安装方案键,因为 Python 环境知道它正在使用哪个安装方案,因此它可以从一开始就将内容放在正确的位置。

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

类似于 wheel 的 <package>-<version>.dist-info 目录,pybi 存档必须包含一个名为 pybi-info/ 的顶级目录。(理由:将其称为 pybi-info 而不是 dist-info 可以确保工具不会混淆它们正在查看哪种元数据;省略 {name}-{version} 部分是可以的,因为只有一个 pybi 可以安装到给定的目录中。)pybi-info/ 目录至少包含以下文件

  • .../PYBI:有关存档本身的元数据,格式与 METADATAWHEEL 文件的 RFC822 格式相同
    Pybi-Version: 1.0
    Generator: {name} {version}
    Tag: {platform tag}
    Tag: {another platform tag}
    Tag: {...and so on...}
    Build: 1   # optional
    
  • .../RECORD:与 wheel 中的相同,但请参见下面关于符号链接的说明。
  • .../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:所有在安装此 Pybi 时保持静态的 PEP 508 环境标记变量的值,以 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 pybi 除外:这些 pybi 可以在 x86-64 或 arm64 模式下运行,我们只有在实际调用解释器时才知道哪个模式,因此我们无法在静态元数据中记录它。

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

    标记通常也是有用的信息。例如,如果您有一个 pypy3-7.3.2 pybi,并且您想知道它支持哪个版本的 Python 语言,那么该信息记录在 python_version 标记中。

    (注意:我们可能希望弃用/删除 platform_versionplatform_release?它们存在问题,我无法找到任何它们有用的情况。但这不在本 PEP 的范围内。)

  • Pybi-Paths:安装 wheel 所需的安装路径(与 sysconfig.get_paths() 中的键相同),以从 zip 文件根目录开始的相对路径形式表示,以 JSON 字典形式表示。

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

    必须能够通过运行 {paths["scripts"]}/python 来调用 Python 解释器。如果有其他解释器入口点(例如 Windows GUI 应用程序的 pythonw),那么它们也应该位于该目录下,使用其常规名称,并且不附加版本号。(如果需要,您也可以拥有一个 python3.11 符号链接;没有规则禁止这样做。只是 python 必须存在并工作。)

    **理由:**Pybi-PathsPybi-Wheel-Tag(见下文)一起足以让安装程序选择 wheel 并将其安装到解压的 pybi 环境中,而无需调用 Python。此外,我们需要在某处写下解释器的位置,所以一举两得。

  • Pybi-Wheel-Tag:此解释器支持的 wheel 标签,按优先级排序(最优先的排在最前面,最不优先的排在最后),但特殊平台标签 PLATFORM 应该替换任何依赖于最终安装系统的平台标签。

    **讨论:**如果安装程序能够提前计算 pybi 的对应 wheel 标签,那将非常棒™,这样它们就可以将 wheel 安装到解压的 pybi 中,而无需实际调用 Python 解释器来查询其标签——这既是为了效率,也为了允许更奇特的用例,例如从 Linux 主机设置 Windows 环境。

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

    • 标记为 manylinux_2_12_x86_64 的 pybi 始终可以使用标记为 manylinux_2_12_x86_64 的 wheel。它也**可能**能够使用标记为 manylinux_2_17_x86_64 的 wheel,但前提是最终安装系统具有 glibc 2.17+。
    • 标记为 macosx_11_0_universal2(= 在同一个二进制文件中支持 x86-64 和 arm64)的 pybi 可能能够使用标记为 macosx_11_0_arm64 的 wheel,但前提是它安装在“Apple Silicon”机器上并在 arm64 模式下运行。

    在这两种情况下,安装工具仍然可以通过计算本地平台标签、从 Pybi-Wheel-Tag 获取 wheel 标签模板以及将实际支持的平台替换为魔术字符串 PLATFORM 来计算合适的 wheel 标签集。

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

    • 您可以在(通常)在 64 位 Windows 上运行 32 位和 64 位应用程序。因此,pybi
      安装程序可能会将当前平台上允许的 pybi 标签集计算为 [win32win_amd64]。但是,您不能只获取该集合并将其替换到 pybi 的 wheel 标签模板中,否则会得到无意义的结果
      [
        "cp39-cp39-win32",
        "cp39-cp39-win_amd64",
        "cp39-abi3-win32",
        "cp39-abi3-win_amd64",
        ...
      ]
      

      为了处理这个问题,安装程序需要以某种方式理解,只要 manylinux_2_12_x86_64manylinux_2_17_x86_64 在当前机器上都是有效的标签,manylinux_2_12_x86_64 pybi 就可以使用 manylinux_2_17_x86_64 wheel,但是 win32 pybi**不能**使用 win_amd64 wheel,即使它们在当前机器上都是有效的标签。

    • 一个标记为 macosx_11_0_universal2 的 pybi 或许能够使用标记为 macosx_11_0_x86_64 的 wheel,但前提是它安装在 x86-64 机器上 *或者* 安装在 ARM 机器上 *并且* 解释器使用特殊的咒语来告诉 macOS 以 x86-64 模式运行二进制文件。因此,安装程序计划如何调用 pybi 也很重要!

    所以,实际上使用 Pybi-Wheel-Tag 值并没有看起来那么简单,它们可能只在相当复杂的工具中才有用。但是,智能的 pybi 安装程序已经需要理解很多这些平台兼容性问题才能选择一个可用的 pybi,并且对于跨平台固定/环境构建的情况,用户可以提供任何必要的信息来明确他们目标的平台。因此,将其包含在 PyBI 元数据中仍然很有用——不认为它有用的工具可以简单地忽略它。

您可能可以通过在构建的解释器上运行此脚本生成这些元数据值

import packaging.markers
import packaging.tags
import sysconfig
import os.path
import json
import sys

marker_vars = packaging.markers.default_environment()
# Delete any keys that depend on the final installation
del 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_tags
tags = []
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)

这会在标准输出上输出一个 JSON 字典,其中包含每个 pybi 特定标签集的单独条目。

非规范性注释

为什么不直接使用 conda?

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

简单的答案是:conda 很好!但是,还有很多不是 conda 用户的 Python 用户,他们也应该享受美好的事物。本 PEP 只是给了他们另一种选择。

更深入的答案是:将软件包上传到 PyPI 的维护者是 Python 生态系统的支柱。他们是 Python 打包工具的首批受众。他们想要做的一件事就是上传一个软件包,并使其能够通过 Python 部署的所有不同方式访问:在 Debian 和 Fedora 和 Homebrew 和 FreeBSD 中,在 Conda 环境中,在大公司的单体存储库中,在 Nix 中,在 Blender 插件中,在 RenPy 游戏中……您明白了。

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

但是,如果您是发布 sdist+wheel 的维护者,那么您自然希望测试您发布的内容,这可能依赖于任意 PyPI 软件包和版本。因此,您需要可以直接从 PyPI 构建 Python 环境的工具,而 conda 从根本上来说并不是为了这个目的而设计的。因此,conda 和 pip 在不同的情况下都是必要的,而本提案恰好针对的是 pip 方面。

源代码分发包(或不使用)

拥有 pybi 的“sdist”等效项可能很酷,即某种 Python 源代码发布的格式,其结构足够让工具自动获取并在 pybi 中构建它,用于没有预构建 pybi 的平台。但是,对于 MVP 来说,这不是必需的,并且会打开一个潘多拉魔盒,所以我们以后再担心这个问题。

哪些包应该捆绑在 pybi 中?

Pybi 构建者有权选择要包含的确切内容。例如,您可以在 pybi 的 site-packages 目录中包含一些预安装的软件包,或者修剪掉您不需要的标准库部分。我们无法阻止你!但是,如果您确实预安装了软件包,那么强烈建议您也包含正确的元数据(.dist-info 等),以便 Pip 或其他工具能够理解发生了什么。

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

  • 确保 site-packages 是 *空的*。

    理由:对于针对最终用户的传统独立 Python 安装程序,您可能希望至少包含 pip,以避免引导问题(PEP 453)。但 pybi 不同:它们旨在由“智能”工具安装,这些工具将 pybi 作为某种更大自动化部署过程的一部分使用。对于这些安装程序来说,从空白状态开始并添加他们需要的内容更容易,而不是从他们可能需要也可能不需要的一些预安装软件包开始。(此外,您仍然可以运行 python -m ensurepip。)

  • 包含完整的标准库,*除了* test

    理由:顶级 test 模块包含 CPython 自己的测试套件。它很大(没有 test 的 CPython 大约为 37 MB,然后 test 在此基础上又增加了大约 25 MB!),并且基本上从未被常规用户代码使用。此外,作为先例,官方的 nuget 软件包、官方的 manylinux 镜像以及多个 Linux 发行版都将其排除在外,这并没有造成任何重大问题。

    因此,这似乎是在广泛兼容性和合理的下载/安装大小之间取得平衡的最佳方法。

  • 我不提供任何 .pyc 文件。它们占用下载空间,可以在最终系统上以最小的成本生成,并且删除它们可以消除位置依赖性。(.pyc 文件存储相应 .py 文件的绝对路径并将其包含在回溯中;但是,pybi 是可重定位的,因此正确的路径直到安装后才知道。)

向后兼容性

没有向后兼容性考虑因素。

安全影响

没有安全隐患,除了承担分发二进制文件责任的任何人必须提出一个计划来管理其安全(例如,在 OpenSSL CVE 发布后是否会进行新的构建)。但是,总体而言,我们核心 Python 人员已经在为所有主要平台维护二进制构建(通过 python.org 进行 macOS + Windows 构建,以及通过官方 manylinux 镜像进行 Linux 构建),因此即使我们确实开始在 PyPI 上发布官方的 CPython 构建,也不会真正引发任何新的安全问题。

如何教授

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


来源:https://github.com/python/peps/blob/main/peps/pep-0711.rst

上次修改:2023-09-09 17:39:29 GMT