平台兼容性标签

平台兼容性标签允许构建工具将发行版标记为与特定平台兼容,并允许安装程序理解哪些发行版与它们运行的系统兼容。

概述

标签格式为 {python tag}-{abi tag}-{platform tag}

Python 标签

“py27”、“cp33”

ABI 标签

“cp32dmu”、“none”

平台标签

“linux_x86_64”、“any”

例如,标签 py27-none-any 表示与 Python 2.7(任何 Python 2.7 实现)兼容,无 ABI 要求,可在任何平台上运行。

wheel 构建的包格式在其文件名中包含这些标签,形式为 {distribution}-{version}(-{build tag})?-{python tag}-{abitag}-{platform tag}.whl。其他包格式可能有自己的约定。

任何标签中可能存在的空格都应替换为 _

Python 标签

Python 标签表示发行版所需的实现和版本。主要实现有缩写代码,最初是

  • py: 通用 Python(不要求特定于实现的功能)

  • cp: CPython

  • ip: IronPython

  • pp: PyPy

  • jy: Jython

其他 Python 实现应使用 sys.implementation.name

版本是 py_version_nodot。CPython 省去了点,但如果需要,则使用下划线 _ 代替。PyPy 可能应该在这里使用自己的版本,例如 pp18, pp19

对于许多纯 Python 发行版,版本可以是主版本 23,例如 py2, py3

重要的是,诸如 py2py3 这样的仅包含主版本的标签并非 py20py30 的缩写。相反,这些标签表示打包者有意发布了一个跨版本兼容的发行版。

一个单一源代码的 Python 2/3 兼容发行版可以使用复合标签 py2.py3。请参阅下面的 压缩标签集

ABI 标签

ABI 标签表示任何包含的扩展模块所需的 Python ABI。对于特定于实现的 ABI,实现以与 Python 标签相同的方式缩写,例如,cp33d 将是带调试功能的 CPython 3.3 ABI。

CPython 稳定 ABI 是 abi3,与共享库后缀一样。

ABI 非常不稳定的实现可以使用其源代码修订和编译器标志等的 SHA-256 散列的前 6 字节(作为 8 个 base64 编码字符),但可能不需要大量分发二进制发行版。每个实现的社区可以决定如何最好地使用 ABI 标签。

平台标签

基本平台标签

最简单的形式,平台标签是 sysconfig.get_platform(),其中所有连字符 - 和句点 . 都替换为下划线 _。在 Python 3.12 中移除 distutils 之前,这是 distutils.util.get_platform()。例如

  • win32

  • linux_i386

  • linux_x86_64

manylinux

由于 Linux 平台生态系统庞大且它们之间存在细微差异,上述简单方案不足以将 wheel 文件公开发布到 Linux 平台。

相反,对于这些平台,manylinux 标准代表了 Linux 平台的一个公共子集,并允许构建带有 manylinux 平台标签的 wheel,这些 wheel 可以在大多数常见的 Linux 发行版上使用。

当前的标准是面向未来的 manylinux_x_y 标准。它定义了 manylinux_x_y_arch 形式的标签,其中 xy 是支持的 glibc 主次版本(例如 manylinux_2_24_xxx 应适用于使用 glibc 2.24+ 的任何发行版),arch 是体系结构,与系统上 sysconfig.get_platform() 的值匹配,如上面“简单”形式所述。

以下旧标签仍受支持以实现向后兼容性

  • manylinux1 支持 x86_64i686 架构上的 glibc 2.5。

  • manylinux2010 支持 x86_64i686 上的 glibc 2.12。

  • manylinux2014 支持 x86_64i686aarch64armv7lppc64ppc64les390x 上的 glibc 2.17。

通常,为旧版规范构建的发行版是向前兼容的(意味着 manylinux1 发行版应继续在现代系统上运行),但不向后兼容(意味着 manylinux2010 发行版预计不会在 2010 年之前存在的平台上运行)。

