Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 增强提案

PEP 571 – manylinux2010 平台标签

作者:
Mark Williams <mrw at enotuniq.org>,Geoffrey Thomas <geofft at ldpreload.com>,Thomas Kluyver <thomas at kluyver.me.uk>
BDFL 代表:
Alyssa Coghlan <ncoghlan at gmail.com>
讨论列表:
Distutils-SIG 列表
状态:
已取代
类型:
信息性
主题:
打包
创建日期:
2018 年 2 月 5 日
修订历史:

取代版本:
600
决议:
Distutils-SIG 消息

目录

摘要

本 PEP 提案创建一个 manylinux2010 平台标签,以取代由 PEP 513 引入的 manylinux1 标签。它还建议更新 PyPI 和 pip 以支持在兼容平台上上传、下载和安装 manylinux2010 发行版。

基本原理

顾名思义,manylinux1 平台标签使得在许多 Linux 系统上安装二进制扩展模块成为现实。像 cryptography [2]numpy [3] 这样的库现在更容易被 Python 开发人员访问,因为它们在常见架构上的安装不再依赖于脆弱的开发环境和构建工具链。

manylinux1 wheel 通过允许它们包含的扩展模块仅链接到一小部分导出足够旧的版本化符号以受益于向后兼容性策略的系统级共享库来实现其可移植性。例如,manylinux1 wheel 中依赖于 glibc 的扩展模块必须针对 2.5 或更早版本构建;然后它们可以在提供更新的 glibc 版本但仍然在 2.5 版本导出所需符号的系统上运行。

PEP 513 从 CentOS 5.11 中提取了其白名单共享库及其符号版本,CentOS 5.11 是其编写时最旧的支持的 CentOS 版本。不幸的是,CentOS 5.11 于 2017 年 3 月 31 日终止支持,并明确警告不要继续使用。 [4] 将不再提供任何更新,例如安全补丁。这意味着其软件包将保持在过时的版本,这阻碍了使用 manylinux1 Docker 镜像的 Python 软件打包人员的工作。

CentOS 6 现在是最旧的支持的 CentOS 版本,并且将在 2020 年 11 月 30 日之前收到维护更新。 [5] 我们建议从 CentOS 6 派生一个名为 manylinux2010 的新的 PEP 425 样式平台标签,并且更新 manylinux 工具链、PyPI 和 pip 以支持它。

最初将其提议为 manylinux2,但版本控制已更改为使用日历年份(也称为 CalVer [22])。这使得更容易无序地定义未来的 manylinux 标签:例如,假设的 manylinux2017 标准可以在 manylinux2014 之前通过新的 PEP 定义,或者可以定义 manylinux2007 标准,该标准针对比本 PEP 旧但比 manylinux1 新的系统。

日历版本控制也大致了解哪些 Linux 发行版支持哪些标签:manylinux2010 将适用于 2010 年以来发布的大多数发行版版本。但这只是一个近似值:实际的兼容性规则定义如下,一些更新的发行版可能不满足这些规则。

manylinux2010 策略

