Python - PEP 730 – 将 iOS 添加为支持平台
摘要
本 PEP 提议在 CPython 中将 iOS 添加为支持平台。初步目标是在 Python 3.13 实现 Tier 3 支持。本文描述了支持 iOS 所需的技术变更,并介绍了将 iOS 纳入 Tier 3 平台的项目管理相关问题。
动机
在过去 15 年里,移动平台在计算领域中变得日益重要。iOS 是两个主导该领域的操作系统之一。然而,CPython 目前并没有正式支持 iOS。
BeeWare 项目和 Kivy 已经支持 iOS 近 10 年。通过这些项目生成的应用已被 iOS App Store 接受发布,证明了 iOS 支持在技术上是可行的。
确保 Python 能在任何广泛使用的硬件或操作系统上使用,对于其未来至关重要。如果 Python 不能在主流平台上使用,潜在用户就会转向支持这些平台的其他语言,从而影响 Python 的采用。
原理
开发环境现状
iOS 提供单一 API,但有两种不同的 ABI —— iphoneos(物理设备)和 iphonesimulator。每种 ABI 又可用于多种 CPU 架构。目前,Apple 官方支持设备 ABI 下的 arm64,以及模拟器 ABI 下的 arm64 和 x86_64。
与 macOS 类似,iOS 支持“fat”二进制(多架构合成),但 fat 二进制不能跨 ABI。即,可有 fat 模拟器或 fat 设备库,但不能有同时覆盖两者的“胖”iOS 二进制。为分发单一开发产物,Apple 使用“XCframework” —— 它是多个 ABI 框架的集合封装。
iOS 运行 Darwin 内核,类似 macOS。但实现层面须区分 iOS 和 macOS,因为二者的平台差异显著。
iOS 代码需针对最低 iOS 版本编译。
Apple 在市场推广中常提及“iPadOS”,但开发层面,iPadOS 与 iOS 无实质区别。为 iphoneos 或 iphonesimulator ABI 编译的二进制均可在 iPad 上部署。
tvOS、watchOS、visionOS 等其它 Apple 平台使用不同的 ABI,不在本 PEP 范围内。
POSIX 兼容性
iOS 基本是 POSIX 平台。但如 WASI/Emscripten,iOS 存在 POSIX API,但有些不能用,有些根本不存在。
最显著的是 iOS 不支持多进程。fork 和 spawn 在 API 中存在,但若调用,会导致当前进程直接终止,新进程不会启动。
与 WASI/Emscripten 不同,iOS 支持线程。
套接字也有限制。由于进程沙箱化,不支持通过 socket 进行进程间通信,但网络 socket 可用。
动态库
iOS App Store 指南 允许用非 Objective C/Swift 语言开发应用,但对动态库结构有严格要求:
- 二进制内容必须编译为动态库,而非共享对象或二进制 bundle。
- 必须以 Framework 形式打包进应用包。
- 每个 Framework 只能包含一个动态库。
- Framework 必须位于 App 的
Frameworks文件夹下。 - Framework 不能包含非库内容。
这对 CPython 有限制:不能像惯例那样将二进制模块放在 lib-dynload 或 site-packages 下,必须放在 app 的 Frameworks 文件夹,每个模块包裹为一个 Framework。因此,Python 模块通过 __file__ 属性定位二进制模块的传统方式不再适用。
与 macOS 一样,构建可被静态链接 Python 加载的二进制模块需用 --undefined dynamic_lookup 编译选项,以避免每个模块都链接 libpython3.x。但在 iOS 上,该编译参数会引发弃用警告。macOS 也有类似警告。Apple 员工声称不会通过移除该选项破坏 CPython 生态。但 Python 在 iOS 上影响力有限,无法判断 iOS 下该选项是否同样受保障。
控制台与交互式使用
分发传统的 CPython REPL 或交互式“python.exe”不应作为本工作目标。
移动设备(包括 iOS)没有 TTY 控制台,不提供 stdin、stdout 或 stderr。iOS 提供系统日志,可重定向 stdout、stderr 到日志,但无 stdin 对应。
此外,iOS 限制运行时下载代码(此举会被视为规避 App Store 审查)。因此,传统的“创建虚拟环境并 pip install”开发体验在 iOS 上不可行。
理论上可开发原生 iOS 应用实现 REPL,但更类似于 IDLE 用户体验。由于 Tkinter 不能用于 iOS,需从零开发界面。App Store 已有类似产品(如 Pythonista、Pyto)。本工作聚焦于提供可被 IDE 类原生界面嵌入的发行版,而不是面向用户的“Python on iOS”应用。
规范
平台识别
sys
在模拟器和物理设备上,sys.platform 标识为 "ios"。
sys.implementation._multiarch 描述 ABI 和 CPU 架构:
- ARM64 设备:
"arm64-iphoneos" - ARM64 模拟器:
"arm64-iphonesimulator" - x86_64 模拟器:
"x86_64-iphonesimulator"
platform
platform 模块将支持返回 iOS 相关细节。大部分值与 os.uname() 返回一致,例外如下:
platform.system()—— 返回"iOS"或iPadOS(视硬件),而非"Darwin"platform.release()—— 返回 iOS 版本字符串(如"16.6.1"),而非 Darwin 内核版本
还将新增 platform.ios_ver(),类似于 platform.mac_ver(),返回包含以下内容的 namedtuple:
system—— OS 名称(iOS或iPadOS)release—— iOS 版本字符串(如"16.6.1")model—— 设备型号标识(如"iPhone13,2"),模拟器返回"iPhone"或"iPad"is_simulator—— 是否为模拟器的布尔值
os
os.uname() 返回 POSIX uname() 的原始结果:
sysname——"Darwin"release—— Darwin 内核版本(如"22.6.0")
即,os 模块作为系统 API 原始接口,platform 提供更高层、更通用的值。
sysconfig
sysconfig 模块将以最低 iOS 版本作为 sysconfig.get_platform() 的一部分(如 "ios-12.0-arm64-iphoneos")。sysconfigdata_name 和 Config makefile 按现有平台规则(用 sys.platform、sys.implementation._multiarch 等)构造标识符。
子进程支持
iOS 参照 WASI/Emscripten 的方式禁用子进程。subprocess 模块尝试启动子进程时抛出异常,os.fork 和 os.spawn 抛出 OSError。
动态模块加载
为适配 iOS 动态加载,importlib 启动流程将新增 metapath finder,可将 Python 二进制模块请求转换为 Framework 路径。仅当 sys.platform == "ios" 时安装该 finder。
该 finder 将 Python 模块名(如 foo.bar._whiz)转为唯一 Framework 名(如 foo.bar._whiz.framework),即在对应目录下查找同名二进制文件。
编译
唯一支持的二进制格式是可动态链接的 libpython3.x.dylib,并按 iOS Framework 格式打包。虽然编译时可用 --undefined dynamic_lookup,但该选项长期可用性无法保证。因此,iOS 二进制模块将链接 libpython3.x.dylib,不支持静态链接的 libpython3.x.a,与 Windows 的做法一致。
构建 CPython for iOS 需用 configure 构建系统。一套 configure/make/make install 会产出单 ABI、单架构的 Python.framework。
额外工具需要将多个架构的 Python.framework 合并为“fat”库,并将多 ABI 合并为 Apple 的 XCframework 格式。
将提供 Xcode 项目以运行 CPython 测试套件,并自动化编译、部署、安装与执行测试的流程。
分发
将 iOS 添加为 Tier 3 平台仅要求能从原生 CPython 源码编译出 iOS 兼容构建,无需官方分发 iOS 产物。
若未来 iOS 升级为 Tier 2 或 1,可用上述工具生成 XCframework 发行包,作为“嵌入式发行版”或 CocoaPod/Swift 包供 Xcode 项目集成。
CI 资源
Anaconda 提供物理硬件运行 iOS buildbot。
GitHub Actions 支持在其 macOS 机器上运行 iOS 模拟器,ARM64 runner 亦已上线(付费计划)。但为避免消耗 macOS 资源,iOS 的 CI 任务不会添加到标准 CI 配置中。
打包
iOS 不提供“通用” wheel 格式,而是每个 ABI-架构组合单独 wheel。
iOS wheel 标签示例:
ios_12_0_arm64_iphoneosios_12_0_arm64_iphonesimulatorios_12_0_x86_64_iphonesimulator
其中“12.0”为最低支持的 iOS 版本(可随编译时设定)。iOS 12.0 能覆盖几乎全部设备,并支持大部分重要特性,故作为版本下限。
这些 wheel 可像桌面平台一样包含二进制模块,但需后处理,将二进制模块移到 Frameworks 目录,可通过 Xcode 构建步骤自动化。
PEP 11 更新
PEP 11 将新增以下 iOS ABI:
arm64-apple-iosarm64-apple-ios-simulator
Ned Deily 将作为这些 ABI 的核心团队联系人。
x86_64-apple-ios-simulator 目标仅“尽力支持”,不纳入 tier 3。因 x86_64 模拟器即将弃用,且难以获得 x86_64 macOS 硬件。
向后兼容性
新增平台不会引入对 CPython 本身的向后兼容性问题。
若 CPython 最终补丁与 BeeWare、Kivy 等历史支持项目不一致,可能影响其兼容性。
此外,虽然不属于传统意义上的兼容性问题,但平台推广有待关注。即便 CPython 支持 iOS,若不清楚如何生成 iOS 兼容 wheel,且主流库(如 cryptography、Pillow、NumPy)不提供 iOS wheel,社区采用 Python on iOS 也会受限。因此需清晰文档说明如何在 CI 和发布工具中添加 iOS 构建。为 crossenv、cibuildwheel 等工具添加 iOS 支持,是可行方式。
安全影响
新增 iOS 平台不会带来安全影响。
如何教学
本 PEP 的教学重点是指导终端用户如何将 iOS 支持集成到自己的 Xcode 项目中。可通过文档和教程实现。若未来 iOS 支持升级为 Tier 2 或 1,需同步推出便于部署的产物(如 Cocoapod 或 Swift 包)以便 Xcode 开发集成。
参考实现
BeeWare 的 Python-Apple-support 仓库包含了参考补丁和构建工具。
Briefcase 提供在 iOS 模拟器上执行测试的参考代码。Toga Testbed 是一个用 GitHub Actions 在 iOS 模拟器上执行的测试套件示例。
被拒绝的方案
模拟器识别
PEP 初稿建议在 sys.implementation._simulator 增加模拟器标识,后因该属性名为保护名且污染 sys 命名空间被否决。
讨论中也提议增加通用 platform.is_emulator(),但因“模拟器”定义和检测难以统一,仅保留 iOS 专用的 platform.ios_ver()。
GNU 编译器三元组
autoconf 需用 GNU 编译器三元组标识平台,但其工具链不原生支持 iOS 模拟器。可通过补丁解决,但会导致命名不一致(如 arm64 vs aarch64,以及模拟器标识)。
Apple 自身工具用 arm64,平台标识为 iphoneos 和 iphonesimulator。Rust 工具链用 aarch64,平台为 aarch64-apple-ios、aarch64-apple-ios-sim,但 x86_64 模拟器为 x86_64-apple-ios。
最终决定采用 arm64-apple-ios 和 arm64-apple-ios-simulator,理由包括 autoconf 已支持 ios,Apple 工具三元组习惯,架构名在 GNU 工具链外不可见,模拟器状态常用 -simulator 后缀,且所有 iOS 包都用 Apple 工具。
PEP 初稿用 aarch64,最终版改为 arm64。
“通用” wheel 格式
macOS 支持 2 种 CPU 架构,因而有“universal2” wheel 格式。iOS 理论可类比,但本 PEP 不采用这一方案,原因有二:
- macOS 经验显示,通用 wheel 在数值计算生态难以实现,非 Python 上游库大多不支持多架构构建,编译和分发复杂,主流项目倾向于分别分发 ARM64 和 x86_64 wheel。
- iOS 的“通用”定义更复杂,历史上至少有 5 种组合,未来还可能有新架构加入。直接规定单平台 wheel,可避免持续为“通用格式”标准化争论。
这样做虽会使部署更复杂,但 iOS 部署本就复杂且需工具协助。目前只需单一设备架构,无需合并。
静态构建支持
虽然 --undefined dynamic_lookup 目前可用,但长期可用性无法保证。依赖 Apple 的决策风险较高,加之生态鼓励用 Framework,无需考虑遗留静态库用途,静态链接仅带来轻微启动加速,故不支持静态 libpython3.x.a 是合理的。
若未来 macOS 链接方案有突破,iOS 亦可采用,使静态链接变得可行。但当前无明确需求。
交互/REPL 模式
移动设备无命令行,iOS app 无 stdout/stderr/stdin,理论上可重定向输出到系统日志,但无输入。要做 REPL 必须开发专用界面应用,更像 IDLE,故本 PEP 仅聚焦“嵌入模式”分发。
x86_64 模拟器支持
Apple 已不再销售 x86_64 硬件,获取 x86_64 buildbot 很难。可在 ARM64 上运行 x86_64 兼容模式,但不理想。因此,x86_64 模拟器不纳入 Tier 3。实际运行 iOS 应无问题,只是不列为官方 Tier 3。
真机测试
模拟器测试很容易,真机测试难度大,因可用的设备池有限。Apple 官方 Xcode Cloud 也不支持真机测试,认为 API 在模拟器和设备上一致,ARM64 模拟器测试足够发现 CPU 相关问题。
_multiarch 标签顺序
初稿用 <platform>-<arch> 顺序(如 iphoneos-arm64),最终版为 <arch>-<platform>(如 arm64-iphoneos),以保持与其他平台(尤其 Linux)的三元组一致,先架构后平台。
platform.ios_ver() 返回值
初稿未包含 system 字段,后为支持 platform.system() 补充。初稿也描述了 min_release,但最终版移除,因为运行时无意义,仅影响二进制兼容性;最低版本包含在 sysconfig.get_platform() 返回值中,用于 wheel 兼容性。
版权
本文档可置于公有领域或 CC0-1.0-Universal 许可下,以更宽松者为准。
原文地址:https://peps.python.org/pep-0730/
4085

被折叠的 条评论
为什么被折叠?