包维护者应尝试针对最兼容的规范,但需要注意的是,manylinux1manylinux2010 提供的构建环境已达到生命周期结束,这意味着这些镜像将不再接收安全更新。

下表显示了支持各种 manylinux 标准的相关项目的最低版本

工具

manylinux1

manylinux2010

manylinux2014

manylinux_x_y

pip

>=8.1.0

>=19.0

>=19.3

>=20.3

auditwheel

>=1.0.0

>=2.0.0

>=3.0.0

>=3.3.0 [1]

musllinux

musllinux 系列标签类似于 manylinux,但适用于使用 musl libc 而非 glibc 的 Linux 平台(一个主要例子是 Alpine Linux)。该方案是 musllinux_x_y_arch,支持架构 arch 上的 musl x.y 及更高版本。

musl 版本值可以通过执行 Python 解释器当前运行的 musl libc 共享库并解析输出获得

import re
import subprocess

def get_musl_major_minor(so: str) -> tuple[int, int] | None:
    """Detect musl runtime version.

    Returns a two-tuple ``(major, minor)`` that indicates musl
    library's version, or ``None`` if the given libc .so does not
    output expected information.

    The libc library should output something like this to stderr::

        musl libc (x86_64)
        Version 1.2.2
        Dynamic Program Loader
    """
    proc = subprocess.run([so], stderr=subprocess.PIPE, text=True)
    lines = (line.strip() for line in proc.stderr.splitlines())
    lines = [line for line in lines if line]
    if len(lines) < 2 or lines[0][:4] != "musl":
        return None
    match = re.match(r"Version (\d+)\.(\d+)", lines[1])
    if match:
        return (int(match.group(1)), int(match.group(2)))
    return None

目前有两种可能的方法来找到 Python 解释器正在运行的 musl 库的位置:使用系统 ldd 命令,或者通过解析可执行文件的 ELF 头中的 PT_INTERP 部分的值。

macOS

macOS 使用 macosx 系列标签(x 后缀是 Apple 官方 macOS 命名方案的历史遗留产物)。兼容性标签的模式是 macosx_x_y_arch,表示 wheel 兼容 macOS x.y 或更高版本,并在 arch 架构上。

xy 的值分别对应 macOS 版本的_主_和_次_版本号。它们都必须是正整数,且 x 的值必须 >= 10。版本号始终包含主版本和次版本,即使 Apple 的官方版本编号只提及主版本。例如,macosx_11_0_arm64 表示兼容 macOS 11 或更高版本。

macOS 二进制文件可以针对单个架构编译,也可以在同一个二进制文件中包含对多个架构的支持(有时称为“胖”二进制文件)。要表示支持单个架构,arch 的值必须与系统上 platform.machine() 的值匹配。要表示支持多个架构,arch 标签应为以下列表中描述支持架构集的标识符

arch

支持的架构

universal2

arm64x86_64

universal

i386ppcppc64x86_64

intel

i386x86_64

fat

i386ppc

fat3

i386ppcx86_64

fat64

ppc64x86_64

支持的最低 macOS 版本也可能受到架构的限制。例如,macOS 11 (Big Sur) 是第一个支持 arm64 的版本。这些额外的限制由 macOS 编译工具链在构建支持多个架构的二进制文件时透明地执行。

安卓

Android 使用 android_apilevel_abi 方案,表示与给定 Android API 级别或更高版本兼容,并在给定 ABI 上。例如,android_27_arm64_v8a 表示支持 API 级别 27 或更高版本,在 arm64_v8a 设备上。Android 不区分物理设备和模拟设备。

API 级别应为正整数。这与面向用户的 Android 版本_不同_。例如,Android 12(代号“Snow Cone”)根据所使用的具体 Android 版本,使用 API 级别 31 或 32。Android 的发布文档包含Android 版本及其对应 API 级别的完整列表

共有 4 个受支持的 ABI。根据上述规则进行标准化后,它们是

  • armeabi_v7a

  • arm64_v8a

  • x86

  • x86_64

