PEP 720 – 交叉编译 Python 包

猫勺猫勺 03-19 990 阅读

抽象

PEP 尝试记录下游交叉编译的状态 项目。

它应该概述分销商目前使用的方法 (Linux 发行版、WASM 环境提供商等)交叉编译下游 项目(第三方扩展等)。

赋予动机

我们编写此 PEP 来表达交叉编译中的挑战,并充当 未来改进建议中的支持文件。

分析

介绍

有几种不同的方法来解决这个问题,包括 用户需要不同级别的交互,但它们都需要 大量的努力。这是由于缺乏标准化 Python 打包生态系统上的交叉编译基础设施,它本身 源于交叉构建的复杂性,使其成为一项艰巨的任务。

    

上游支持

一些主要项目,如 CPython、setuptools 等,提供了一些支持来提供帮助 通过交叉编译,但它是非官方的,并且要尽最大努力。为 例如,该模块允许通过以下方式覆盖数据模块名称 环境变量,即 交叉构建是必需的,并且 SetupTools 接受补丁进行调整/修复 其逻辑与流行的“环境伪造”工作流程兼容 。sysconfig_PYTHON_SYSCONFIGDATA_NAME

上游项目缺乏第一方支持导致交叉编译 是脆弱的,需要用户付出巨大的努力,但同时 时间,缺乏标准化使上游更难改进 支持,因为不清楚应如何提供此功能。

在撰写本文时(2023 年 6 月),setuptools 的编译器接口代码, 最影响交叉编译的组件是在 pypa/distutils 存储库上开发的,该存储库会定期同步到 setuptools 存储库。

我们特别提到了流行的工作流程,因为这不是 标准化。虽然,许多最流行的实现 (CrossEnv、Conda-forge 的构建系统等)工作类似,这个 就是我们在这里所指的。为清楚起见,我们的实现 这里引用可以描述为 crossenv 风格。

具有良好跨构建支持的项目

指出有一些现代 Python 包似乎是相关的 构建后端,至少具有不错的交叉编译支持,即 scikit-build 和 meson-python。这两个项目都集成了外部成熟 将系统构建到 Python 打包中——分别是 CMake 和 Meson——所以 跨构建支持继承自它们。

下游方法

交叉编译方法属于一个范围,从设计上, 需要广泛的用户交互(理想情况下)几乎没有。通常,他们会 基于以下两种主要策略之一,使用跨构建环境, 或伪造目标环境。

跨构建环境

这包括正常运行 Python 解释器并利用 由项目的构建系统提供的交叉构建。然而,正如我们上面看到的, 缺乏上游支持,因此此方法仅适用于一小部分 项目。当这失败时,通常的策略是修补构建系统代码 要构建,请使用正确的工具链、系统详细信息等 。

由于这种方法通常需要特定于软件包的修补,因此需要很多 的用户交互。

例子
python-for-android、kivy-ios 等。

构建系统修补的范围因用户而异,通常 取决于他们的目标 - 一些(例如。Linux 发行版)可能会修补 build-system 来支持交叉构建,而其他人可能会进行硬编码 编译器路径和系统信息,只需将 构建工作。

伪造目标环境

为了降低对用户输入的要求,一种流行的方法试图 伪造目标环境。它通常包括对 Python 进行 monkeypatching interpreter 让它模仿目标系统上的解释器,这 包括更改许多模块属性、数据等。使用此策略,构建后端不需要有任何 跨构建支持,并且应该在不进行任何代码更改的情况下工作。syssysconfig

但不幸的是,不可能真正伪造目标环境。 造成这种情况的原因有很多,其中一个主要原因是它破坏了代码 这实际上需要反省正在运行的解释器。因此, 猴子修补 Python 以看起来像目标非常棘手 - 实现更少的 破损量,我们只能修补解释器的某些方面。 因此,构建后端可能需要一些代码更改,但这些更改通常是 比以前的方法小得多。这是 技术,这意味着此策略仍然需要一些用户交互。

尽管如此,这种策略仍然开箱即用,而且要多得多 项目,并且在这些情况下需要的工作量要少得多。 它成功地减少了所需的用户交互量,甚至 尽管它没有成功成为通用的。

例子
CrossEnv、Conda-Forge 等。

环境内省

如上所述,大多数构建系统代码都是在以下假设下编写的 目标系统与生成发生的位置相同,因此需要自省 通常用于指导构建。

在本节中,我们将尝试记录实现此目的的大多数方法。它 应该对所需的环境细节有一个体面的概述 构建系统。

片段描述方差
>>> 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_version1013
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.hexversion0x30b03f0
编码为整数的 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 语言而异 version 和实现版本 — 如果存在。它还可能包括 依赖于体系结构的信息,因此它也可能因 系统架构。
>>> sys.int_infosys.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 码位的值。因实现和早于 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 版本不同,则不同
>>> 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 分布配置变量。它包括一组 变量 — 如 、 等 — 基于 运行上下文 ,并且可能包含一些基于 Python 实现和系统。prefixexec_prefix

在 CPython 和大多数其他使用相同的实现中 build-system,上面提到的“额外”变量是:在 POSIX 上,所有 用于构建解释器的变量,以及 Windows,它通常只包含那些  的一小部分—— 比如 、 等。MakefileEXT_SUFFIXBINDIR

这是实现定义的,但通常因 不相同的构建。请参阅 sysconfig 配置变量表,了解不同的 通常存在的配置变量。

