Python 打包准则
32 位 – CLR – CMake – DKMS – Eclipse – Electron – Free Pascal – GNOME – Go – Haskell – Java – 交叉编译工具 – KDE – Lisp – Meson – MinGW – 内核模块 – Node.js – Nonfree – OCaml – Perl – PHP – Python – R – Ruby – Rust – VCS – Web – Wine – 字体
本文档描述了如何基于标准 PKGBUILD 来为 Python 程序进行打包。
包名[编辑 | 编辑源代码]
对于 Python 3 的库模块包或与 Python 生态紧密耦合的应用程序(如 pip 和 tox),使用 python-模块名或应用程序名
命名。对于其他应用程序,则仅使用程序原名。
架构[编辑 | 编辑源代码]
参见 PKGBUILD#arch。
包含 C 扩展的 Python 软件包与架构相关。反之则通常无关于架构。
使用 setuptools 构建的软件包通过在 setup.py
中定义 ext_modules
关键字来声明其使用的 C 扩展。
源代码[编辑 | 编辑源代码]
PyPI 网站提供的下载链接包含不可预测的哈希值,每次更新软件包时需重新从 PyPI 获取。这导致其不适合直接用于 PKGBUILD。PyPI 提供了以下稳定方案:PKGBUILD#source source=()
数组应使用以下 URL 模板:
- 源代码包:
https://files.pythonhosted.org/packages/source/${_name::1}/${_name//-/_}/${_name//-/_}-$pkgver.tar.gz
- 纯 Python wheel 包
https://files.pythonhosted.org/packages/py2.py3/${_name::1}/$_name/${_name//-/_}-$pkgver-py2.py3-none-any.whl
(同时兼容 Python 2 和 Python 3)https://files.pythonhosted.org/packages/py3/${_name::1}/$_name/${_name//-/_}-$pkgver-py3-none-any.whl
(仅兼容 Python 3)- 注意分发包名称(distribution name,在 PyPI 注册的名称)可能包含连字符,但在 wheel 文件名中需转换为下划线。
- 架构相关的 wheel 包
- 可通过追加下划线和架构名添加额外数组(如
source_x86_64=('...')
)。另可使用_py=cp310
避免重复 Python 版本: https://files.pythonhosted.org/packages/$_py/${_name::1}/$_name/${_name//-/_}-$pkgver-$_py-${_py}m-manylinux1_x86_64.whl
注意使用自定义变量 _name
代替 pkgname
,因 Python 软件包通常带有 python-
前缀。该变量可通过以下方式定义:
_name=${pkgname#python-}
安装方式[编辑 | 编辑源代码]
Python 中的包通常使用 Python 专用的包管理器(如 pip)进行安装,这类工具会从在线仓库(通常是 PyPI——Python 软件包索引)获取软件包并追踪相关文件。
然而,在使用 PKGBUILD
管理 Python 软件包时,需要将 Python 包“安装”到临时位置 $pkgdir/usr/lib/python<Python 版本>/site-packages/$pkgname
。
对于使用标准元数据(standard metadata)在 pyproject.toml
中指定构建后端(build backend)的 Python 包,使用 python-build包 和 python-installer包 是完成上述“安装”过程最容易的方式。
旧版本软件包可能未声明使用 setuptools,仅提供需手动调用的 setup.py
。
depends
数组中,否则相关依赖不会被安装。基于标准(PEP 517)的安装方式[编辑 | 编辑源代码]
$pkgver
:
- python-flit-core包、python-hatch-vcs包 或 python-setuptools-scm包:
SETUPTOOLS_SCM_PRETEND_VERSION
- python-pbr包:
PBR_VERSION
- python-pdm-backend包:
PDM_BUILD_SCM_VERSION
基于标准的工作流程很简单:使用 python-build包 构建一个 wheel,再使用 python-installer包 将其安装到 $pkgdir
:
makedepends
。所有仓库可用的构建后端均属于 python-build-backend包组。请检查项目的 pyproject.toml
文件中 build-system.build-backend
配置项的值,该值即为此项目实际使用的构建后端,若未配置则默认使用 python-setuptools包。makedepends=(python-build python-installer python-wheel) build() { cd $_name-$pkgver python -m build --wheel --no-isolation } package() { cd $_name-$pkgver python -m installer --destdir="$pkgdir" dist/*.whl }
其中:
--wheel
表示仅构建 wheel 文件,不生成源代码分发包--no-isolation
表示使用系统已安装的包(含depends
中声明的依赖)进行构建,默认会创建隔离的虚拟环境执行构建--destdir="$pkgdir"
可防止直接尝试安装到宿主系统(而非软件包目录),避免权限错误--compile-bytecode=……
或--no-compile-bytecode
可传递给installer
,但默认选择合理值,通常无需手动指定
build
阶段而直接将 .whl
文件放入 source
数组,应优先采用源代码构建。仅在无法通过源码构建时(例如项目仅提供 wheel 源)才应使用此方式。python-……-git
),请在 prepare
函数中添加 git -C "${srcdir}/${pkgname}" clean -dfx
命令。此操作可清除旧版 wheel 及其他构建产物,避免后续问题。另请参阅 setuptools 与 Poetry 的上游 issue。使用 setuptools 或 distutils 的安装方式[编辑 | 编辑源代码]
若项目中不存在 pyproject.toml
文件,或该文件未包含 [build-system]
表,则说明项目使用遗留格式,即通过 setup.py 文件调用 setuptools 或 distutils.core 的 setup
函数。
此类软件包通常仍可使用上述 python-build包 和 python-installer包 方法构建安装(推荐方式),但需在 makedepends
中添加 python-setuptools包。
通过直接运行 setup.py 的旧方式(见下)仍可构建安装,但此方法已弃用,仅建议在 PEP 517 兼容方式因故不可用时作为备选方案。
需注意,使用此方法构建时会在 package
阶段输出以下警告:
SetuptoolsDeprecationWarning: setup.py install is deprecated.
另请注意,Python 3.12 及后续版本的标准库中已移除 distutils。对于仍使用 setup.py 的项目,必须在 makedepends
中添加 python-setuptools包(该软件包提供了自带的 distutils 实现)。
makedepends=('python-setuptools') build() { cd $_name-$pkgver python setup.py build } package() { cd $_name-$pkgver python setup.py install --root="$pkgdir" --optimize=1 }
其中:
--root="$pkgdir"
作用同前文--destdir
--optimize=1
预生成优化字节码文件(“.opt-1.pyc”)而非在宿主系统按需生成,以便由 pacman 追踪这些文件并保证权限一致--skip-build
跳过不必要的重复构建步骤(如build()
函数已执行过的构建过程)
若软件包使用 python-setuptools-scm包,构建时可能报错如下:
LookupError: setuptools-scm was unable to detect version for /build/python-jsonschema/src/jsonschema-3.2.0. Make sure you're either building from a fully intact git repository or PyPI tarballs. Most other sources (such as GitHub's tarballs, a git checkout without the .git folder) don't contain the necessary metadata and will not work.
需将 SETUPTOOLS_SCM_PRETEND_VERSION
环境变量设为 $pkgver
以完成构建:
export SETUPTOOLS_SCM_PRETEND_VERSION=$pkgver
测试[编辑 | 编辑源代码]
- 避免使用
tox
运行测试套件,因其明确设计用于依据从 PyPI 下载的可复现的、固定的配置环境测试,而不会测试软件包实际安装后的版本,若使用则将使 check 函数失去意义 - 避免在 checkdepends 中添加用于代码检查(lint)、覆盖率或类型检查的 pytest 插件(详见 #禁用 pytest 附加选项章节)。这些工具会增加引导难度且非发行版打包必需,因其不验证功能正确性
大多数提供测试套件的 Python 项目使用 unittest 运行器、nosetests 或 pytest(分别由 python包、python-nose包 和 python-pytest包 提供),通过检测特定格式的文件/目录名和类/函数名(如含 test
的命名)自动发现测试套件。通常直接运行 nosetests
或 pytest
即可执行测试套件。
check(){ cd $_name-$pkgver # 使用内置的 unittest python -m unittest discover -vs . # 使用 nosetests nosetests # 使用 pytest pytest }
若存在已编译的 C 扩展模块,测试时需通过 $PYTHONPATH
指定与当前 Python 主次版本匹配的构建路径以正确加载模块。
check(){ cd $_name-$pkgver local python_version=$(python -c 'import sys; print("".join(map(str, sys.version_info[:2])))') # 使用内置的 unittest PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-$python_version" python -m unittest discover -vs . # 使用 nosetests PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-$python_version" nosetests # 使用 pytest PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-$python_version" pytest }
提示和技巧[编辑 | 编辑源代码]
获取 Python 版本号[编辑 | 编辑源代码]
在准备、构建、测试或安装过程中,如需引用系统 Python 的主次版本号(如 3.9
或 3.10
),请勿硬编码版本号,而应通过 Python 解释器动态获取并存储至局部变量:
check(){ local python_version=$(python -c 'import sys; print(".".join(map(str, sys.version_info[:2])))') ... }
获取 site-packages 路径[编辑 | 编辑源代码]
在构建、测试或安装过程中,如需引用系统的 site-packages
目录,请勿硬编码路径,而应通过 Python 解释器动态获取并存储至局部变量:
check(){ local site_packages=$(python -c "import site; print(site.getsitepackages()[0])") ... }
避免在 site-package 中放置测试目录[编辑 | 编辑源代码]
避免在 site-packages/
下直接安装名为 tests/
的目录(即 /usr/lib/pythonX.Y/site-packages/tests/
),否则可能引发软件包冲突。某些使用 setuptools 的 Python 项目存在错误配置,可能将其测试目录作为顶层 Python 包包含。若发现此类问题,请向项目提交 issue 请求修复,例如此 issue。
禁用 pytest 附加选项[编辑 | 编辑源代码]
运行 pytest 时,通常应禁用额外插件。特别是代码检查(lint)和覆盖率(coverage)插件在打包场景中可能适得其反,因其行为变更可能导致测试失败。
推荐通过命令行覆盖配置选项(而非修改 pytest 配置文件)来禁用 addopts
等参数,以减少维护成本。要清空所有附加选项,可使用:
pytest -o addopts=""
修复 meson-python 的可复现性问题[编辑 | 编辑源代码]
使用 meson-python包 作为 PEP 517 构建后端时,其随机生成的构建目录路径会导致可复现性问题。可通过 -Cbuild-dir
参数硬编码构建目录来规避此问题:
python -m build --wheel --no-isolation -Cbuild-dir=build