Python for Formula Authors

此文件說明如何在 Homebrew 公式中成功使用 Python。

Homebrew 區分 Python 應用程式和 Python 函式庫。差別在於使用者通常不關心應用程式是用 Python 編寫的;使用者通常不會預期在安裝應用程式後可以使用 import foo。應用程式的範例包括 ansiblejrnl

Python 函式庫存在供其他 Python 模組匯入;它們通常是 Python 應用程式的相依性。它們通常在終端機中只是附帶有用。函式庫的範例包括 certifinumpy

繫結是函式庫的一種特殊情況,允許 Python 程式碼與以其他語言實作的函式庫或應用程式互動。範例是 libxml2 安裝的 Python 繫結。

Homebrew 很樂意接受以 Python 建置的應用程式,不論這些應用程式是否可從 PyPI 取得。Homebrew 通常不會接受可以用 pip install foo 正確安裝的函式庫。可能會為提供它們的套件安裝繫結,特別是如果無法透過 pip 取得等效功能時。類似地,具有大量原生程式碼且因此編譯時間很長的函式庫可能是好的候選對象。不過,如有疑問:請勿封裝函式庫。

應用程式應無條件地組合所有其 Python 語言相依性和函式庫,並應安裝任何未滿足的相依性;這些策略會在以下各節中深入探討。

應用程式

應用程式的 Python 宣告

需要 Python 3 的應用程式公式必須宣告對 "python@3.y" 的無條件相依性。這些應用程式必須與目前的 Homebrew Python 3.y 公式一起使用。

安裝應用程式

從 Python@3.12 開始,Homebrew 遵循 PEP 668。應用程式必須安裝到根植於 libexec 的 Python 虛擬環境 中。這可防止應用程式的 Python 模組污染系統 site-packages,反之亦然。

應用程式的 Python 模組相依性(及其相依性,遞迴)應宣告為公式中的 resource,並安裝到虛擬環境中。每個相依性都應明確指定;請勿依賴 setup.pypip 執行自動相依性解析,原因 在此說明

您可以使用 brew update-python-resources 來協助撰寫資源節。要使用它,只需執行 brew update-python-resources <formula>。有時,brew update-python-resources 無法自動更新資源。如果發生這種情況,請嘗試執行 brew update-python-resources --print-only <formula> 來列印資源節,而不是將變更直接套用於檔案。然後,您可以視需要複製並貼上資源。

如果使用 brew update-python-resources 無效,您可以使用 homebrew-pypi-poet 來協助撰寫資源節。要使用它,請設定虛擬環境並安裝您的套件及其所有相依性。然後,在同一個虛擬環境中 pip install homebrew-pypi-poet。執行 poet some_package 將產生必要的資源節。您可以這樣做

# Use a temporary directory for the virtual environment
cd "$(mktemp -d)"

# Create and source a new virtual environment in the venv/ directory
python3 -m venv venv
source venv/bin/activate

# Install the package of interest as well as homebrew-pypi-poet
pip install some_package homebrew-pypi-poet
poet some_package

# Destroy the virtual environment
deactivate
rm -rf venv

Homebrew 提供了用於建立和填入虛擬環境的輔助方法。您可以透過在 Formula 類別定義的頂端放置 include Language::Python::Virtualenv 來使用它們。

對於大多數應用程式,您只需要撰寫

class Foo < Formula
  include Language::Python::Virtualenv

  # ...
  url "https://example.com/foo-1.0.tar.gz"
  sha256 "abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1"

  depends_on "python@3.y"

  def install
    virtualenv_install_with_resources
  end
end

這與撰寫以下內容完全相同

class Foo < Formula
  include Language::Python::Virtualenv

  # ...
  url "https://example.com/foo-1.0.tar.gz"
  sha256 "abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1"

  depends_on "python@3.y"

  def install
    # Create a virtualenv in `libexec`.
    venv = virtualenv_create(libexec, "python3.y")
    # Install all of the resources declared on the formula into the virtualenv.
    venv.pip_install resources
    # `pip_install_and_link` takes a look at the virtualenv's bin directory
    # before and after installing its argument. New scripts will be symlinked
    # into `bin`. `pip_install_and_link buildpath` will install the package
    # that the formula points to, because buildpath is the location where the
    # formula's tarball was unpacked.
    venv.pip_install_and_link buildpath
  end
