Python 打包准则 - Arch Linux 中文维基

跳转到内容

# Python 打包准则

来自 Arch Linux 中文维基
翻译状态：

- 本文（或部分内容）译自 Python package guidelines，最近一次同步于 2026-01-19，若英文版本有所更改，则您可以帮助同步与翻译更改的内容。
- 您可以在 ArchWiki 的对应页面找到本文翻译的原始修订历史。
- 本文可能与英文原文存在出入。

Arch 打包准则

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 – Shell – VCS – Web – Wine – 字体

本文档描述了使用 PKGBUILD 来为 Python 程序进行打包时须遵守的标准和指引。

## 包名

对于 Python 3 的库模块，或与 Python 生态紧密耦合的应用程序（如 pip 和 tox），使用 `python-模块名或应用程序名` 命名。对于其他应用程序，则仅使用程序原名。

注意：包名应全为小写。

## 架构

参见 PKGBUILD#arch。

包含 C 扩展的 Python 软件包与架构相关。反之则通常无关于架构。

使用 setuptools 构建的软件包通过在 `setup.py` 中定义 `ext_modules` 关键字来声明其使用的 C 扩展。

## 源代码

注意：根据 RFC0020，默认应使用上游提供的源代码压缩包，而非 PyPI 提供的 sdist 压缩包。

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）的安装方式

提示：当从上游提供的源代码 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`：

注意：除 python-build包 和 python-installer包 外，还需将软件包使用的构建后端（build backend）添加至 `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 及其他构建产物，避免后续问题。另请参阅 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 附加选项章节）。这些工具会增加引导难度且非发行版打包必需，因其不验证功能正确性
- 在为测试配置自定义 `PYTHONPATH` 时，避免使用相对路径，否则可能导致问题出现。请使用绝对路径。

大多数提供测试套件的 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

### 使用已安装的软件包运行测试

有些软件包需要安装后才能正常测试。在这种情况下（例如 python-narwhals包），可以将构建的软件包安装到虚拟环境下，并在该环境中运行测试：

    python -m venv --system-site-packages test-env
    test-env/bin/python -m installer dist/*.whl
    test-env/bin/python -P -m pytest

分类：​

- Arch 打包准则

Python 打包准则
添加话题