平台兼容性标签#
平台兼容性标签允许构建工具将发行版标记为与特定平台兼容,并允许安装程序了解哪些发行版与它们运行的系统兼容。
概述#
标签格式为 {python tag}-{abi tag}-{platform tag}
。
- python 标签
‘py27’,‘cp33’
- abi 标签
‘cp32dmu’,‘none’
- platform 标签
‘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
。
版本可以只是主版本 2
或 3
py2
、py3
对于许多纯 Python 发行版。
重要的是,仅主版本的标签(如 py2
和 py3
)不是 py20
和 py30
的简写。相反,这些标签表示打包器有意发布了跨版本兼容的发行版。
单源 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 平台的庞大生态系统及其之间的细微差别,上述简单方案不足以将轮子文件公开分发到 Linux 平台。
相反,对于这些平台,manylinux
标准表示 Linux 平台的公共子集,并允许构建标记有 manylinux
平台标记的轮子,该标记可在大多数常见 Linux 发行版中使用。
当前标准是面向未来的 manylinux_x_y
标准。它定义了 manylinux_x_y_arch
形式的标记,其中 x
和 y
是支持的 glibc 主版本和次版本(例如,manylinux_2_24_xxx
应该适用于使用 glibc 2.24+ 的任何发行版),而 arch
是架构,与 sysconfig.get_platform()
在系统上的值匹配,如上文中的“简单”形式。
出于向后兼容性的考虑,仍支持以下较旧标记
manylinux1
在x86_64
和i686
架构上支持 glibc 2.5。manylinux2010
在x86_64
和i686
上支持 glibc 2.12。manylinux2014
在x86_64
、i686
、aarch64
、armv7l
、ppc64
、ppc64le
和s390x
上支持 glibc 2.17。
一般来说,为旧版规范构建的发行版向前兼容(这意味着 manylinux1
发行版应继续在现代系统上运行),但向后不兼容(这意味着 manylinux2010
发行版预计无法在 2010 年之前存在的平台上运行)。
软件包维护人员应尝试针对尽可能兼容的规范,但需要注意的是,manylinux1
和 manylinux2010
的提供的构建环境已达到生命周期终点,这意味着这些映像将不再收到安全更新。
下表显示了支持各种 manylinux
标准的相关项目的最低版本
工具 |
|
|
|
|
---|---|---|---|---|
pip |
|
|
|
|
auditwheel |
|
|
|
|
musllinux
#
musllinux
系列标签与 manylinux
类似,但适用于使用 musl libc 而不是 glibc 的 Linux 平台(一个典型示例是 Alpine Linux)。架构为 arch
的 musl x.y
及更高版本支持架构 musllinux_x_y_arch
。
可以通过执行 Python 解释器当前正在运行的 musl libc 共享库并解析输出,来获取 musl 版本值
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
部分的值。
使用#
安装程序使用这些标签来决定从潜在构建发行版列表中下载哪个构建发行版(如果有)。安装程序维护一个它将支持的 (pyver, abi, arch) 元组列表。如果构建发行版的标签 in
列表中,则可以安装它。
建议安装程序尝试在回退到为较旧 Python 版本发布的纯 Python 版本之前,默认选择最完整的可用构建发行版(最适用于安装环境的版本)。还建议安装程序提供一种配置和重新排序允许兼容性标签列表的方法;例如,用户可能只接受 *-none-any
标签,以便仅下载自称为纯 Python 的构建包。
另一个理想的安装程序功能可能是将“如果可能,从源代码重新编译”作为比某些兼容但过时的预构建选项更可取的选项。
此示例列表适用于在 linux_x86_64 系统上运行 CPython 3.3 的安装程序。它按从最优先(具有为 Python 当前版本构建的已编译扩展模块的发行版)到最不优先(使用较旧 Python 版本构建的纯 Python 发行版)的顺序排列
cp33-cp33m-linux_x86_64
cp33-abi3-linux_x86_64
cp3-abi3-linux_x86_64
cp33-none-linux_x86_64*
cp3-none-linux_x86_64*
py33-none-linux_x86_64*
py3-none-linux_x86_64*
cp33-none-any
cp3-none-any
py33-none-any
py3-none-any
py32-none-any
py31-none-any
py30-none-any
构建发行版可能是特定于平台的,原因不仅仅是 C 扩展,还包括将本机可执行文件作为子进程调用。
有时,特定版本的包将有多个受支持的构建发行版。例如,打包程序可以发布标记为 cp33-abi3-linux_x86_64
的包,其中包含可选的 C 扩展,以及标记为 py3-none-any
的相同发行版,但不包含 C 扩展。受支持标签列表中标签的索引打破了平局,并且带有 C 扩展的包优先于没有 C 扩展的包安装,因为该标签在列表中首先出现。
压缩标签集#
为了允许与多个兼容性标签三元组一起使用的 bdist 的紧凑文件名,文件名中的每个标签都可以是“.”分隔的、排序的标签集。例如,pip 是一个纯 Python 包,编写为使用相同的源代码在 Python 2 和 3 下运行,它可以分发带有标签 py2.py3-none-any
的 bdist。简单标签的完整列表是
for x in pytag.split('.'):
for y in abitag.split('.'):
for z in archtag.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 中定义的轮格式的现有参考实现(例如,使用,
而不是.
来分隔压缩标签中的组件)。- 谁将维护缩写实现的注册表?
可以在 python-dev 邮件列表中请求新的两个字母缩写。根据经验,缩写保留给当前最突出的 4 个实现。
- 兼容性标签是进入 METADATA 还是 PKG-INFO?
否。兼容性标签是构建的发行版元数据的一部分。METADATA/PKG-INFO 应适用于整个发行版,而不是该发行版的单个版本。
- 为什么没有提到我最喜欢的 Python 实现?
缩写标签有助于在公共索引中共享已编译的 Python 代码。你的 Python 实现也可以使用此规范,但标签会更长。请记住,所有“纯 Python”构建的发行版都只使用
py
。- 为什么 ABI 标签(第二个标签)在参考实现中有时是“none”?
由于 Python 2 没有一种简单的方法来获取 SOABI(这个概念来自 Python 3 的较新版本),因此在撰写本文时,参考实现猜测为“none”。理想情况下,它会检测到类似于 Python 较新版本的“py27(d|m|u)”,但在此期间,“none”是表示“不知道”的一种足够好的方式。