PEP 720 – 交叉编译 Python 包
- 作者:
- Filipe Laíns <lains at riseup.net>
- PEP 代理人:
- 状态:
- 草案
- 类型:
- 信息性
- 创建日期:
- 2023 年 7 月 1 日
- Python 版本:
- 3.12
摘要
此 PEP 试图记录下游项目交叉编译的现状。
它应该概述发行版(Linux 发行版、WASM 环境提供商等)目前用于交叉编译下游项目(第三方扩展等)的方法。
动机
我们编写此 PEP 以表达交叉编译中的挑战,并作为未来改进提案的支持文件。
分析
介绍
目前有几种不同的方法来解决这个问题,这些方法需要用户进行不同程度的交互,但都需要大量的努力。这是由于 Python 包生态系统中缺乏标准化的交叉编译基础设施,而这本身又源于交叉构建的复杂性,使其成为一项巨大的任务。
上游支持
一些主要项目(如 CPython、setuptools 等)提供了一些支持来帮助交叉编译,但这些支持是非官方的,并且基于尽力而为的原则。例如,sysconfig
模块允许通过 _PYTHON_SYSCONFIGDATA_NAME
环境变量覆盖数据模块名称,这是交叉构建所必需的,而 setuptools 接受补丁 [1] 以调整/修复其逻辑,使其与流行的 “环境模拟” 工作流程兼容 [2].
上游项目中缺乏第一方支持导致交叉编译变得脆弱,需要用户付出巨大的努力,但同时,缺乏标准化也使得上游项目更难改进支持,因为不清楚应该如何提供此功能。
具有良好交叉构建支持的项目
似乎值得指出的是,有一些现代的 Python 包构建后端至少具有不错的交叉编译支持,它们分别是 scikit-build 和 meson-python。这两个项目都将外部成熟的构建系统集成到 Python 包中——分别是 CMake 和 Meson——因此交叉构建支持是从它们那里继承的。
下游方法
交叉编译方法落入一个频谱中,该频谱从设计上需要大量的用户交互到(理想情况下)几乎不需要用户交互。通常,它们将基于两种主要策略之一,使用 交叉构建环境 或 模拟目标环境。
交叉构建环境
这包括正常运行 Python 解释器并利用项目构建系统提供的交叉构建。但是,正如我们上面所看到的,上游支持不足,因此这种方法仅适用于一小部分项目。当这种方法失败时,通常的策略是修补构建系统代码以使用正确的工具链、系统详细信息等 [3]。
由于这种方法通常需要针对特定包进行修补,因此需要大量用户交互。
示例
模拟目标环境
为了消除对用户输入的需求,一种流行的方法是尝试模拟目标环境。它通常包括对 Python 解释器进行猴子修补,使其模仿目标系统上的解释器,这包括更改许多 sys
模块属性、sysconfig
数据等。使用这种策略,构建后端不需要任何交叉构建支持,并且应该在没有任何代码更改的情况下正常工作。
不幸的是,虽然不可能真正模拟目标环境。有很多原因导致这种情况,其中一个主要的原因是它会破坏实际上需要自省运行时解释器的代码。因此,对 Python 进行猴子修补以使其看起来像目标非常棘手——为了实现最少的破坏,我们只能修补解释器的某些方面。因此,构建后端可能需要一些代码更改,但这些更改通常比以前的方法要小得多。这是该技术的固有局限性,这意味着这种策略仍然需要一些用户交互。
尽管如此,这种策略仍然可以与比上面方法更多项目开箱即用,并且在这些情况下需要更少的努力。它在减少用户交互所需的数量方面取得了成功,即使它没有成功实现通用性。
示例
环境自省
如上所述,大多数构建系统代码是在假设目标系统与构建发生的地方相同的情况下编写的,因此自省通常用于指导构建。
在本节中,我们试图记录完成此操作的大多数方法。它应该概述构建系统所需的有关环境的详细信息。
代码片段 | 描述 | 差异 |
---|---|---|
>>> importlib.machinery.EXTENSION_SUFFIXES
[
'.cpython-311-x86_64-linux-gnu.so',
'.abi3.so',
'.so',
]
|
此解释器支持的扩展(本机模块)后缀。 | 这是实现定义的,但它通常根据实现、系统架构、构建配置、Python 语言版本和实现版本(如果存在)而有所不同。 |
>>> importlib.machinery.SOURCE_SUFFIXES
['.py']
|
此解释器支持的源代码(纯 Python)后缀。 | 这是实现定义的,但它通常不会有所不同(除了奇特的实现或系统)。 |
>>> importlib.machinery.all_suffixes()
[
'.py',
'.pyc',
'.cpython-311-x86_64-linux-gnu.so',
'.abi3.so',
'.so',
]
|
此解释器支持的所有模块文件后缀。它应该所有 importlib.machinery.*_SUFFIXES 属性的并集。 |
这是实现定义的,但它通常根据实现、系统架构、构建配置、Python 语言版本和实现版本(如果存在)而有所不同。有关更多信息,请参见上面的条目。 |
>>> sys.abiflags
''
|
ABI 标志,如 PEP 3149 中所指定。 | 根据构建配置而有所不同。 |
>>> sys.api_version
1013
|
C API 版本。 | 根据 Python 安装而有所不同。 |
>>> sys.base_prefix
/usr
|
安装范围目录的前缀,其中安装了与平台无关的文件。 | 根据平台和安装而有所不同。 |
>>> sys.base_exec_prefix
/usr
|
安装范围目录的前缀,其中安装了与平台相关的文件。 | 根据平台和安装而有所不同。 |
>>> sys.byteorder
'little'
|
本机字节序。 | 根据平台而有所不同。 |
>>> sys.builtin_module_names
('_abc', '_ast', '_codecs', ...)
|
所有编译到 Python 解释器中的模块的名称。 | 根据平台、系统架构和构建配置而有所不同。 |
>>> sys.exec_prefix
/usr
|
站点特定目录的前缀,其中安装了与平台无关的文件。由于它涉及站点特定目录,因此在标准虚拟环境实现中,它将是虚拟环境特定的路径。 | 根据平台、安装和环境而有所不同。 |
>>> sys.executable
'/usr/bin/python'
|
正在使用的 Python 解释器的路径。 | 根据安装而有所不同。 |
>>> with open(sys.executable, 'rb') as f:
... header = f.read(4)
... if is_elf := (header == b'\x7fELF'):
... elf_class = int(f.read(1))
... size = {1: 52, 2: 64}.get(elf_class)
... elf_header = f.read(size - 5)
|
Python 解释器是否是 ELF 文件以及 ELF 头文件。这种方法用于识别安装的目标架构(示例)。 | 根据安装而有所不同。 |
>>> sys.float_info
sys.float_info(
max=1.7976931348623157e+308,
max_exp=1024,
max_10_exp=308,
min=2.2250738585072014e-308,
min_exp=-1021,
min_10_exp=-307,
dig=15,
mant_dig=53,
epsilon=2.220446049250313e-16,
radix=2,
rounds=1,
)
|
有关浮点类型的信息,如 float.h 中所定义。 |
根据架构和平台而有所不同。 |
>>> sys.getandroidapilevel()
21
|
表示 Android API 级别整数。 | 根据平台而有所不同。 |
>>> sys.getwindowsversion()
sys.getwindowsversion(
major=10,
minor=0,
build=19045,
platform=2,
service_pack='',
)
|
系统的 Windows 版本。 | 根据平台而有所不同。 |
>>> sys.hexversion
0x30b03f0
|
作为整数编码的 Python 版本。 | 根据 Python 语言版本而有所不同。 |
>>> sys.implementation
namespace(
name='cpython',
cache_tag='cpython-311',
version=sys.version_info(
major=3,
minor=11,
micro=3,
releaselevel='final',
serial=0,
),
hexversion=0x30b03f0,
_multiarch='x86_64-linux-gnu',
)
|
解释器实现详细信息。 | 根据解释器实现、Python 语言版本和实现版本(如果存在)而有所不同。它可能还包括架构相关信息,因此它也可能根据系统架构而有所不同。 |
>>> sys.int_info
sys.int_info(
bits_per_digit=30,
sizeof_digit=4,
default_max_str_digits=4300,
str_digits_check_threshold=640,
)
|
有关 Python 内部整数表示的低级信息。 | 根据架构、平台、实现、构建和运行时标志而有所不同。 |
>>> sys.maxsize
0x7fffffffffffffff
|
类型为 Py_ssize_t 的变量可以取的最大值。 |
根据架构、平台和实现而有所不同。 |
>>> sys.maxunicode
0x10ffff
|
最大 Unicode 代码点的值。 | 根据实现而有所不同,并且在 Python 版本 3.3 之前的版本中,根据构建而有所不同。 |
>>> sys.platform
linux
|
平台标识符。 | 根据平台而有所不同。 |
>>> sys.prefix
/usr
|
站点特定目录的前缀,其中安装了与平台相关的文件。由于它涉及站点特定目录,因此在标准虚拟环境实现中,它将是虚拟环境特定的路径。 | 根据平台、安装和环境而有所不同。 |
>>> sys.platlibdir
lib
|
特定于平台的库目录。 | 根据平台和供应商而有所不同。 |
>>> sys.version_info
sys.version_info(
major=3,
minor=11,
micro=3,
releaselevel='final',
serial=0,
)
|
解释器实现的 Python 语言版本。 | 如果目标 Python 版本与当前版本不同,则有所不同 [4]。 |
>>> sys.thread_info
sys.thread_info(
name='pthread',
lock='semaphore',
version='NPTL 2.37',
)
|
有关线程实现的信息。 | 根据平台和实现而有所不同。 |
>>> sys.winver
3.8-32
|
用于形成 Windows 注册表项的版本号。 | 根据平台和实现而有所不同。 |
>>> sysconfig.get_config_vars()
{ ... }
>>> sysconfig.get_config_var(...)
...
|
Python 分发配置变量。它包括一组变量 [5]——例如 prefix 、exec_prefix 等——它们基于运行时上下文 [6],并且可能包括一些根据 Python 实现和系统而有所不同的额外变量。在 CPython 和大多数使用相同构建系统的其他实现中,上面提到的“额外”变量是:在 POSIX 上,所有来自用于构建解释器的 |
这是实现定义的,但它 **通常** 在不同构建之间有所不同。请参阅 sysconfig 配置变量 表,以了解通常存在的不同配置变量的概述。 |
sys.prefix
和其他属性。Makefile
,而是 使用 Visual Studio 构建系统。提供最相关的 Makefile
变量的一个子集,以使使用它们的代码更简单。CPython(以及类似的)
名称 | 示例值 | 描述 | 差异 |
---|---|---|---|
SOABI |
cpython-311-x86_64-linux-gnu |
ABI 字符串——由 PEP 3149 定义。 | 根据实现、系统架构、Python 语言版本和实现版本(如果存在)而有所不同。 |
SHLIB_SUFFIX |
.so |
共享库后缀。 | 根据平台而有所不同。 |
EXT_SUFFIX |
.cpython-311-x86_64-linux-gnu.so |
解释器特定的 Python 扩展(原生模块)后缀——通常定义为 .{SOABI}.{SHLIB_SUFFIX} 。 |
根据实现、系统架构、Python 语言版本和实现版本(如果存在)而有所不同。 |
LDLIBRARY |
libpython3.11.so |
共享 libpython 库名称——如果可用。如果不可用 [8],则变量将为空,如果可用,则库应位于 LIBDIR 中。 |
根据实现、系统架构、构建配置、Python 语言版本和实现版本(如果存在)而有所不同。 |
PY3LIBRARY |
libpython3.so |
仅 Python 3(仅绑定主版本) [9] 共享 libpython 库名称——如果可用。如果不可用 [8],则变量将为空,如果可用,则库应位于 LIBDIR 中。 |
根据实现、系统架构、构建配置、Python 语言版本和实现版本(如果存在)而有所不同。 |
LIBRARY |
libpython3.11.a |
静态 libpython 库名称——如果可用。如果不可用 [8],则变量将为空,如果可用,则库应位于 LIBDIR 中。 |
根据实现、系统架构、构建配置、Python 语言版本和实现版本(如果存在)而有所不同。 |
Py_DEBUG |
0 |
是否为 调试构建。 | 根据构建配置而有所不同。 |
WITH_PYMALLOC |
1 |
此构建是否具有 pymalloc 支持。 | 根据构建配置而有所不同。 |
Py_TRACE_REFS |
0 |
是否启用了引用跟踪(仅调试构建)。 | 根据构建配置而有所不同。 |
Py_UNICODE_SIZE |
Py_UNICODE 对象的大小(以字节为单位)。此变量仅存在于早于 3.3 的 CPython 版本中,通常用于检测构建是否使用 UCS2 或 UCS4 来表示 Unicode 对象——在 PEP 393 之前。 |
根据构建配置而有所不同。 | |
Py_ENABLE_SHARED |
1 |
是否存在共享 libpython 。 |
根据构建配置而有所不同。 |
PY_ENABLE_SHARED |
1 |
是否存在共享 libpython 。 |
根据构建配置而有所不同。 |
CC |
gcc |
用于构建 Python 发行版的 C 编译器。 | 根据构建配置而有所不同。 |
CXX |
g++ |
用于构建 Python 发行版的 C 编译器。 | 根据构建配置而有所不同。 |
CFLAGS |
-DNDEBUG -g -fwrapv ... |
用于构建 Python 发行版的 C 编译器标志。 | 根据构建配置而有所不同。 |
py_version |
3.11.3 |
Python 版本的完整形式。 | 根据 Python 语言版本而有所不同。 |
py_version_short |
3.11 |
Python 版本的自定义形式,仅包含主版本号和次版本号。 | 根据 Python 语言版本而有所不同。 |
py_version_nodot |
311 |
Python 版本的自定义形式,仅包含主版本号和次版本号,没有点。 | 根据 Python 语言版本而有所不同。 |
prefix |
/usr |
与 sys.prefix 相同,请参阅上面表格中的条目。 |
根据平台、安装和环境而有所不同。 |
base |
/usr |
与 sys.prefix 相同,请参阅上面表格中的条目。 |
根据平台、安装和环境而有所不同。 |
exec_prefix |
/usr |
与 sys.exec_prefix 相同,请参阅上面表格中的条目。 |
根据平台、安装和环境而有所不同。 |
platbase |
/usr |
与 sys.exec_prefix 相同,请参阅上面表格中的条目。 |
根据平台、安装和环境而有所不同。 |
installed_base |
/usr |
与 sys.base_prefix 相同,请参阅上面表格中的条目。 |
根据平台和安装而有所不同。 |
installed_platbase |
/usr |
与 sys.base_exec_prefix 相同,请参阅上面表格中的条目。 |
根据平台和安装而有所不同。 |
platlibdir |
lib |
与 sys.platlibdir 相同,请参阅上面表格中的条目。 |
根据平台和供应商而有所不同。 |
SIZEOF_* |
4 |
特定 C 类型(double 、float 等)的大小。 |
根据系统架构和构建细节而有所不同。 |
libpython
支持的编译。libpython
库,稳定 ABI 的用户应该与其链接,如果他们需要与 libpython
链接。相关信息
构建系统需要一些信息——例如平台特性——这些信息分散在许多地方,而且经常难以识别基于这些信息的假设代码。在本节中,我们将尝试记录最相关的案例。
何时应将扩展链接到 libpython
?
- 简短回答
- 是的,在 Windows 上。在 POSIX 平台上,除 Android、Cygwin 和其他基于 Windows 的 POSIX 类平台外,其他平台上则不是。
在为动态加载构建扩展时,它们可能需要与 libpython
链接,具体取决于目标平台。
在 Windows 上,扩展需要与 libpython
链接,因为所有符号都必须在链接时可解析。基于 Windows 的 POSIX 类平台——例如 Cygwin、MinGW 或 MSYS——也将需要与 libpython
链接。
在大多数 POSIX 平台上,不需要与 libpython
链接,因为符号已经存在于解释器中——或者,在嵌入时,相关的可执行文件/库已经与 libpython
链接。不将扩展模块与 libpython
链接将允许它被静态 Python 构建加载,因此,如果可能,最好这样做(参见 GH-65735)。
这可能不适用于所有 POSIX 平台,因此请务必检查。一个例子是 Android,在那里只有主可执行文件和 LD_PRELOAD
条目被视为 RTLD_GLOBAL
(意味着依赖项是 RTLD_LOCAL
) [10],这会导致在加载扩展时无法使用 libpython
符号。
什么是 prefix
、exec_prefix
、base_prefix
和 base_exec_prefix
?
这些是 sys
属性 在 Python 初始化中设置,它们描述了运行环境。根据下表,它们指的是安装/环境文件安装的目录的前缀。
名称 | 目标文件 | 环境范围 |
---|---|---|
prefix |
平台无关(例如纯 Python) | 站点特定 |
exec_prefix |
平台依赖(例如原生代码) | 站点特定 |
base_prefix |
平台无关(例如纯 Python) | 安装范围 |
base_exec_prefix |
平台依赖(例如原生代码) | 安装范围 |
由于站点特定的前缀在虚拟环境中会不同,因此检查 sys.prexix != sys.base_prefix
通常用于检查我们是否在虚拟环境中。
案例研究
crossenv
- 描述:
- 用于交叉编译 Python 扩展模块的虚拟环境。
- URL:
- https://github.com/benfogle/crossenv
crossenv
是一种创建虚拟环境的工具,它使用一个猴子补丁 Python 安装程序,尝试在某些情况下模拟目标机器。有关此方法的更多信息,请参见 伪造目标环境 部分。
conda-forge
- 描述:
- 一个社区主导的 conda 包管理器配方、构建基础设施和发行版的集合。
- URL:
- https://forge.conda.org.cn/
XXX:Jaime 将在 PEP 草案公开后撰写一份简短的总结。
XXX 使用修改后的 crossenv。
Yocto 项目
- 描述:
- Yocto 项目是一个开源协作项目,它帮助开发人员创建自定义的基于 Linux 的系统,无论硬件架构如何。
- URL:
- https://www.yoctoproject.org/
XXX:已向邮件列表发送电子邮件。
TODO
Buildroot
- 描述:
- Buildroot 是一种简单、高效且易于使用的工具,可通过交叉编译生成嵌入式 Linux 系统。
- URL:
- https://buildroot.org/
TODO
Pyodide
- 描述:
- Pyodide 是一个基于 WebAssembly 的浏览器和 Node.js Python 发行版。
- URL:
- https://pyodide.org/en/stable/
XXX:Hood 应该审查/扩展本节。
Pyodide
提供了一个使用 Emscripten 工具链编译到 WebAssembly 的 Python 发行版。
它修补了 CPython 安装和一些外部组件的几个方面。一个自定义的包管理器——micropip——支持 Pure 和 wasm32/Emscripten 轮子,也作为发行版的一部分提供。除此之外,还提供了一个存储库,其中包含 一组精选的第三方包,并且默认情况下启用。
Beeware
- 描述:
- BeeWare 允许您使用 Python 编写应用程序并在多个平台上发布。
- URL:
- https://beeware.org/
TODO
python-for-android
- 描述:
- 将您的 Python 应用程序转换为 Android APK。
- URL:
- https://github.com/kivy/python-for-android
资源 https://github.com/Android-for-Python/Android-for-Python-Users
python-for-android
是一个用于将 Python 应用程序打包到 Android 上的工具。它会创建一个包含您的应用程序及其依赖项的 Python 发行版。
纯 Python 依赖项会以通用方式自动处理,但本地依赖项需要 配方。提供了一组针对 常用依赖项 的配方,但用户需要为任何其他本地依赖项提供自己的配方。
kivy-ios
- 描述:
- 用于编译 Python / Kivy / 其他库以供 iOS 使用的工具链。
- URL:
- https://github.com/kivy/kivy-ios
kivy-ios
是一个用于将 Python 应用程序打包到 iOS 上的工具。它提供了一个工具链来构建包含您的应用程序及其依赖项的 Python 发行版,以及一个 CLI 来创建和管理与工具链集成的 Xcode 项目。
它使用与 python-for-android 相同的方法(也由 Kivy 项目维护)来处理应用程序依赖项 - 纯 Python 依赖项会自动处理,但本地依赖项需要 配方,并且该项目为 常用依赖项 提供了配方。
AidLearning
- 描述:
- AI、Android、Linux、ARM:基于 Android+Linux 集成生态的 AI 应用开发平台。
- URL:
- https://github.com/aidlearning/AidLearning-FrameWork
TODO
QPython
- 描述:
- QPython 是 Android 的 Python 引擎。
- URL:
- https://github.com/qpython-android/qpython
TODO
pyqtdeploy
- 描述:
- pyqtdeploy 是一个用于部署 PyQt 应用程序的工具。
- URL:
- https://www.riverbankcomputing.com/software/pyqtdeploy/
联系 https://www.riverbankcomputing.com/pipermail/pyqt/2023-May/thread.html 联系了维护者 Phil
TODO
Chaquopy
- 描述:
- Chaquopy 提供了将 Python 组件包含到 Android 应用程序中所需的一切。
- URL:
- https://chaquo.com/chaquopy/
TODO
EDK II
- 描述:
- EDK II 是一个现代的、功能丰富的、跨平台的固件开发环境,适用于 UEFI 和 PI 规范。
- URL:
- https://github.com/tianocore/edk2-libc/tree/master/AppPkg/Applications/Python
TODO
ActivePython
- 描述:
- 面向企业级、质量保证的 Python 发行版,专注于在 Windows、Linux、Mac OS X、Solaris、HP-UX 和 AIX 上轻松安装和跨平台兼容性。
- URL:
- https://www.activestate.com/products/python/
TODO
Termux
- 描述:
- Termux 是一个 Android 终端模拟器和 Linux 环境应用程序,可以直接使用,无需 rooting 或设置。
- URL:
- https://termux.dev/en/
TODO
来源: https://github.com/python/peps/blob/main/peps/pep-0720.rst
最后修改时间: 2023-09-09 17:39:29 GMT