PEP 656 – 使用 Musl 的 Linux 发行版的平台标签
- 作者:
- Tzu-ping Chung <uranusjr at gmail.com>
- 赞助商:
- Brett Cannon <brett at python.org>
- PEP 代表:
- Paul Moore <p.f.moore at gmail.com>
- 讨论地址:
- Discourse 线程
- 状态:
- 最终
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建:
- 2021 年 3 月 17 日
- 更新历史:
- 2021 年 3 月 17 日,2021 年 4 月 18 日
- 决议:
- Discourse 消息
摘要
本 PEP 提议了一个新的平台标签系列 musllinux
,用于 Python 安装依赖于 Linux 发行版上的 musl 的 Python 二进制包分发。该标签与 PEP 600 中指定的“永久 manylinux”平台标签类似,但目标是基于 musl 的平台。
动机
随着容器的广泛使用,像 Alpine Linux [alpine] 这样的发行版比以往任何时候都更受欢迎。它们中的许多基于 musl [musl],它是与 glibc 不同的 libc 实现,因此无法使用现有的 manylinux
平台标签。这意味着 Python 包项目无法在 PyPI 上为它们部署二进制分发。这些项目的使用者要求这些项目进行构建约束,给项目维护者带来不必要的负担。
理由
根据文档,musl 具有稳定的 ABI,并且保持向后兼容性 [musl-compatibility] [compare-libcs],因此针对早期版本 musl 编译的二进制文件保证可以在较新 musl 运行时上运行 [musl-compat-ml]。因此,我们使用与基于 glibc 版本的 manylinux 标签类似的方案,但针对的是 musl 版本而不是 glibc。
新平台标签背后的逻辑很大程度上遵循 PEP 600(“永久 manylinux”),并且要求使用该标签的轮子做出类似的承诺。有关该设计背后的理由和论证的更多详细信息,请参阅 PEP 600。
The musllinux
平台标签仅适用于针对 musl libc 动态链接并在 Linux 操作系统上的运行时共享库上执行的 Python 解释器。静态链接的解释器或与其他 libc 实现(例如 glibc)混合构建不在本文件定义的平台标签的范围内,也不受其支持。此类解释器不应声明与 musllinux
平台标签的兼容性。
规范
使用新方案的标签将采用以下形式
musllinux_${MUSLMAJOR}_${MUSLMINOR}_${ARCH}
该标签承诺该轮子适用于任何使用 musl 版本 ${MUSLMAJOR}.${MUSLMINOR}
的主流 Linux 发行版,遵循永久设计。所有其他系统级依赖关系要求依赖于社区对 PEP 600 中引入的故意模糊的“主流”描述的定义。当所有使用指定 musl 版本的主流发行版默认提供依赖关系时,轮子可以使用较新的系统依赖关系;一旦所有使用 musl 版本的主流发行版默认提供特定依赖关系版本,依赖于旧版本的使用者将自动从该 musllinux
标签的覆盖范围内删除。
读取 musl 版本
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
命令 [ldd],另一种是解析可执行文件 ELF 头部的 PT_INTERP
部分的值 [elf]。
格式化标签
使用该标签的发行版做出了与 PEP 600 中描述的类似承诺,包括
- 该分发适用于任何 musl 版本为
${MUSLMAJOR}.${MUSLMINOR}
或更高版本的主流 Linux 发行版。 - 该分发的
${ARCH}
与主机系统上sysconfig.get_platform()
的返回值匹配,将句点 (.
) 和连字符 (-
) 字符替换为下划线 (_
),如 PEP 425 和 PEP 427 中所述。
示例值
musllinux_1_1_x86_64 # musl 1.1 running on x86-64.
musllinux_1_2_aarch64 # musl 1.2 running on ARM 64-bit.
可以使用以下 Python 代码格式化该值
import sysconfig
def format_musllinux(musl_version: tuple[int, int]) -> str:
os_name, sep, arch = sysconfig.get_platform().partition("-")
assert os_name == "linux" and sep, "Not a Linux"
arch = arch.replace(".", "_").replace("-", "_")
return f"musllinux_{musl_version[0]}_{musl_version[1]}_{arch}"
对包索引的建议
建议 Python 包仓库(包括 PyPI)接受与以下正则表达式匹配的平台标签
musllinux_([0-9]+)_([0-9]+)_([^.-]+)
Python 包仓库可能会实施额外的要求来拒绝已知存在问题的 Wheel,包括但不限于
- 一个
musllinux_1_1
轮子,其中包含仅在 musl 1.2 或更高版本中可用的符号。 - 依赖于包索引的预期受众无法普遍获得的外部库的 Wheel。
- 一个声称与不存在的 musl 版本兼容的平台标签(例如
musllinux_9000_0
)。
此类策略最终取决于各个包仓库。作者无意对维护者施加限制。
向后兼容性
本 PEP 中没有向后兼容性问题。
被拒绝的想法
基于 Alpine Linux 创建一个平台标签
过去在 manylinux
标签系列上的经验表明,这种方法在时间上成本过高。作者认为“与其他人配合良好”的规则既更具包容性,在实践中也足够有效。
参考文献
版权
本文件置于公共领域或 CC0-1.0-Universal 许可证下,以较宽松者为准。
来源: https://github.com/python/peps/blob/main/peps/pep-0656.rst
最后修改时间: 2023 年 9 月 9 日 17:39:29 GMT