python-peps/peps/pep-0730.rst

369 lines
15 KiB
ReStructuredText

PEP: 730
Title: Adding iOS as a supported platform
Author: Russell Keith-Magee <russell@keith-magee.com>
Sponsor: Ned Deily <nad@python.org>
Discussions-To: https://discuss.python.org/t/pep730-adding-ios-as-a-supported-platform/35854
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 09-Oct-2023
Python-Version: 3.13
Abstract
========
This PEP proposes adding iOS as a supported platform in CPython. The initial
goal is to achieve Tier 3 support for Python 3.13. This PEP describes the
technical aspects of the changes that are required to support iOS. It also
describes the project management concerns related to adoption of iOS as a Tier 3
platform.
Motivation
==========
Over the last 15 years, mobile platforms have become increasingly important
parts of the computing landscape. iOS is one of two operating systems that
control the vast majority of these devices. However, there is no official
support for iOS in CPython.
The `BeeWare Project <https://beeware.org>`__ and `Kivy <https://kivy.org>`__
have both supported iOS for almost 10 years. This support has been able to
generate applications that have been accepted for publication in the iOS App
Store. This demonstrates the technical feasibility of iOS support.
It is important for the future of Python as a language that it is able to be
used on any hardware or OS that has widespread adoption. If Python cannot be
used a on a platform that has widespread use, adoption of the language will be
impacted as potential users will adopt other languages that *do* provide support
for these platforms.
Rationale
=========
Development landscape
---------------------
iOS provides a single API, but 2 distinct ABIs - ``iphoneos`` (physical
devices), and ``iphonesimulator``. Each of these ABIs can be provided on
multiple CPU architectures. At time of writing, Apple officially supports
``arm64`` on the device ABI, and ``arm64`` and ``x86_64`` are supported on the
simulator ABI.
As with macOS, iOS supports the creation of "fat" binaries that contains
multiple CPU architectures. However, fat binaries *cannot* span ABIs. That is,
it is possible to have a fat *simulator* binary, and a fat *device* binary, but
it is not possible to create a single fat "iOS" binary that covers both
simulator and device needs. To support distribution of a single development
artefact, Apple uses an "XCframework" structure - a wrapper around multiple ABIs
that implement a common API.
iOS runs on a Darwin kernel, similar to macOS. However, there is a need to
differentiate between macOS and iOS at an implementation level, as there are
significant platform differences between iOS and macOS.
iOS code is compiled for compatibility against a minimum iOS version.
POSIX compliance
----------------
iOS is broadly a POSIX platform. However, similar to WASI/Emscripten, there are
POSIX APIs that exist on iOS, but cannot be used; and POSIX APIs that don't
exist at all.
Most notable of these is the fact that iOS does not provide any form of
multiprocess support. ``fork`` and ``spawn`` both *exist* in the iOS API;
however, if they are invoked, the invoking iOS process stops, and the new
process doesn't start.
Unlike WASI/Emscripten, threading *is* 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 communication *are* available.
Dynamic libraries
-----------------
The iOS `App Store guidelines
<https://developer.apple.com/app-store/review/guidelines>`__ allow apps to be
written in languages other than Objective C or Swift. However, they have very
strict guidelines about the structure of apps that are submitted for
distribution.
iOS apps can use dynamically loaded libraries; however, there are very strict
requirements on how dynamically loaded content is packaged for use on iOS:
* Dynamic binary content must be compiled as dynamic libraries, not shared
objects or binary bundles.
* They must be packaged in the app bundle as Frameworks.
* Each Framework can only contain a single dynamic library.
* The Framework *must* be contained in the iOS App's ``Frameworks`` folder.
* A Framework may not contain any non-library content.
This imposes some constraints on the operation of CPython. It is not possible
store binary modules in the ``lib-dynload`` and/or ``site-packages`` folders;
they must be stored in the app's Frameworks folder, with each module wrapped in
a Framework. This also means that the common assumption that a Python module can
construct the location of a binary module by using the ``__file__`` attribute of
the Python module no longer holds.
As with macOS, compiling a dynamic library requires the use of the ``--undefined
dynamic_lookup`` option to avoid linking libPython into every binary module.
This option currently raises a deprecation warning when it is used. This warning
*was* previously raised on macOS builds as well; however, responses from Apple
staff suggests this was unintentional, and they `did not intend to break the
CPython ecosystem by removing this option
<https://developer.apple.com/forums/thread/719961>`__. It is difficult to judge
whether iOS support would fall under the same umbrella.
Distribution
------------
Adding iOS as a Tier 3 platform only requires adding support for compiling an
iOS-compatibile code with an unpatched CPython code checkout. It does not
require production of officially distributed iOS artefacts for use by end-users.
If/when iOS is updated to Tier 2 or 1 support, there should be a process for
producing iOS distribution artefacts. This could be in the form of an "embedded
distribution" analogous to the Windows embedded distribution, or as a Cocoapod
or Swift Package that could be added to an Xcode project.
Console and interactive usage
-----------------------------
Distribution of a traditional CPython REPL or interactive "python.exe" should
not be considered a goal of this work.
Mobile devices (including iOS) do not provide a TTY-style console. They do not
provide ``stdin``, ``stdout`` or ``stderr``. iOS provides a system log, and it
is possible to install a redirection so that all ``stdout`` and ``stderr``
content is redirected to the system log; but there is no analog for
``stdin``.
In addition, iOS places restrictions on downloading additional code at runtime
(as this behavior would be functionally indistinguishable from trying to work
around App Store review). As a result, a traditional "create a virtual
environment and pip install" development experience will not be viable on iOS.
It is *possible* to build an native iOS application that provides a REPL
interface. 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. The
iOS app store already contains several examples of apps in this category (e.g.,
`Pythonista <http://www.omz-software.com/pythonista/>`__ and `Pyto
<https://pyto.readthedocs.io/>`__). The focus of this work would be to provide
an embedded distribution that IDE-style native interfaces could utilize, not a
user-facing "app" interface to iOS on Python.
Specification
=============
Platform identification
-----------------------
``sys``
'''''''
``sys.platform`` will identify as ``"ios"`` on both simulator and physical
devices.
``sys.implementation._multiarch`` will describe the ABI and CPU architecture:
* ``"iphoneos-arm64"`` for ARM64 devices
* ``"iphonesimulator-arm64"`` for ARM64 simulators
* ``"iphonesimulator-x86_64"`` for x86_64 simulators
``sys.implementation`` will also have an additional attribute - ``_simulator`` -
storing a Boolean that is ``True`` if the device running the app is a simulator.
This attribute would not exist on non-iOS platforms.
``platform``
''''''''''''
Platform will be used as the primary mechanism for retrieving OS and device
details.
* ``platform.system()`` - ``"iOS"``
* ``platform.node()`` - the user-provided name of the device, as returned by the
``[[UIDevice currentDevice] systemName]`` system call (e.g.,
``"Janes-iPhone"``). For simulated devices, this will be the name of the
development computer running the simulator.
* ``platform.release()`` - the iOS version number, as a string (e.g., ``"16.6.1"``)
* ``platform.machine()`` - The device model returned by ``[[UIDevice
currentDevice] model]`` (e.g., ``"iPhone13,2"``); or ``"iPhoneSimulator"`` for
simulated devices.
All other values will be as returned by ``os.uname()``
``os``
''''''
``os.uname()`` will return the raw result of a POSIX ``uname()`` call. This will
result in the following values:
* ``sysname`` - ``"Darwin"``
* ``release`` - The Darwin kernel version (e.g., ``"22.6.0"``)
``sysconfig``
'''''''''''''
The ``sysconfig`` module will use the minimum iOS version as part of
``sysconfig.get_platform()`` identifier (e.g., ``"iOS-12.0-iphoneos-arm64"``).
The ``sysconfigdata_name`` and Config makefile will follow the same patterns as
existing platforms (using ``sys.platform``, ``sys.implementation._multiarch``
etc.) to construct identifiers.
Subprocess support
------------------
iOS will leverage the pattern for disabling subprocesses established by
WASI/Emscripten. The ``subprocess`` module will raise an exception if an attempt
is made to start a subprocess, and ``os.fork`` and ``os.spawn`` calls will raise
an ``OSError``.
Dynamic module loading
----------------------
To accommodate iOS dynamic loading, the ``importlib`` bootstrap will be extended
to add a metapath finder that can convert a request for a Python binary module
into a Framework location. This finder will only be installed if ``sys.platform
== "ios"``.
This finder will convert a Python module name (e.g., ``foo.bar._whiz``) into a
unique Framework name by replacing the dots with underscores (i.e.,
``foo_bar__whiz.framework``). A framework is a directory; the finder will look
for ``_whiz.dylib`` in that directory.
CI resources
------------
GitHub Actions is able to host iOS simulators on their macOS machines, and the
iOS simulator can be controlled by scripting environments. The free tier
currently only provides x86_64 macOS machines; however ARM64 runners `have
recently become available on paid plans <https://github.blog/
2023-10-02-introducing-the-new-apple-silicon-powered-m1-macos-larger-runner-for-github-actions/>`__.
If GitHub Actions resources are insufficient or not viable for cost reasons,
Anaconda has offered to provide resources to support CI requirements.
Packaging
---------
iOS will not provide a "universal" wheel format. Instead, wheels will be
provided for each ABI-arch combination. At present, no binary merging is
required. There is only one on-device architecture; and simulator binaries are
not considered to be distributable artefacts, so only one architecture is needed
to build a simulator.
iOS wheels will use tags:
* ``iOS_12_0_iphoneos_arm64``
* ``iOS_12_0_iphonesimulator_arm64``
* ``iOS_12_0_iphonesimulator_x86_64``
In these tags, "12.0" is the minimum supported iOS version. The choice of
minimum supported iOS version is a decision of whoever compiles CPython for iOS.
At time of writing, iOS 12.0 exposes most significant iOS features, while
reaching near 100% of devices.
These wheels can include binary modules in-situ (i.e., co-located with the
Python source, in the same way as wheels for a desktop platform); however, they
will need to be post-processed as binary modules need to be moved into the
"Frameworks" location for distribution. This can be automated with an Xcode
build step.
PEP 11 Update
-------------
:pep:`11` will be updated to include the three iOS ABIs:
* ``aarch64-apple-ios``
* ``aarch64-apple-ios-simulator``
* ``x86_64-apple-ios-simulator``
Ned Deily will serve as the initial core team contact for these ABIs.
Backwards Compatibility
=======================
Adding a new platform does not introduce any backwards compatibility concerns to
CPython itself.
There may be some backwards compatibility implications on the projects that have
historically provided CPython support (i.e., BeeWare and Kivy) if the final form
of any CPython patches don't align with the patches they have historically used.
Although not strictly a backwards compatibility issue, there *is* a platform
adoption consideration. Although CPython itself may support iOS, if it is
unclear how to produce iOS-compatible wheels, and prominent libraries like
cryptography, Pillow, and NumPy don't provide iOS wheels, the ability of the
community to adopt Python on iOS will be limited. Therefore, it will be
necessary to clearly document how projects can add iOS builds to their CI and
release tooling. Adding iOS support to tools like `crossenv
<https://crossenv.readthedocs.io/>`__ and `cibuildwheel
<https://cibuildwheel.readthedocs.io/>`__ may be one way to achieve this.
Security Implications
=====================
Adding iOS as a new platform does not add any security implications.
How to Teach This
=================
The education needs related to this PEP mostly relate to how end-users can add
iOS support to their own Xcode projects. This can be accomplished with
documentation and tutorials on that process. The need for this documentation
will increase if/when support raises from Tier 3 to Tier 2 or 1; however, this
transition should also be accompanied with simplified deployment artefacts (such
as a Cocoapod or Swift package) that are integrated with Xcode development.
Reference Implementation
========================
The BeeWare `Python-Apple-support
<https://github.com/beeware/Python-Apple-support>`__ repository contains a
reference patch and build tooling to compile a distributable artefact.
`Briefcase <https://briefcase.readthedocs.org>`__ provides a reference
implementation of code to execute test suites on iOS simulators. The `Toga
Testbed <https://github.com/beeware/toga/tree/main/testbed>`__ is an example of
a test suite that is executed on the iOS simulator using GitHub Actions.
Rejected Ideas
==============
``sys.implementation._simulator`` availability
----------------------------------------------
The ``_simulator`` attribute could be provided on *all* platforms, returning
``False``. However, the attribute has no use outside of an iOS context.
Open Issues
===========
On-device testing
-----------------
CI testing on simulators can be accommodated reasonably easily.
On-device testing is much harder, as availability of device farms that could be
configured to provide Buildbots or Github Actions runners is limited.
However, on device testing may not be necessary. As a data point - Apple's Xcode
Cloud solution doesn't provide on-device testing. They rely on the fact that the
API is consistent between device and simulator, and ARM64 simulator testing is
sufficient to reveal CPU-specific issues.
Copyright
=========
This document is placed in the public domain or under the CC0-1.0-Universal
license, whichever is more permissive.