Python 打包准则
32 位 – CLR – CMake – Cross – DKMS – Eclipse – Electron – Font – 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-modulename
进行命名。如果软件包与 Python 某个生态系统(例如 pip 或 tox) 关系密切,请添加相应前缀。对于其他 Python 程序来说, 使用程序名就足够了。无论如何, 包的名字应该完全使用小写。
这同样适用于 Python 2,只需要修改前缀为 python2-
(如果必须)。
带版本的软件包[编辑 | 编辑源代码]
如果你需要添加一个带版本的软件包,请使用这种格式: python-模块名-版本
, 比如 python-colorama-0.2.5
。这样,Python 的依赖colorama==0.2.5
就会成为名为 python-colorama-0.2.5
的 Arch 软件包。
运行架构[编辑 | 编辑源代码]
参见 PKGBUILD#arch。
包含C扩展的Python包依赖于体系结构。否则,它很可能是与架构无关的。
使用setuptools构建的包使用 setup.py 中的 ext_modules 关键字定义其C扩展。
源代码[编辑 | 编辑源代码]
PyPI 中像是这种格式 https://pypi.python.org/packages/source/${_name:0:1}/${_name}/${_name}-${pkgver}.tar.gz
<footnote> 的 URL 已经在 2016 年静默失效了, 而新的格式需要一种只有从 PyPI 网站才能取得的不可预测的哈希码[1][失效链接 2021-05-17 ⓘ]。
在下游维护者向 PyPI 维护者抱怨了这个问题 [2][失效链接 2021-05-17 ⓘ] 之后, 一种新的稳定的格式出现了 [3][失效链接 2021-05-17 ⓘ]: PKGBUILD#source source=()
数值现在需要使用如下 URL 模板:
请注意,我们使用了自定义的 $_name
而不是 $pkgname
,因为 Python 包通常都命名成这样: python-$_name
。
- 源码包:
https://files.pythonhosted.org/packages/source/${_name::1}/$_name/$_name-$pkgver.tar.gz
- 双版本 wheel 包 (Python 2 和 Python 3 都兼容)
https://files.pythonhosted.org/packages/py2.py3/${_name::1}/$_name/${_name//-/_}-$pkgver-py2.py3-none-any.whl
(Python 2 and Python 3 都兼容)
- python3 wheel 包 (仅兼容Python 3)
https://files.pythonhosted.org/packages/py3/${_name::1}/$_name/${_name//-/_}-$pkgver-py3-none-any.whl
(仅兼容Python 3)
请注意,发行版名称可以包含破折号,而其在wheel文件名中的表示形式则不能(它们被转换为下划线)。
- 特定架构的 wheel 包
- 对于特定架构的 wheel 包,可以使用
source_x86_64=('...')
来表示 x86_64 架构的下载地址。与此同时可以使用_py=py36
来避免重复书写 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 或者 easy_install[失效链接 2021-05-17 ⓘ], 这些工具就像是能从在线源 (通常是 PyPI, Python 包索引 (Python Package Index) ) 获取源文件的软件包管理器一样,而且还能跟踪相关的文件状态。 (如需这两个工具的详细比较,请参见 pip vs easy_install).
然而,为了从 PKGBUILD
中管理Python包,需要将Python包“安装”到临时位置
$pkgdir/usr/lib/python<Python version>/site-packages/$pkgname
对于使用standard metadata在 pyproject.toml
中指定其构建后端的Python包,这可以使用python-build包和python-installer包最容易地实现。旧的包可能无法指定它们使用setuptools,并且只提供必须手动调用的 setup.py
。
基于标准(PEP 517)的安装方式[编辑 | 编辑源代码]
当从上游提供的源tarball构建时,上游依赖于git为项目派生版本字符串,在构建wheel之前,需要将工具特定的环境变量设置为 $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 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
数组中是不鼓励的,因为跳过build
不有利于从源代码构建,并且只应该在从源码构建不是可行的选择时使用(例如,仅带有wheel源代码的包,因此不能从源代码构建)。python-…-git
),请在您的 prepare
函数中包含命令 git -C "${srcdir}/${pkgname}" clean -dfx
。这将沿着其他构建工件一起删除旧的.whl
文件,并有助于防止将来出现问题。另见setuptools和Poetry的上游问题。distutils 或 setuptools[编辑 | 编辑源代码]
如果找不到 pyproject.toml
或者它没有包含 [build-system]
表,这意味着项目使用的是旧的遗留格式,它使用了调用setuptools或distutils的 setup.py文件。请注意,虽然 distutils 包含在Python的standardlib中,但安装setuptools 意味着您使用的是distutils 的补丁版本。
makedepends=('python-setuptools') # unless it only requires distutils 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()
函数中运行的构建步骤的不必要尝试(如果是这种情况)
如果setuptools未安装,有些软件包会尝试使用setuptools失败,然后使用distutils进行安装。在这种情况下,setuptools应该作为 makedepends
添加,这样得到的Python元数据会更好。
如果一个包需要 setuptools 来构建,因为它包含了可执行文件(distutils不支持),但只导入了distutils,那么构建时会发出如下警告,表示生成的包会被破坏(它将不包含可执行文件):
/usr/lib/python3.8/distutils/dist.py:274: UserWarning: Unknown distribution option: 'entry_points' warnings.warn(msg)
应报告上游错误。要解决这个问题,可以使用未记录的setuptools功能:
# 使用 distutils 失败 python setup.py build # 使用 setuptools 有效 python -m setuptools.launch setup.py 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
来运行测试套件,因为它被明确设计为在 tox
运行时测试从PyPI下载的可重复配置,并且不测试包将安装的版本。这完全违背了具有检查功能的目的。大多数提供测试套件的Python项目都使用nosetests或pytest (分别由python-nose包 和 python-pytest包提供)来运行测试,并在包含测试套件的文件或目录的名称中使用 test
。一般来说,简单地运行 nosetests
或者 pytest
就足以运行测试套件。
check(){ cd $_name-$pkgver # 使用 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])))') # nosetests PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-$python_version" nosetests # pytest PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-$python_version" pytest }
有些项目提供了 setup.py
入口点来运行测试。这对 pytest
和 nosetests
都有效。
check(){ cd $_name-$pkgver # nosetests python setup.py nosetests # pytest - 需要安装 python-pytest-runner python setup.py pytest }
提示和技巧[编辑 | 编辑源代码]
在PyPI上发现分离的PGP签名[编辑 | 编辑源代码]
如果给定Python sdist tarball的分离PGP签名存在,则应使用它们来验证tarball。但是,签名文件不会直接显示在pypi.org上任何给定项目的文件下载部分。要发现sdist tarball及其潜在的签名文件,可以使用此服务来获得每个项目的概述: https://pypi.debian.net/
对于python-requests包, 请见 https://pypi.debian.net/requests.
使用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包。如果你遇到这种情况,你可以通过向软件包项目提交一个问题来帮助他们解决这个问题,例如像这样。