以下标准决定了 linux wheel 是否有资格获得 manylinux2010 标签

  1. wheel 只能包含为 CentOS 6 支持的两种架构之一编译的二进制可执行文件和共享对象:x86_64 或 i686。 [5]
  2. wheel 的二进制可执行文件或共享对象不得链接到外部提供的库,除非是在以下白名单中
    libgcc_s.so.1
    libstdc++.so.6
    libm.so.6
    libdl.so.2
    librt.so.1
    libc.so.6
    libnsl.so.1
    libutil.so.1
    libpthread.so.0
    libresolv.so.2
    libX11.so.6
    libXext.so.6
    libXrender.so.1
    libICE.so.6
    libSM.so.6
    libGL.so.1
    libgobject-2.0.so.0
    libgthread-2.0.so.0
    libglib-2.0.so.0
    

    此列表与 manylinux1 的白名单外部提供的库相同,减去 libncursesw.so.5libpanelw.so.5[7] libpythonX.Y 由于与 PEP 513 中概述的原因相同,因此仍不符合包含条件。

    在 Fedora 30 发布包含 libcrypt.so.2 而不是 libcrypt.so.1 后,libcrypt.so.1 已从白名单中追溯删除。

    在基于 Debian 的系统上,这些库由以下软件包提供

    软件包
    libc6 libdl.so.2, libresolv.so.2, librt.so.1, libc.so.6, libpthread.so.0, libm.so.6, libutil.so.1, libnsl.so.1
    libgcc1 libgcc_s.so.1
    libgl1 libGL.so.1
    libglib2.0-0 libgobject-2.0.so.0, libgthread-2.0.so.0, libglib-2.0.so.0
    libice6 libICE.so.6
    libsm6 libSM.so.6
    libstdc++6 libstdc++.so.6
    libx11-6 libX11.so.6
    libxext6 libXext.so.6
    libxrender1 libXrender.so.1

    在基于 RPM 的系统上,它们由这些软件包提供

    软件包
    glib2 libglib-2.0.so.0, libgthread-2.0.so.0, libgobject-2.0.so.0
    glibc libresolv.so.2, libutil.so.1, libnsl.so.1, librt.so.1, libpthread.so.0, libdl.so.2, libm.so.6, libc.so.6
    libICE libICE.so.6
    libX11 libX11.so.6
    libXext libXext.so.6
    libXrender libXrender.so.1
    libgcc libgcc_s.so.1
    libstdc++ libstdc++.so.6
    mesa libGL.so.1
  3. 如果 wheel 包含链接到任何也导出版本化符号的白名单库的二进制可执行文件或共享对象,则它们只能依赖于以下最大版本
    GLIBC_2.12
    CXXABI_1.3.3
    GLIBCXX_3.4.13
    GCC_4.5.0
    

    例如,manylinux2010 wheel 可以包含需要 glibc 版本为 GLIBC_2.4 的符号的二进制构件,因为这比 GLIBC_2.12 的最大值早。

  4. 如果为任何版本的 CPython 2 或 CPython 3.0 及其包括 3.2 的版本构建 wheel,则 *必须* 包含一个指示其 Unicode ABI 的 CPython ABI 标签。然后,针对 Python 2 构建的 manylinux2010 wheel 必须包含 cpy27mu 标签(表示它针对具有 UCS-4 ABI 的解释器构建)或 cpy27m 标签(表示针对具有 UCS-2 ABI 的解释器构建)。(PEP 3149[9]
  5. wheel *不得* 要求使用 PyFPE_jbuf 符号。这是通过针对 *不带* --with-fpectl configure 标记编译的 Python 构建它来实现的。

兼容 Wheel 的编译

manylinux1 一样,auditwheel 工具将 manylinux2010 平台标签添加到由 pip wheelbdist_wheelmanylinux2010 Docker 容器中构建的 linux wheel。

Docker 镜像

提供了两个基于 CentOS 6 的 manylinux2010 Docker 镜像,用于构建可以可靠地转换为 manylinux2010 wheel 的二进制 linux wheel。 [10] x86_64 和 i686 镜像安装了新的编译器套件(来自 devtoolset-8gccg++gfortran),以及 Python 和 pip 的最新版本。

与缺少 vsyscall 的内核的兼容性

Docker 容器假定其用户空间与其主机的内核兼容。不幸的是,越来越常见的内核配置打破了 x86_64 CentOS 6 Docker 镜像的这一假设。

glibc 2.14 及更早版本中,需要内核在 x86_64 上提供一种称为 vsyscall 的过时系统调用优化。[11] 为了实现该优化,内核将一个包含常用系统调用的只读页面(最显著的是 time(2))映射到每个进程的固定内存位置。然后,glibc 通过对指向 vsyscall 页面中适当偏移量的函数指针进行解引用并调用它来调用这些系统调用。这避免了影响普通系统调用调用的内核调用相关的开销。vsyscall 早已弃用,取而代之的是一种称为 vDSO 或“虚拟动态共享对象”的等效机制,其中内核将包含优化系统调用的可重定位虚拟共享对象映射到每个进程中。[12]

vsyscall 页面存在严重的安全隐患,因为它不参与地址空间布局随机化 (ASLR)。其可预测的位置和内容使其成为返回导向编程攻击中使用的代码片段的有用来源。[13] 同时,它的消除破坏了 x86_64 ABI,因为依赖于 vsyscallglibc 版本在尝试对指向不存在页面的系统调用指针进行解引用时会出现段错误。作为一种折衷方案,Linux 3.1 实现了一个“模拟”的 vsyscall,它减少了映射到进程中的可执行代码,从而减少了 ROP 代码片段的材料。[14] 多年来,vsyscall=emulated 一直是大多数发行版内核的默认配置。

不幸的是,vsyscall 模拟仍然在可靠的内存位置公开可预测的代码,并且仍然可用于返回导向编程。[15] 由于大多数发行版现已升级到不依赖于 vsyscallglibc 版本,因此它们开始发布完全不支持 vsyscall 的内核。[16]

CentOS 5.11 和 6 都包含依赖于 vsyscall 页面的 glibc 版本(分别为 2.5 和 2.12.2),因此基于这两个版本的容器无法在许多发行版即将发布的内核版本下运行。[17] 例如,如果 Travis CI 开始在不提供 vsyscall 接口的内核下运行作业,那么 Python 包管理器将无法在那里使用我们的 Docker 镜像来构建 manylinux 轮子。[18]

我们从 glibc git 仓库中提取了一个补丁,它将删除所有对 vsyscall 的依赖关系的功能移植到我们 manylinux2010 镜像中包含的 glibc 版本。[19] 重新构建 glibc,以及 manylinux2010 镜像本身,仍然需要一个提供 vsyscall 机制的宿主机内核,但生成的镜像既可以在提供该机制的宿主机上运行,也可以在不提供该机制的宿主机上运行。因为 vsyscall 接口是一种仅应用于正在运行的进程的优化,所以使用此修改后的镜像构建的 manylinux2010 轮子应该与在未修改的 CentOS 6 系统上构建的轮子相同。此外,vsyscall 问题仅适用于 x86_64;它不是 i686 ABI 的一部分。

Auditwheel

auditwheel 工具也已更新以生成 manylinux2010 轮子。[20] 除了这一点,它的行为和目的与 PEP 513 中描述的一样。

安装程序的平台检测

平台可以在 PEP 513 中描述的 _manylinux 模块上定义一个 manylinux2010_compatible 布尔属性。如果该属性为 False,则认为该平台与 manylinux2010 不兼容。

如果找不到 _manylinux 模块,或者它没有 manylinux2010_compatible 属性,则工具可能会回退到检查 glibc。如果平台具有 glibc 2.12 或更高版本,则假设它与 manylinux2010 兼容,除非 _manylinux 模块另有说明。

具体来说,我们提出的算法是

def is_manylinux2010_compatible():
    # Only Linux, and only x86-64 / i686
    from distutils.util import get_platform
    if get_platform() not in ["linux-x86_64", "linux-i686"]:
        return False

    # Check for presence of _manylinux module
    try:
        import _manylinux
        return bool(_manylinux.manylinux2010_compatible)
    except (ImportError, AttributeError):
        # Fall through to heuristic check below
        pass

    # Check glibc version. CentOS 6 uses glibc 2.12.
    # PEP 513 contains an implementation of this function.
    return have_compatible_glibc(2, 12)

与 manylinux1 wheel 的向后兼容性

PEP 513 中所解释的,为 manylinux1 白名单库指定的符号版本构成一个上限。对于本 PEP 中为 manylinux2010 定义的符号版本也是如此。因此,manylinux1 轮子被视为 manylinux2010 轮子。因此,识别 manylinux2010 平台标签的 pip 将为 manylinux2010 平台安装 manylinux1 轮子(即使显式设置),前提是没有任何可用的 manylinux2010 轮子。[21]

PyPI 支持

PyPI 应该允许上传包含 manylinux2010 平台标签的轮子,就像允许上传 manylinux1 轮子一样。它不应尝试验证 manylinux2010 轮子的兼容性。

PEP 571 的更改总结

根据在 PEP 批准后收到的反馈,对 PEP 进行了以下更改

  • libgcc_s 的最大版本符号已从 GCC_4.3.0 更新到 GCC_4.5.0 以解决 32 位 Cent OS 6 的问题。这不会影响 x86_64,因为 x86_64 的 libgcc_sGCC_4.3.0GCC_4.5.0 之间没有额外的符号。

参考文献


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

最后修改时间:2023-10-11 12:05:51 GMT