几乎所有当前的物理设备都使用 ARM 架构之一。x86x86_64 受支持用于模拟器。x86 自 2020 年以来不再作为开发平台支持,此后也没有发布新的模拟器镜像。

iOS

iOS 使用 ios_x_y_arch_sdk 方案,表示兼容 iOS x.y 或更高版本,在 arch 架构上,使用 sdk SDK。

xy 的值分别对应 iOS 版本的_主_和_次_版本号。它们都必须是正整数。版本号始终包含主版本和次版本,即使 Apple 的官方版本编号只提及主版本。例如,ios_13_0_arm64_iphonesimulator 表示兼容 iOS 13 或更高版本。

arch 的值必须与系统上 platform.machine() 的值匹配。

sdk 的值必须是 iphoneos(用于物理设备)或 iphonesimulator(用于设备模拟器)。这些 SDK 具有相同的 API 表面,但在二进制级别上不兼容,即使它们在相同的 CPU 架构上运行。为 arm64 模拟器编译的代码不会在 arm64 设备上运行。

arch_sdk 的组合被称为“多架构”。多架构有三种可能的值

  • arm64_iphoneos,用于物理 iPhone/iPad 设备。这包括自 2015 年以来制造的所有 iOS 设备;

  • arm64_iphonesimulator,用于在 Apple Silicon macOS 硬件上运行的模拟器;以及

  • x86_64_iphonesimulator,用于在 x86_64 硬件上运行的模拟器。

使用

安装程序使用标签来决定从潜在的已构建发行版列表中下载哪个已构建发行版(如果有)。安装程序维护一个它将支持的 (pyver, abi, arch) 元组列表。如果已构建发行版的标签 列表中,则可以安装。

建议安装程序默认选择功能最完整的已构建发行版(最特定于安装环境的),然后才回退到为旧版 Python 发布纯 Python 版本。还建议安装程序提供一种方法来配置和重新排序允许的兼容性标签列表;例如,用户可能只接受 *-none-any 标签,以便只下载声明为纯 Python 的已构建包。

安装程序的另一个理想功能可能是将“如果可能,从源代码重新编译”视为比某些兼容但旧的预构建选项更优选。

此示例列表适用于在 linux_x86_64 系统上运行 CPython 3.3 的安装程序。它的顺序是从最优先(带有编译扩展模块、为当前 Python 版本构建的发行版)到最不优先(使用旧版本 Python 构建的纯 Python 发行版)

  1. cp33-cp33m-linux_x86_64

  2. cp33-abi3-linux_x86_64

  3. cp3-abi3-linux_x86_64

  4. cp33-none-linux_x86_64*

  5. cp3-none-linux_x86_64*

  6. py33-none-linux_x86_64*

  7. py3-none-linux_x86_64*

  8. cp33-none-any

  9. cp3-none-any

  10. py33-none-any

  11. py3-none-any

  12. py32-none-any

  13. py31-none-any

  14. py30-none-any

  • 已构建的发行版可能因除 C 扩展之外的原因而具有平台特定性,例如包含作为子进程调用的原生可执行文件。

有时,对于特定版本的软件包,会有多个受支持的已构建发行版。例如,打包者可能会发布一个标记为 cp33-abi3-linux_x86_64 的包,其中包含一个可选的 C 扩展,以及一个标记为 py3-none-any 的相同发行版,其中不包含 C 扩展。标签在受支持标签列表中的索引打破了僵局,带有 C 扩展的包将优先于不带 C 扩展的包安装,因为该标签首先出现在列表中。

压缩标签集

为了允许 bdist 的紧凑文件名,这些 bdist 可与多个兼容性标签三元组一起工作,文件名中的每个标签都可以是一个“.”分隔的、已排序的标签集合。例如,pip 是一个纯 Python 包,使用相同的源代码在 Python 2 和 3 下运行,它可以分发一个带有标签 py2.py3-none-any 的 bdist。简单标签的完整列表是

