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,用于在 Linux 发行版上依赖 musl 的 Python 安装的二进制 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”),并要求使用此标签的 wheel 做出类似的承诺。有关设计背后的原理和原因的更多详细信息,请参阅 PEP 600。
musllinux 平台标签仅适用于在 Linux 操作系统上动态链接到 musl libc 并在运行时共享库上执行的 Python 解释器。静态链接的解释器或与其他 libc 实现(如 glibc)混合构建的解释器超出了范围,并且不受本文档中定义的平台标签支持。此类解释器不应声称与 musllinux 平台标签兼容。
规范
使用新方案的标签将采用以下形式
musllinux_${MUSLMAJOR}_${MUSLMINOR}_${ARCH}
此标签承诺,按照常青设计,该 wheel 可在任何使用 musl 版本 ${MUSLMAJOR}.${MUSLMINOR} 的主流 Linux 发行版上运行。所有其他系统级依赖项要求都依赖于社区对 PEP 600 中引入的有意模糊的“主流”描述的定义。当所有使用指定 musl 版本的主流发行版默认提供该依赖项时,wheel 可以使用更新的系统依赖项;一旦 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_1wheel 包含仅在 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