Important
This PEP is a historical document. The up-to-date, canonical documentation can now be found atUsing Python on iOS.
×
SeePEP 1 for how to propose changes.
This PEP proposes adding iOS as a supported platform in CPython. The initialgoal is to achieve Tier 3 support for Python 3.13. This PEP describes thetechnical aspects of the changes that are required to support iOS. It alsodescribes the project management concerns related to adoption of iOS as a Tier 3platform.
Over the last 15 years, mobile platforms have become increasingly importantparts of the computing landscape. iOS is one of two operating systems thatcontrol the vast majority of these devices. However, there is no officialsupport for iOS in CPython.
TheBeeWare Project andKivyhave both supported iOS for almost 10 years. This support has been able togenerate applications that have been accepted for publication in the iOS AppStore. This demonstrates the technical feasibility of iOS support.
It is important for the future of Python as a language that it is able to beused on any hardware or OS that has widespread adoption. If Python cannot beused a on a platform that has widespread use, adoption of the language will beimpacted as potential users will adopt other languages thatdo provide supportfor these platforms.
iOS provides a single API, but 2 distinct ABIs -iphoneos (physicaldevices), andiphonesimulator. Each of these ABIs can be provided onmultiple CPU architectures. At time of writing, Apple officially supportsarm64 on the device ABI, andarm64 andx86_64 are supported on thesimulator ABI.
As with macOS, iOS supports the creation of “fat” binaries that containsmultiple CPU architectures. However, fat binariescannot span ABIs. That is,it is possible to have a fatsimulator binary, and a fatdevice binary, butit is not possible to create a single fat “iOS” binary that covers bothsimulator and device needs. To support distribution of a single developmentartefact, Apple uses an “XCframework” structure - a wrapper around multiple ABIsthat implement a common API.
iOS runs on a Darwin kernel, similar to macOS. However, there is a need todifferentiate between macOS and iOS at an implementation level, as there aresignificant platform differences between iOS and macOS.
iOS code is compiled for compatibility against a minimum iOS version.
Apple frequently refers to “iPadOS” in their marketing material. However, from adevelopment perspective, there is no discernable difference between iPadOS andiOS. A binary that has been compiled for theiphoneos oriphonesimulatorABIs can be deployed on iPad.
Other Apple platforms, such as tvOS, watchOS, and visionOS, use different ABIs,and are not covered by this PEP.
iOS is broadly a POSIX platform. However, similar to WASI/Emscripten, there arePOSIX APIs that exist on iOS, but cannot be used; and POSIX APIs that don’texist at all.
Most notable of these is the fact that iOS does not provide any form ofmultiprocess support.fork andspawn bothexist in the iOS API;however, if they are invoked, the invoking iOS process stops, and the newprocess doesn’t start.
Unlike WASI/Emscripten, threadingis supported on iOS.
There are also significant limits to socket handling. Due to process sandboxing,there is no availability of interprocess communication via socket. However,sockets for network communicationare available.
The iOSApp Store guidelines allow apps to bewritten in languages other than Objective C or Swift. However, they have verystrict guidelines about the structure of apps that are submitted fordistribution.
iOS apps can use dynamically loaded libraries; however, there are very strictrequirements on how dynamically loaded content is packaged for use on iOS:
Frameworks folder.This imposes some constraints on the operation of CPython. It is not possiblestore binary modules in thelib-dynload and/orsite-packages folders;they must be stored in the app’s Frameworks folder, with each module wrapped ina Framework. This also means that the common assumption that a Python module canconstruct the location of a binary module by using the__file__ attribute ofthe Python module no longer holds.
As with macOS, compiling a binary module that is accessible from astatically-linked build of Python requires the use of the--undefineddynamic_lookup option to avoid linkinglibpython3.x into every binarymodule. However, on iOS, this compiler flag raises a deprecation warning when itis used. A warning from this flag has been observed on macOS as well - however,responses from Apple staff suggest that theydo not intend to break the CPythonecosystem by removing this option. AsPython does not currently have a notable presence on iOS, it is difficult tojudge whether iOS usage of this flag would fall under the same umbrella.
Distribution of a traditional CPython REPL or interactive “python.exe” shouldnot be considered a goal of this work.
Mobile devices (including iOS) do not provide a TTY-style console. They do notprovidestdin,stdout orstderr. iOS provides a system log, and itis possible to install a redirection so that allstdout andstderrcontent is redirected to the system log; but there is no analog forstdin.
In addition, iOS places restrictions on downloading additional code at runtime(as this behavior would be functionally indistinguishable from trying to workaround App Store review). As a result, a traditional “create a virtualenvironment and pip install” development experience will not be viable on iOS.
It ispossible to build an native iOS application that provides a REPLinterface. This would be closer to an IDLE-style user experience; however,Tkinter cannot be used on iOS, so any app would require a ground-up rewrite. TheiOS app store already contains several examples of apps in this category (e.g.,Pythonista andPyto). The focus of this work would be to providean embedded distribution that IDE-style native interfaces could utilize, not auser-facing “app” interface to iOS on Python.
syssys.platform will identify as"ios" on both simulator and physicaldevices.
sys.implementation._multiarch will describe the ABI and CPU architecture:
"arm64-iphoneos" for ARM64 devices"arm64-iphonesimulator" for ARM64 simulators"x86_64-iphonesimulator" for x86_64 simulatorsplatformplatform will be modified to support returning iOS-specific details. Most ofthe values returned by theplatform module will match those returned byos.uname(), with the exception of:
platform.system() -"iOS" oriPadOS (depending on the hardware inuse), instead of"Darwin"platform.release() - the iOS version number, as a string (e.g.,"16.6.1"), instead of the Darwin kernel version.In addition, aplatform.ios_ver() method will be added. This mirrorsplatform.mac_ver(), which can be used to provide macOS version information.ios_ver() will return a namedtuple that contains the following:
system - the OS name (iOS oriPadOS, depending on hardware)release - the iOS version, as a string (e.g.,"16.6.1").model - the model identifier of the device, as a string (e.g.,"iPhone13,2"). On simulators, this will return"iPhone" or"iPad",depending on the simulator device.is_simulator - a boolean indicating if the device is a simulator.osos.uname() will return the raw result of a POSIXuname() call. This willresult in the following values:
sysname -"Darwin"release - The Darwin kernel version (e.g.,"22.6.0")This approach treats theos module as a “raw” interface to system APIs, andplatform as a higher-level API providing more generally useful values.
sysconfigThesysconfig module will use the minimum iOS version as part ofsysconfig.get_platform() (e.g.,"ios-12.0-arm64-iphoneos"). Thesysconfigdata_name and Config makefile will follow the same patterns asexisting platforms (usingsys.platform,sys.implementation._multiarchetc.) to construct identifiers.
iOS will leverage the pattern for disabling subprocesses established byWASI/Emscripten. Thesubprocess module will raise an exception if an attemptis made to start a subprocess, andos.fork andos.spawn calls will raiseanOSError.
To accommodate iOS dynamic loading, theimportlib bootstrap will be extendedto add a metapath finder that can convert a request for a Python binary moduleinto a Framework location. This finder will only be installed ifsys.platform=="ios".
This finder will convert a Python module name (e.g.,foo.bar._whiz) into aunique Framework name by using the full module name as the framework name (i.e.,foo.bar._whiz.framework). A framework is a directory; the finder will lookfor a binary namedfoo.bar._whiz in that directory.
The only binary format that will be supported is a dynamically-linkablelibpython3.x.dylib, packaged in an iOS-compatible framework format. Whilethe--undefineddynamic_lookup compiler option currently works, thelong-term viability of the option cannot be guaranteed. Rather than rely on acompiler flag with an uncertain future, binary modules on iOS will be linkedwithlibpython3.x.dylib. This means iOS binary modules will not be loadableby an executable that has been statically linked againstlibpython3.x.a.Therefore, a staticlibpython3.x.a iOS library will not be supported. Thisis the same pattern used by CPython on Windows.
Building CPython for iOS requires the use of the cross-platform tooling inCPython’sconfigure build system. A singleconfigure/make/makeinstall pass will produce aPython.framework artefact that can be used ona single ABI and architecture.
Additional tooling will be required to merge thePython.framework builds formultiple architectures into a single “fat” library. Tooling will also berequired to merge multiple ABIs into theXCframework format that Apple usesto distribute multiple frameworks for different ABIs in a single bundle.
An Xcode project will be provided for the purpose of running the CPython testsuite. Tooling will be provided to automate the process of compiling the testsuite binary, start the simulator, install the test suite, and execute it.
Adding iOS as a Tier 3 platform only requires adding support for compiling aniOS-compatible build from an unpatched CPython code checkout. It does notrequire production of officially distributed iOS artefacts for use by end-users.
If/when iOS is updated to Tier 2 or 1 support, the tooling used to generate anXCframework package could be used to produce an iOS distribution artefact.This could then be distributed as an “embedded distribution” analogous to theWindows embedded distribution, or as a CocoaPod or Swift package that could beadded to an Xcode project.
Anaconda has offered to provide physical hardware torun iOS buildbots.
GitHub Actions is able to host iOS simulators on their macOS machines, and theiOS simulator can be controlled by scripting environments. The free tiercurrently only provides x86_64 macOS machines; however ARM64 runnersrecentlybecame available on paid plans.However, in order to avoid exhausting macOS runner resources, a GitHub Actionsrun for iOS will not be added as part of the standard CI configuration.
iOS will not provide a “universal” wheel format. Instead, wheels will beprovided for each ABI-arch combination.
iOS wheels will use tags:
ios_12_0_arm64_iphoneosios_12_0_arm64_iphonesimulatorios_12_0_x86_64_iphonesimulatorIn these tags, “12.0” is the minimum supported iOS version. As with macOS, thetag will incorporate the minimum iOS version that is selected when the wheel iscompiled; a wheel compiled with a minimum iOS version of 15.0 would use theios_15_0_* tags. At time of writing, iOS 12.0 exposes most significant iOSfeatures, while reaching near 100% of devices; this will be used as a floor foriOS version matching.
These wheels can include binary modules in-situ (i.e., co-located with thePython source, in the same way as wheels for a desktop platform); however, theywill need to be post-processed as binary modules need to be moved into the“Frameworks” location for distribution. This can be automated with an Xcodebuild step.
PEP 11 will be updated to include two of the iOS ABIs:
arm64-apple-iosarm64-apple-ios-simulatorNed Deily will serve as the initial core team contact for these ABIs.
Thex86_64-apple-ios-simulator target will be supported on a best-effortbasis, but will not be targeted for tier 3 support. This is due to the impendingdeprecation of x86_64 as a simulation platform, combined with the difficulty ofcommissioning x86_64 macOS hardware at this time.
Adding a new platform does not introduce any backwards compatibility concerns toCPython itself.
There may be some backwards compatibility implications on the projects that havehistorically provided CPython support (i.e., BeeWare and Kivy) if the final formof any CPython patches don’t align with the patches they have historically used.
Although not strictly a backwards compatibility issue, thereis a platformadoption consideration. Although CPython itself may support iOS, if it isunclear how to produce iOS-compatible wheels, and prominent libraries likecryptography, Pillow, and NumPy don’t provide iOS wheels, the ability of thecommunity to adopt Python on iOS will be limited. Therefore, it will benecessary to clearly document how projects can add iOS builds to their CI andrelease tooling. Adding iOS support to tools likecrossenv andcibuildwheel may be one way to achieve this.
Adding iOS as a new platform does not add any security implications.
The education needs related to this PEP mostly relate to how end-users can addiOS support to their own Xcode projects. This can be accomplished withdocumentation and tutorials on that process. The need for this documentationwill increase if/when support raises from Tier 3 to Tier 2 or 1; however, thistransition should also be accompanied with simplified deployment artefacts (suchas a Cocoapod or Swift package) that are integrated with Xcode development.
The BeeWarePython-Apple-support repository contains areference patch and build tooling to compile a distributable artefact.
Briefcase provides a referenceimplementation of code to execute test suites on iOS simulators. TheTogaTestbed is an example ofa test suite that is executed on the iOS simulator using GitHub Actions.
Earlier versions of this PEP suggested the inclusion ofsys.implementation._simulator attribute to identify when code is running ondevice, or on a simulator. This was rejected due to the use of a protected namefor a public API, plus the pollution of thesys namespace with aniOS-specific detail.
Another proposal during discussion was to include a genericplatform.is_emulator() API that could be implemented by any platform - forexample to differentiate running on x86_64 code on ARM64 hardware, or whenrunning in QEMU or other virtualization methods. This was rejected on the basisthat it wasn’t clear what a consistent interpretation of “emulator” would be, orhow an emulator would be detected outside of the iOS case.
The decision was made to keep this detail iOS-specific, and include it on theplatform.ios_ver() API.
autoconf requires the use of a GNU compiler triple to identify build andhost platforms. However, theautoconf toolchain doesn’t provide nativesupport for iOS simulators, so we are left with the task of working out how tosqueeze iOS hardware into GNU’s naming regimen.
This can be done (with some patching ofconfig.sub), but it leads to 2 majorsources of naming inconsistency:
arm64 vsaarch64 as an identifier of 64-bit ARM hardware; andApple’s own tools usearm64 as the architecture, but appear to be tolerantofaarch64 in some cases. The device platform is identified asiphoneosandiphonesimulator.
Rust toolchains usesaarch64 as the architecture, and useaarch64-apple-ios andaarch64-apple-ios-sim to identify the deviceplatform; however, they usex86_64-apple-ios to represent iOSsimulatorson x86_64 hardware.
The decision was made to usearm64-apple-ios andarm64-apple-ios-simulator because:
autoconf toolchain already contains support forios as a platforminconfig.sub; it’s only the simulator that doesn’t have a representation.sys.platform.arm64, andthe GNU tooling usage of the architecture isn’t visible outside the buildprocess.-simulator suffix.The initially accepted version of this document used theaarch64 form as the PEP 11 identifier; this was corrected during finalization.
macOS currently supports 2 CPU architectures. To aid the end-user developmentexperience, Python defines a “universal2” wheel format that incorporates bothx86_64 and ARM64 binaries.
It would be conceptually possible to offer an analogous “universal” iOS wheelformat. However, this PEP does not use this approach, for 2 reasons.
Firstly, the experience on macOS, especially in the numerical Python ecosystem,has been that universal wheels can be exceedingly difficult to accommodate.While native macOS libraries maintain strong multi-platform support, and Pythonitself has been updated, the vast majority of upstream non-Python libraries donot provide multi-architecture build support. As a result, compiling universalwheels inevitably requires multiple compilation passes, and complex decisionsover how to distribute header files for different architectures. As a result ofthis complexity, many popular projects (including NumPy and Pillow) do notprovide universal wheels at all, instead providing separate ARM64 and x86_64wheels.
Secondly, historical experience is that iOS would require a much more fluid“universal” definition. In the last 10 years, there have beenat least 5different possible interpretations of “universal” that would apply to iOS,including various combinations of armv6, armv7, armv7s, arm64, x86 and x86_64architectures, on device and simulator. If defined right now, “universal-iOS”would likely include x86_64 and arm64 on simulator, and arm64 on device;however, the pending deprecation of x86_64 hardware would add anotherinterpretation; and there may be a need to add arm64e as a new devicearchitecture in the future. Specifying iOS wheels as single-platform-only meansthe Python core team can avoid an ongoing standardization discussion about theupdated “universal” formats.
It also means wheel publishers are able to make per-project decisions over whichplatforms are feasible to support. For example, a project may choose to dropx86_64 support, or adopt a new architecture earlier than other parts of thePython ecosystem. Using platform-specific wheels means this decision can be leftto individual package publishers.
This decision comes at cost of making deployment more complicated. However,deployment on iOS is already a complicated process that is best aided by tools.At present, no binary merging is required, as there is only one on-devicearchitecture, and simulator binaries are not considered to be distributableartefacts, so only one architecture is needed to build an app for a simulator.
While the long-term viability of the--undefineddynamic_lookup optioncannot be guaranteed, the option does exist, and it works. One option would beto ignore the deprecation warning, and hope that Apple either reverses thedeprecation decision, or never finalizes the deprecation.
Given that Apple’s decision-making process is entirely opaque, this would be, atbest, a risky option. When combined with the fact that the broader iOSdevelopment ecosystem encourages the use of frameworks, there are no legacy usesof a static library to consider, and the only benefit to a statically-linked iOSlibpython3.x.a is a very slightly reduced app startup time, omitting supportfor static builds oflibpython3.x seems a reasonable compromise.
It is worth noting that there has been some discussion onan alternate approachto linking on macOS thatwould remove the need for the--undefineddynamic_lookup option, althoughdiscussion on this approach appears to have stalled due to complications inimplementation. If those complications were to be overcome, it is highly likelythat the same approachcould be used on iOS, whichwould make a staticallylinkedlibpython3.x.a plausible.
The decision to link binary modules againstlibpython3.x.dylib wouldcomplicate the introduction of staticlibpython3.x.a builds in the future,as the process of moving to a different binary module linking approach wouldrequire a clear way to differentate “dynamically-linked” iOS binary modules from“static-compatible” iOS binary modules. However, given the lack of tangiblebenefits of a staticlibpython3.x.a, it seems unlikely that there will beany requirement to make this change.
A traditionalpython.exe command line experience isn’t really viable onmobile devices, because mobile devices don’t have a command line. iOS apps don’thave a stdout, stderr or stdin; and while you can redirect stdout and stderr tothe system log, there’s no source for stdin that exists that doesn’t alsoinvolve building a very specific user-facing app that would be closer to anIDLE-style IDE experience. Therefore, the decision was made to only focus on“embedded mode” as a target for mobile distribution.
Apple no longer sells x86_64 hardware. As a result, commissioning an x86_64buildbot can be difficult. It is possible to run macOS binaries in x86_64compatibility mode on ARM64 hardware; however, this isn’t ideal for testingpurposes. Therefore, the x86_64 Simulator (x86_64-apple-ios-simulator) willnot be added as a Tier 3 target. It is highly likely that iOS support will workon the x86_64 without any modification; this only impacts on theofficial Tier3 status.
CI testing on simulators can be accommodated reasonably easily. On-devicetesting is much harder, as availability of device farms that could be configuredto provide Buildbots or Github Actions runners is limited.
However, on device testing may not be necessary. As a data point - Apple’s XcodeCloud solution doesn’t provide on-device testing. They rely on the fact that theAPI is consistent between device and simulator, and ARM64 simulator testing issufficient to reveal CPU-specific issues.
_multiarch tagsThe initially accepted version of this document used<platform>-<arch>ordering (e.g.,iphoneos-arm64) forsys.implementation._multiarch (andrelated values, such as wheel tags). The final merged version uses the<arch>-<platform> ordering (e.g.,arm64-iphoneos). This is forconsistency with compiler triples on other platforms (especially Linux), whichspecify the architecture before the operating system.
platform.ios_ver()The initially accepted version of this document didn’t include asystemidentifier. This was added during the implementation phase to support the implementation ofplatform.system().
The initially accepted version of this document also described thatmin_release would be returned in theios_ver() result. The final versionomits themin_release value, as it is not significant at runtime; it onlyimpacts on binary compatibility. The minimum versionis included in the valuereturned bysysconfig.get_platform(), as this is used to define wheel (andother binary) compatibility.
This document is placed in the public domain or under the CC0-1.0-Universallicense, whichever is more permissive.
Source:https://github.com/python/peps/blob/main/peps/pep-0730.rst
Last modified:2025-02-01 08:55:40 GMT