本文档解释如何在 Homebrew 配方中成功使用 Python。
Homebrew 区分 Python 应用程序和 Python 库。区别在于,用户通常不关心应用程序是用 Python 编写的;用户期望在安装应用程序后能够 import foo
的情况并不常见。应用程序的示例包括 ansible
和 jrnl
。
Python 库的存在是为了供其他 Python 模块导入;它们通常是 Python 应用程序的依赖项。它们通常在终端中用处不大。库的示例包括 certifi
和 numpy
。
绑定是库的一种特殊情况,它允许 Python 代码与用另一种语言实现的库或应用程序进行交互。一个示例是 libxml2
安装的 Python 绑定。
Homebrew 乐于接受用 Python 构建的应用程序,无论这些应用程序是否可从 PyPI 获得。Homebrew 通常不会接受可以用 pip install foo
正确安装的库。可以为提供它们的软件包安装绑定,特别是如果 pip 无法提供同等功能时。同样,具有大量本机代码且因此编译时间长的库也可能是不错的候选者。但如果有疑问:不要打包库。
应用程序应无条件地捆绑所有其 Python 语言依赖项和库,并应安装任何未满足的依赖项;以下部分将深入讨论这些策略。
需要 Python 3 的应用程序的配方必须声明对 "[email protected]"
的无条件依赖。这些应用程序必须与当前的 Homebrew Python 3.y 配方一起使用。
从 [email protected] 开始,Homebrew 遵循 PEP 668。应用程序必须安装到根目录为 libexec
的 Python 虚拟环境 中。这可防止应用程序的 Python 模块污染系统 site-packages
,反之亦然。
应用程序的所有 Python 模块依赖项(及其依赖项,递归)都应在公式中声明为 resource
,并安装到虚拟环境中。每个依赖项都应明确指定;请不要依赖 setup.py
或 pip
执行自动依赖项解析,原因 在此处进行了说明。
你可以使用 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 "[email protected]"
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 "[email protected]"
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 "[email protected]"
以配合当前 Homebrew Python 3.y 公式。
绑定应遵循与库相同的 Python 模块依赖项建议;请参阅以下内容以了解更多信息。
如果通过调用 setup.py
安装绑定,请执行类似以下操作
system "python3.y", "-m", "pip", "install", *std_pip_args(build_isolation: true), "./source/python"
如果配置脚本采用 --with-python
标志,它通常不需要额外的帮助来查找 Python。但是,如果依赖项树中有多个 Python 公式,则可能需要帮助来查找正确的公式。
如果 configure
和 make
脚本不想安装到 Cellar,有时您可以
./configure --without-python
(或类似的命名选项)pip
(如上所述)有时,我们必须使用 Homebrew 的 inreplace
帮助器方法即时编辑 Makefile
,以使用我们的前缀来进行 Python 绑定。
如果 cmake
找到的 Python 与直接依赖项不同,有时您可以通过使用 -D
选项设置以下变量之一来帮助它找到正确的 Python
Python3_EXECUTABLE
适用于 FindPython3
模块Python_EXECUTABLE
适用于 FindPython
模块PYTHON_EXECUTABLE
适用于 FindPythonInterp
模块作为 Homebrew 符号链接安装和 Python sysconfig 补丁的副作用,meson
可能无法自动检测到将 Python 绑定安装到的 Cellar 目录。如果公式的 meson
构建定义使用 install_sources()
或类似方法,您可以设置 python.purelibdir
和/或 python.platlibdir
以覆盖默认路径。
如果 meson
找到的 Python 与直接依赖项不同,并且公式的 meson
选项定义文件未提供用户可设置的选项,那么您需要检查如何检测 Python 可执行文件。一种常见的方法是 find_installation()
方法,该方法将根据 name_or_path
参数的设置而表现不同。
记住:库的使用场景非常有限(例如,编译了大量原生代码),因此,如有疑问,请不要打包它们。
我们不用 python-
前缀来表示这类公式!
numpy
、scipy
:构建时间长,构建过程复杂
cryptography
:用 rust
构建
certifi
:已修补的公式,允许任何基于 Python 的公式利用酿造的 CA 证书(请参阅 https://github.com/orgs/Homebrew/discussions/4691)。
为 Python 3 构建的库必须包含 depends_on "[email protected]"
,它将针对 Homebrew 的 Python 3.y 进行装瓶。
可以通过编写 .pth
文件(命名为“homebrew-foo.pth”)到 prefix
site-packages,将库安装到 libexec
并将其添加到 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 库依赖项(例如 setuptools
、six
)的公式不应使用此方法,因为它会使用安装在 libexec/<vendor>
中的所有库污染系统 site-packages
。
额外的评论,解释了 Homebrew 为什么执行某些操作。
Distutils 是 Python 标准库中的一个模块,在 Python 3.12 中删除之前,它为开发者提供了一个基本的包管理 API。Setuptools 是一个在标准库外部分发的模块,它扩展并取代了 Distutils。根据惯例,Python 包会提供一个 setup.py
,它调用 Distutils 或 Setuptools 中的 setup()
函数。
Setuptools 过去提供 easy_install
命令,这是一个终端用户包管理工具,用于从 PyPI(Python 包索引)获取并安装包。easy_install
控制台脚本已在 Setuptools v52.0.0 中删除,自 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["[email protected]"].opt_bin/"python3.y", *Language::Python.setup_install_args(prefix)
其中 prefix
是目标前缀(通常为 libexec
或 prefix
)。
--single-version-externally-managed
?--single-version-externally-managed
(“SVEM”)是 Setuptools 专用于 setup.py install
的参数。SVEM 的主要作用是使用 Distutils 执行安装,而不是 Setuptools 的 easy_install
。
easy_install
做了一些我们需要避免的事情
sys.path
中的依赖项.pth
和 site.py
文件,这些文件对我们没有用,并且会导致链接冲突Setuptools 要求 SVEM 与 --record
结合使用,后者提供一个文件列表,可供之后卸载软件包时使用。我们不需要或不想要这个,因为 Homebrew 可以管理卸载,但由于 Setuptools 要求,我们遵守。Homebrew 惯例是将记录文件命名为“installed.txt”。
检测 setup.py
是否使用 Setuptools 或 Distutils 的 setup()
很困难,但我们始终需要将此标志传递给基于 Setuptools 的脚本。 pip
面临与我们相同的问题,并强制 setup()
使用 Setuptools 版本,方法是在 setup.py
周围加载一个垫片,在执行任何其他操作之前导入 Setuptools。由于 Setuptools 对 Distutils 进行猴子补丁并替换其 setup
函数,因此这提供了一个单一的、一致的界面。我们借用了此代码,并在 Language::Python.setup_install_args
中使用它。
--prefix
与 --root
setup.py
接受一系列令人困惑的安装选项。Homebrew 的正确开关是 --prefix
,它会自动使用合理的 POSIX-y 值设置 --install-foo
系列选项。
当安装到不会成为文件最终安装位置的前缀中时(例如,在构建 RPM 或二进制发行版时),使用 --root
。在使用基于 Setuptools 的 setup.py
时,--root
具有激活 --single-version-externally-managed
的副作用。将 --root
与空 --prefix
一起使用是不安全的,因为在字节编译模块时,root
会从路径中删除。
将 --prefix
与 --root=/
一起使用可能是安全的,它应该适用于基于 Setuptools 或 Distutils 的 setup.py
,但这有点丑陋。
pip
与 setup.py
PEP 453 向下游分销商(我们)提出建议,即应使用 pip
安装 sdist tarball,而不是直接调用 setup.py
。出于历史原因,我们没有遵循 PEP 453,因此一些公式仍然使用 setup.py
安装。如今,大多数核心公式都使用 pip
,因为我们已将它们迁移到这种首选安装方法。