for x in pytag.split('.'):
    for y in abitag.split('.'):
        for z in platformtag.split('.'):
            yield '-'.join((x, y, z))

实现此方案的 bdist 格式应在 bdist 特定的元数据中包含扩展标签。此压缩方案可以生成大量不受支持的标签和没有任何 Python 实现支持的“不可能”标签,例如“cp33-cp31u-win64”,因此请谨慎使用。

常见问题

默认使用哪些标签?

工具应默认使用最优先的架构相关标签,例如 cp33-cp33m-win32,或者最优先的纯 Python 标签,例如 py33-none-any。如果打包者覆盖了默认值,则表示他们旨在提供跨 Python 的兼容性。

如果我的发行版使用了最新 Python 版本独有的功能,我应该使用哪个标签?

兼容性标签帮助安装程序选择一个发行版_单个版本_的_最兼容_构建。例如,当 beaglevote-1.2.0 没有 Python 3.3 兼容构建时(因为它使用了 Python 3.4 独有的功能),它仍然可以使用 py3-none-any 标签而不是 py34-none-any 标签。Python 3.3 用户必须结合其他限定符,例如对不使用新功能的旧版本 beaglevote-1.1.0 的要求,才能获得兼容的构建。

为什么 Python 版本号中没有 .

CPython 已经持续了 20 多年,没有出现 3 位数的主版本号。这种情况应该还会持续一段时间。其他实现可以使用 _ 作为分隔符,因为 - 和 . 都分隔周围的文件名。

为什么要将连字符和其他非字母数字字符规范化为下划线?

为了避免与分隔文件名的组件的 .- 字符冲突,并为了更好地兼容最广泛的文件名文件系统限制(包括在 URL 路径中无需引用即可使用)。

为什么不使用特殊字符 <X> 而不是 .-

要么是因为该字符在某些上下文中不方便或可能令人困惑(例如,+ 在 URL 中必须被引用,~ 在 POSIX 中用于表示用户的主目录),要么是因为其优点不足以证明更改 PEP 427 中定义的 wheel 格式的现有参考实现(例如,使用 , 而不是 . 来分隔压缩标签中的组件)。

谁来维护缩写实现的注册表?

新的两位字母缩写可以在 python-dev 邮件列表中请求。通常,缩写保留给当前最突出的 4 个实现。

兼容性标签是否会写入 METADATA 或 PKG-INFO?

不。兼容性标签是已构建发行版元数据的一部分。METADATA / PKG-INFO 应适用于整个发行版,而不是该发行版的一个单一构建。

为什么你没有提到我最喜欢的 Python 实现?

缩写标签有助于在公共索引中共享编译的 Python 代码。你的 Python 实现也可以使用此规范,但使用更长的标签。请记住,所有“纯 Python”构建的发行版都只使用 py

为什么 ABI 标签(第二个标签)在参考实现中有时是“none”?

由于 Python 2 没有简单的方法来获取 SOABI(这个概念来自 Python 3 的较新版本),撰写本文时,参考实现猜测为“none”。理想情况下,它会检测“py27(d|m|u)”,类似于 Python 的较新版本,但在此期间,“none”是一种足够好的表达“不知道”的方式。

历史

  • 2013 年 2 月:此规范的原始版本通过 PEP 425 获得批准。

  • 2016 年 1 月:manylinux1 标签通过 PEP 513 获得批准。

  • 2018 年 4 月:manylinux2010 标签通过 PEP 571 获得批准。

  • 2019 年 7 月:manylinux2014 标签通过 PEP 599 获得批准。

  • 2019 年 11 月:manylinux_x_y 常青标签通过 PEP 600 获得批准。

  • 2021 年 4 月:musllinux_x_y 标签通过 PEP 656 获得批准。

  • 2023 年 12 月:iOS 标签通过 PEP 730 获得批准。

  • 2024 年 3 月:Android 标签通过 PEP 738 获得批准。