Python 打包准则

来自 Arch Linux 中文维基
(重定向自Python package guidelines

本文或本节需要翻译。要贡献翻译,请访问简体中文翻译团队

附注: Many changes have happened on the English page since last translation.(在 Talk:Python 打包准则# 中讨论)
Arch 打包准则

32 位CLRCMakeCrossDKMSEclipseElectronFontFree PascalGNOMEGoHaskellJavaKDE内核模块LispMesonMinGWNode.jsNonfreeOCamlPerlPHPPythonRRubyRustVCSWebWine

本文档描述了如何基于标准 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-}
注意: RFC0020默认使用上游提供的源代码压缩包,而不是PyPI提供的sdist压缩包。

安装方式[编辑 | 编辑源代码]

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 metadatapyproject.toml 中指定其构建后端的Python包,这可以使用python-buildpython-installer最容易地实现。旧的包可能无法指定它们使用setuptools,并且只提供必须手动调用的 setup.py

基于标准(PEP 517)的安装方式[编辑 | 编辑源代码]

提示:

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

基于标准的工作流程很简单:使用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源代码的包,因此不能从源代码构建)。
警告: 如果您的软件包是一个VCS packagepython-…-git),请在您的 prepare 函数中包含命令 git -C "${srcdir}/${pkgname}" clean -dfx。这将沿着其他构建工件一起删除旧的.whl文件,并有助于防止将来出现问题。另见setuptoolsPoetry的上游问题。

distutils 或 setuptools[编辑 | 编辑源代码]

如果找不到 pyproject.toml 或者它没有包含 [build-system] 表,这意味着项目使用的是旧的遗留格式,它使用了调用setuptoolsdistutilssetup.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-nosepython-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 入口点来运行测试。这对 pytestnosetests都有效。

check(){
    cd $_name-$pkgver

    # nosetests
    python setup.py nosetests

    # pytest - 需要安装 python-pytest-runner
    python setup.py pytest
}

提示和技巧[编辑 | 编辑源代码]

在PyPI上发现分离的PGP签名[编辑 | 编辑源代码]

警告:2023-05-23 PyPI I删除了提供分离的 OpenPGP 签名的功能

如果给定Python sdist tarball的分离PGP签名存在,则应使用它们来验证tarball。但是,签名文件不会直接显示在pypi.org上任何给定项目的文件下载部分。要发现sdist tarball及其潜在的签名文件,可以使用此服务来获得每个项目的概述: https://pypi.debian.net/

对于python-requests, 请见 https://pypi.debian.net/requests.

使用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中的测试目录[编辑 | 编辑源代码]

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