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 打包準則
新增話題