end

範例公式

安裝具有相依性的公式會如下所示

class Foo < Formula
  include Language::Python::Virtualenv

  desc "Description"
  homepage "https://example.com"
  url "..."

  resource "six" do
    url "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz"
    sha256 "1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"
  end

  resource "parsedatetime" do
    url "https://files.pythonhosted.org/packages/a8/20/cb587f6672dbe585d101f590c3871d16e7aec5a576a1694997a3777312ac/parsedatetime-2.6.tar.gz"
    sha256 "4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455"
  end

  def install
    virtualenv_install_with_resources
  end
end

您也可以使用更詳細的形式,並要求安裝特定資源

class Foo < Formula
  include Language::Python::Virtualenv

  desc "Description"
  homepage "https://example.com"
  url "..."

  def install
    venv = virtualenv_create(libexec)
    %w[six parsedatetime].each do |r|
      venv.pip_install resource(r)
    end
    venv.pip_install_and_link buildpath
  end
end

萬一您需要對不同的資源執行不同的操作。

繫結

若要新增 Python 3 繫結,請新增 depends_on "python@3.y" 以配合目前的 Homebrew Python 3.y 公式。

繫結相依性

繫結應遵循與 Python 模組相依性相同的建議,如同程式庫;請參閱下方以取得更多資訊。

安裝繫結

如果繫結是透過呼叫 setup.py 安裝,請執行類似下列動作

system "python3.y", "-m", "pip", "install", *std_pip_args(build_isolation: true), "./source/python"

Autotools

如果 configure 腳本採用 --with-python 旗標,通常不需要額外協助來尋找 Python。但是,如果相依樹中有數個 Python 公式,則可能需要協助尋找正確的 Python。

如果 configuremake 腳本不想要安裝到 Cellar,有時你可以

  1. 呼叫 ./configure --without-python(或類似名稱的選項)
  2. 在包含 Python 繫結的目錄中呼叫 pip(如上所述)

有時我們必須使用 Homebrew 的 inreplace 輔助方法,即時編輯 Makefile,以使用我們的 Python 繫結前綴。

CMake

如果 cmake 找到的 Python 與直接相依項不同,有時你可以透過使用 -D 選項設定下列變數之一,協助它找到正確的 Python

Meson

由於 Homebrew 的符號連結安裝和 Python sysconfig 修補程式的副作用,meson 可能無法自動偵測安裝 Python 繫結的 Cellar 目錄。如果公式的 meson 建置定義使用 install_sources() 或類似方法,你可以設定 python.purelibdir 和/或 python.platlibdir,以覆寫預設路徑。

如果 meson 找到的 Python 與直接相依項不同,而公式的 meson 選項定義檔案未提供使用者可設定的選項,則你需要檢查 Python 可執行檔是如何偵測的。一種常見的方法是 find_installation() 方法,其行為會根據 name_or_path 參數的設定而有所不同。

函式庫

請記住:函式庫的案例非常有限(例如,編譯了大量的原生程式碼),因此,如有疑問,請不要封裝它們。

我們不會對這類型的公式使用 python- 前綴!

homebrew-core 中允許的函式庫範例

函式庫的 Python 宣告

為 Python 3 建置的函式庫必須包含 depends_on "python@3.y",它會針對 Homebrew 的 Python 3.y 進行裝瓶。

安裝函式庫

函式庫可以安裝到 libexec 並透過撰寫 .pth 檔案(命名為「homebrew-foo.pth」)到 prefix site-packages 來新增到 sys.path。如果意外使用 pip 升級 Homebrew 安裝的套件,這會簡化後續的戲碼,並防止在 Homebrew 的 site-packages 中累積過期的 .pyc 檔案。

目前大多數配方僅安裝到 prefix。任何過期的 .pyc 檔案都會由 brew cleanup 處理。

函式庫的相依性

必須安裝函式庫的相依性,以便可以匯入它們。為了將連結衝突的可能性降至最低,相依性應安裝到 libexec/<vendor> 並透過撰寫第二個 .pth 檔案(命名為「homebrew-foo-dependencies.pth」)到 prefix site-packages 來新增到 sys.path

