Python 包准则

来自 ArchWiki
Arch 软件包准则

32 位CLRCMake交叉编译DKMSEclipseElectron字体Free PascalGNOMEGoHaskellJavaKDE内核模块LispMesonMinGWNode.js非自由软件OCamlPerlPHPPythonRRubyRust - 安全ShellVCSWebWine

本文档涵盖了为 Python 软件编写 PKGBUILDs 的标准和准则。

包命名

对于 Python 3 库模块,请使用 python-模块名。如果软件包提供的程序与 Python 生态系统紧密耦合(例如 piptox),也请使用此前缀。对于其他应用程序,仅使用程序名称。

注意: 软件包名称应全部为小写。

架构

参见 PKGBUILD#arch

包含 C 扩展的 Python 软件包是架构相关的。否则,它很可能是架构无关的。

使用 setuptools 构建的软件包使用 setup.py 中的 ext_modules 关键字定义其 C 扩展。

来源

注意: 根据 RFC0020,默认使用上游提供的源代码 tarball,而不是 PyPI 提供的 sdist tarball。

从 PyPI 网站链接下载 URL 包含不可预测的哈希值,每次必须更新软件包时都需要从 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)
请注意,发行版名称可以包含破折号,但其在 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 软件包通常使用特定于语言的软件包管理器(如 pip)安装,pip 从在线仓库(通常是 PyPI,Python 软件包索引)获取软件包并跟踪相关文件。

但是,为了从 PKGBUILD 中管理 Python 软件包,需要将 Python 软件包“安装”到临时位置 $pkgdir/usr/lib/python<Python 版本>/site-packages/$pkgname

对于使用 标准元数据pyproject.toml 中指定其构建后端的 Python 软件包,这可以使用 python-buildpython-installer 最容易实现。旧软件包可能无法指定它们使用 setuptools,而只提供一个必须手动调用的 setup.py

注意: 来自软件包元数据的依赖项必须在 depends 数组中定义,否则它们将不会被安装。

基于标准 (PEP 517)

提示: 当从上游提供的源代码 tarball 构建,并且上游依赖 git 来为项目派生版本字符串时,需要在构建 wheel 之前设置特定于工具的环境变量为 $pkgver

基于标准的工作流程很简单:使用 python-build 构建 wheel,并使用 python-installer 将其安装到 $pkgdir

注意: 除了 python-buildpython-installer 之外,你还需要将软件包使用的 构建后端 添加到 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 源代码,因此无法从源代码构建)。
提示: 如果你的软件包是 VCS 软件包 (python-…-git),请在你的 prepare 函数中包含命令 git -C "${srcdir}/${pkgname}" clean -dfx。这会删除过时的 wheel 以及其他构建工件,并有助于防止后续出现问题。另请参阅 setuptoolsPoetry 的上游问题。

setuptools 或 distutils

如果找不到 pyproject.toml 或它未能包含 [build-system] 表,则意味着该项目正在使用旧的遗留格式,其中项目提供一个 setup.py 文件,该文件调用来自 setuptoolsdistutils.coresetup 函数。

此类软件包通常也可以使用上述方法使用 python-buildpython-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 跟踪,而不是在主机系统上按需创建。
  • 如果软件包使用了 python-setuptools-scm,则软件包很可能无法构建,并出现如下错误

添加 --skip-build 可以优化掉不必要的尝试,以重新运行已在 build() 函数中运行的构建步骤(如果情况如此)。

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 下载的可重复配置,而 tox 正在运行,并且测试软件包将安装的版本。这完全失去了设置 check 函数的目的。
  • 避免将用于 linting、覆盖率或类型检查的 pytest 插件添加到 checkdepends(有关详细信息,请参阅 #禁用 pytest 选项 部分)。这使得引导启动更加困难,并且这些测试不是发行版打包所必需的,因为它们不测试功能。

大多数提供测试套件的 Python 项目都使用 unittest runner 或 nosetests 或 pytest(分别由 pythonpython-nosepython-pytest 提供)来运行测试,文件名或目录名中包含 test。通常,只需运行 nosetestspytest 就足以运行测试套件。

check(){
    cd $_name-$pkgver
    
    # Builtin unittest
    python -m unittest discover -vs .

    # For nosetests
    nosetests

    # For pytest
    pytest
}

如果存在编译后的 C 扩展,则需要使用 $PYTHONPATH 运行测试,这反映了当前 Python 的主要版本和次要版本,以便查找并加载它。

check(){
    cd $_name-$pkgver
    local python_version=$(python -c 'import sys; print("".join(map(str, sys.version_info[:2])))')
    
    # Builtin unittest
    PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-$python_version" python -m unittest discover -vs .
    
    # For nosetests
    PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-$python_version" nosetests

    # For pytest
    PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-$python_version" pytest
}

提示与技巧

使用 Python 版本

有时在准备、构建、测试或安装期间,需要引用系统的主版本和次版本 Python 版本(例如 3.93.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 中的测试目录

确保不要将名为 tests/ 的目录直接安装在 site-packages/ 下(即 /usr/lib/pythonX.Y/site-packages/tests/)。这样做可能会导致软件包之间发生冲突。使用 setuptools 的 Python 软件包项目有时配置错误,会将包含其测试的目录作为顶级 Python 软件包包含在内。如果遇到这种情况,你可以通过向软件包项目提交 issue 并要求他们修复此问题来提供帮助,例如 像这样

禁用 pytest 选项

运行 pytest 时,通常不希望使用额外的插件运行。特别是用于 linting 和覆盖率的插件在打包中会适得其反,因为行为的更改可能会导致测试失败。为了禁用 pytest 选项(例如 addopts),首选在命令行上使用选项覆盖,而不是修补 pytest 使用的任何配置文件,因为维护开销。

要取消设置所有其他选项,请使用

pytest -o addopts=""

修复 meson-python 的可重现性问题

当使用 meson-python 作为 PEP 517 构建后端时,它使用随机化的文件夹路径 导致可重现性问题。这可以通过使用 -Cbuild-dir 标志硬编码使用的文件夹来规避

python -m build --wheel --no-isolation -Cbuild-dir=build