跳至內容

Python 打包準則

出自 Arch Linux 中文维基
Arch 打包準則

32 位CLRCMakeDKMSEclipseElectronFree PascalGNOMEGoHaskellJava交叉編譯工具KDELispMesonMinGW內核模塊Node.jsNonfreeOCamlPerlPHPPythonRRubyRustVCSWebWine字體

本文檔描述了如何基於標準 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-buildpython-installer 是完成上述「安裝」過程最容易的方式。 舊版本軟件包可能未聲明使用 setuptools,僅提供需手動調用的 setup.py

注意:必須將元數據中的依賴關係聲明在 depends 數組中,否則相關依賴不會被安裝。

基於標準(PEP 517)的安裝方式[編輯 | 編輯原始碼]

提示:當從上游提供的原始碼 tarball 構建時,若項目依賴 git 生成版本字符串,在構建 wheel 前需根據使用的工具將特定的環境變量設為 $pkgver

基於標準的工作流程很簡單:使用 python-build 構建一個 wheel,再使用 python-installer 將其安裝到 $pkgdir

注意:python-buildpython-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 及其他構建產物,避免後續問題。另請參閱 setuptoolsPoetry 的上游 issue。

使用 setuptools 或 distutils 的安裝方式[編輯 | 編輯原始碼]

若項目中不存在 pyproject.toml 文件,或該文件未包含 [build-system] 表,則說明項目使用遺留格式,即通過 setup.py 文件調用 setuptools 或 distutils.core 的 setup 函數。

此類軟件包通常仍可使用上述 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 追蹤這些文件並保證權限一致
  • --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 附加選項章節)。這些工具會增加引導難度且非發行版打包必需,因其不驗證功能正確性

大多數提供測試套件的 Python 項目使用 unittest 運行器、nosetests 或 pytest(分別由 pythonpython-nosepython-pytest 提供),通過檢測特定格式的文件/目錄名和類/函數名(如含 test 的命名)自動發現測試套件。通常直接運行 nosetestspytest 即可執行測試套件。

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.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 包包含。若發現此類問題,請向項目提交 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