具有一般 Python 函式庫相依性的配方(例如 setuptoolssix)不應使用此方法,因為它會使用安裝在 libexec/<vendor> 內的所有函式庫來污染系統 site-packages

更深入探討

其他說明,解釋 Homebrew 執行某些動作的原因。

Setuptools 與 Distutils 與 pip

Distutils 是 Python 標準函式庫中的模組,在 Python 3.12 中移除之前,它為開發人員提供基本的套件管理 API。Setuptools 是在標準函式庫外發布的模組,用於延伸和取代 Distutils。慣例是 Python 套件提供 setup.py,它會呼叫 Distutils 或 Setuptools 的 setup() 函式。

Setuptools 過去提供 easy_install 指令,它是一種終端使用者套件管理工具,用於從 PyPI(Python 套件索引)擷取和安裝套件。在 Setuptools v52.0.0 中已移除 easy_install 主控台指令碼,而直接使用已在 v58.3.0 中棄用。 pip 是另一種較新的終端使用者套件管理工具,它也在標準函式庫外提供。雖然 pip 取代了 easy_install,但它並未取代 Setuptools 模組的其他功能。

Distutils 和 pip 使用「扁平」安裝層級結構,將模組安裝為 site-packages 下的個別檔案,而 easy_install 則將壓縮的 egg 安裝到 site-packages

Distribute(不要與 Distutils 混淆)是 Setuptools 的過時分支。Distlib 是標準函式庫外部維護的套件,pip 用於一些低階包裝作業,與大多數 setup.py 使用者無關。

執行 setup.py

當公式需要與 setup.py 互動,而不是呼叫 pip 時,Homebrew 會提供輔助方法 Language::Python.setup_install_args,它會傳回呼叫 setup.py 的有用引數。您的公式應該使用此方法,而不是明確呼叫 setup.py。語法為

system Formula["python@3.y"].opt_bin/"python3.y", *Language::Python.setup_install_args(prefix)

其中 prefix 是目標前綴(通常為 libexecprefix)。

--single-version-externally-managed 是什麼?

--single-version-externally-managed(「SVEM」)是 Setuptools 專屬的 setup.py install 引數。SVEM 的主要效果是使用 Distutils 執行安裝,而不是 Setuptools 的 easy_install

easy_install 會執行一些我們需要避免的事項

Setuptools 要求 SVEM 與 --record 搭配使用,它會提供稍後可用於解除安裝套件的檔案清單。我們不需要或不想要這個,因為 Homebrew 可以管理解除安裝,但由於 Setuptools 要求,我們會遵守。Homebrew 慣例是將記錄檔命名為「installed.txt」。

偵測 setup.py 是否使用 Setuptools 或 Distutils 的 setup() 很困難,但我們總是需要將此旗標傳遞給基於 Setuptools 的腳本。 pip 面臨與我們相同的問題,並強制 setup() 使用 Setuptools 版本,方法是在 setup.py 周圍載入一個 shim,在執行任何其他操作之前匯入 Setuptools。由於 Setuptools 會猴子修補 Distutils 並取代其 setup 函數,這提供了一個單一且一致的介面。我們已借用此程式碼並在 Language::Python.setup_install_args 中使用它。

--prefix--root

setup.py 接受令人困惑的安裝選項陣列。Homebrew 的正確開關是 --prefix,它會自動設定 --install-foo 選項系列,並使用合理的 POSIX-y 值。

--root 用於安裝到不會成為檔案最終安裝位置的字首,例如建置 RPM 或二進位發行版時。當使用基於 setup.py 的 Setuptools 時,--root 會有啟動 --single-version-externally-managed 的副作用。將 --root 與空的 --prefix 一起使用並不安全,因為在位元組編譯模組時會從路徑中移除 root

--prefix--root=/ 一起使用可能是安全的,這應該適用於基於 Setuptools 或 Distutils 的 setup.py,但有點醜陋。

pipsetup.py

PEP 453 建議下游發行商(我們)使用 pip 安裝 sdist tarball,而不是直接呼叫 setup.py。由於歷史原因,我們沒有遵循 PEP 453,因此有些配方仍使用 setup.py 安裝。現在,大多數核心配方都使用 pip,因為我們已將它們遷移到這種首選的安裝方法。

Fork me on GitHub