理想情况下,您希望使用相同的 Python 版本执行交叉构建 然而,实施情况往往并非如此。它不应该 只要主要版本和次要版本没有,就会非常有问题 改变。

始终存在的配置变量集主要包括 计算安装方案路径所需的变量。

我们在这里引用的上下文由“路径初始化”组成,即 在解释器启动过程中发生的进程,负责 弄清楚它正在运行哪个环境 - 例如。全球环境, 虚拟环境等 — 以及设置和其他 相应的属性。sys.prefix

这是因为 Windows 生成可能不使用 ,而是使用 Visual Studio 生成系统。提供了最相关变量的子集,以生成使用它们的用户代码 简单。MakefileMakefile

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 而异 语言版本和实现版本 — 如果存在。
LDLIBRARYlibpython3.11.so共享库名称 — 如果可用。如果不可用 , 变量将为空,如果可用,则应找到库 在。libpythonLIBDIR因实现、系统架构、构建而异 配置、Python 语言版本和实现版本 — 如果 一个存在。
PY3LIBRARYlibpython3.so仅共享 Python 3(仅绑定主要版本) 库名称 — 如果可用。如果不可用 ,则变量将是 空,如果可用,则库应位于 中。libpythonLIBDIR因实现、系统架构、构建而异 配置、Python 语言版本和实现版本 — 如果 一个存在。
LIBRARYlibpython3.11.a静态库名称 — 如果可用。如果不可用 , 变量将为空,如果可用,则应找到库 在。libpythonLIBDIR因实现、系统架构、构建而异 配置、Python 语言版本和实现版本 — 如果 一个存在。
Py_DEBUG0这是否是调试版本。因生成配置而异。
WITH_PYMALLOC1此版本是否支持 pymalloc。因生成配置而异。
Py_TRACE_REFS0是否启用引用跟踪(仅限调试版本)。因生成配置而异。


名字示例值描述方差
Py_UNICODE_SIZE
对象的大小(以字节为单位)。此变量仅 存在于早于 3.3 的 CPython 版本中,通常用于 检测构建是将 UCS2 还是 UCS4 用于 Unicode 对象 — 在 PEP 393 之前。Py_UNICODE因生成配置而异。
Py_ENABLE_SHARED1共享是否可用。libpython因生成配置而异。
PY_ENABLE_SHARED1共享是否可用。libpython因生成配置而异。
CCgcc用于构建 Python 发行版的 C 编译器。因生成配置而异。
CXXg++用于构建 Python 发行版的 C 编译器。因生成配置而异。
CFLAGS-DNDEBUG -g -fwrapv ...用于构建 Python 发行版的 C 编译器标志。因生成配置而异。
py_version3.11.3Python 版本的完整形式。因 Python 语言版本而异。
py_version_short3.11Python 版本的自定义形式,仅包含主要和次要 数字。因 Python 语言版本而异。
py_version_nodot311Python 版本的自定义形式,仅包含主要和次要 数字,没有点。因 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因平台和安装而异。
platlibdirlib同上,请参考上表中的条目。sys.platlibdir因平台和供应商而异。
SIZEOF_*4某种 C 类型的大小(、 等)。doublefloat因系统体系结构和构建细节而异。

由于 Python 带来编译,分别没有共享或静态支持。libpython

这是稳定 ABI 的用户应该使用的库 链接反对,如果他们需要链接反对.libpythonlibpython

相关资讯

构建系统需要一些信息,例如。平台 特殊性——分散在许多地方,而且往往很难 识别代码,并基于这些假设。在本节中,我们将尝试 记录最相关的案例。

什么时候应该链接到扩展?

简答

是的,在 Windows 上。在 POSIX 平台上没有,Android、Cygwin 等除外 基于 Windows 的类似 POSIX 的平台。

在构建动态加载扩展时,根据目标平台, 它们可能需要与 .libpython

在 Windows 上,扩展需要链接到 ,因为所有符号 必须在链接时可解析。基于 Windows 的类似 POSIX 的平台——比如 Cygwin、MinGW 或 MSYS — 也需要链接到 .libpythonlibpython

在大多数 POSIX 平台上,没有必要链接到 ,因为 这些符号将在解释器中可用 - 或者,当 embedding,有问题的可执行文件/库 — 已经链接到 .不链接扩展模块将允许 它由静态 Python 构建加载,因此在可能的情况下,最好这样做 所以(参见 GH-65735)。libpythonlibpythonlibpython

并非所有 POSIX 平台都如此,因此请务必检查。一 例如 Android,其中只有主要可执行文件和条目 被认为是 (意味着依赖项是 ) ,这会导致符号在加载 外延。LD_PRELOADRTLD_GLOBALRTLD_LOCALlibpython

什么是 、 、 和 ?

这些是 Python 初始化中设置的属性,用于描述 运行环境。它们指的是目录的前缀,其中 安装/环境文件,如下表所示。sys

名字目标文件环境范围
prefix独立于平台(例如纯 Python)特定于站点
exec_prefix依赖于平台(例如本机代码)特定于站点
base_prefix独立于平台(例如纯 Python)安装范围
base_exec_prefix依赖于平台(例如本机代码)安装范围

因为特定于站点的前缀在虚拟中会有所不同 环境中,检查通常用于 检查我们是否处于虚拟环境中。sys.prexix != sys.base_prefix

The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/70.html

上一篇 下一篇

相关阅读

取消
微信二维码
微信二维码
支付宝二维码