-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
gh-114099: Add documentation for iOS platform #117057
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
32e9d12
Add documentation exclusions for iOS.
freakboy3742 09e9302
Add iOS platform usage documentation.
freakboy3742 d18ed2b
Add release note.
freakboy3742 4ac2150
Corrected linting issue.
freakboy3742 b5d8aa2
Resolve a class reference failure.
freakboy3742 f915e22
Remove references to Emscripten.
freakboy3742 142ed17
Correct the placement of some availability inclusions.
freakboy3742 d40ef29
Apply suggestions from code review
freakboy3742 15731f7
Additional cleanups from review.
freakboy3742 6385b9f
Additional details on running in Xcode.
freakboy3742 0d5fcf0
Apply suggestions from code review
freakboy3742 71fe520
Merge branch 'main' into ios-docs
ned-deily 73b33b6
Merge branch 'main' into ios-docs
ned-deily File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add iOS platform usage documentation.
- Loading branch information
commit 09e9302a94d62f1968f3be92bc8dc9c23f13fc1f
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
hugovk marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,314 @@ | ||
.. _using-ios: | ||
|
||
=================== | ||
Using Python on iOS | ||
=================== | ||
|
||
:Authors: | ||
Russell Keith-Magee (2024-03) | ||
|
||
Python on iOS is unlike Python on desktop platforms. On a desktop platform, | ||
Python is generally installed as a system resource that can be used by any user | ||
of that computer. Users then interact with Python by running a ``python`` | ||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
executable and entering commands at an interactive prompt, or by running a | ||
Python script. | ||
|
||
On iOS, there is no concept of installing as a system resource. The only unit | ||
of software distribution is an "app". There is also no console where you could | ||
run a ``python`` executable, or interact with a Python REPL. | ||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
As a result, the only way you can use Python on iOS is in embedded mode - that | ||
is, by writing a native iOS application, and embedding a Python interpreter | ||
using ``libPython``, and invoking Python code using the :ref:`Python embedding | ||
API <embedding>`. The full Python interpreter, the standard library, and all | ||
your Python code is then packaged as a standalone bundle that can be | ||
distributed via the iOS App Store. | ||
|
||
If you're looking to experiment for the first time with writing an iOS app in | ||
Python, projects such as `BeeWare <https://beeware.org>`__ and `Kivy | ||
<https://kivy.org>`__ will provide a much more approachable user experience. | ||
These projects manage the complexities associated with getting an iOS project | ||
running, so you only need to deal with the Python code itself. | ||
|
||
Python at runtime on iOS | ||
======================== | ||
|
||
Platform identification | ||
----------------------- | ||
|
||
When executing on iOS, ``sys.platform`` will report as ``ios``. This value will | ||
be returned on an iPhone or iPad, regardless of whether the app is running on | ||
the simulator or a physical device. | ||
|
||
Information about the specific runtime environment, including the iOS version, | ||
device model, and whether the device is a simulator, can be obtained using | ||
:func:`platform.ios_ver()`. :func:`platform.system()` will report ``iOS`` or | ||
serhiy-storchaka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``iPadOS``, depending on the device. | ||
|
||
:func:`os.uname()` reports kernel-level details; it will report a name of | ||
``Darwin``. | ||
|
||
Standard library availability | ||
----------------------------- | ||
|
||
The Python standard library has some notable ommissions and restrictions on | ||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
iOS. See the :ref:`API availability guide for iOS <iOS-availability>` for | ||
details. | ||
|
||
Binary extension modules | ||
------------------------ | ||
|
||
One notable difference about iOS as a platform is that App Store distribution | ||
imposes hard requirements on the packaging of an application. One of these | ||
requirements governs how binary extension modules are distributed. | ||
|
||
The iOS App Store requires that *all* binary modules in an iOS app must be | ||
dynamic libraries, contained in a framework with appropriate metadata, stored | ||
in the ``Frameworks`` folder of the packaged app. There can be only a single | ||
binary per framework, and there can be no executable binary material outside | ||
the Frameworks folder. | ||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
This conflicts with the usual Python approach for distributing binaries, which | ||
allows a binary extension module to be loaded from any location on | ||
``sys.path``. To ensure compliance with App Store policies, an iOS project must | ||
post-process any Python packages, converting ``.so`` binary modules into | ||
individual standalone frameworks with appropriate metadata and signing. For | ||
details on how to perform this post-processing, see the guide for :ref:`adding | ||
iOS to your project <adding-ios>`. | ||
|
||
To help Python discover binaries in their new location, the original ``.so`` | ||
file on ``sys.path`` is replaced with a ``.fwork`` file. This file is a text | ||
file containing the location of the framework binary, relative to the app | ||
bundle. To allow the framework to resolve back to the original location, the | ||
framework must contain a ``.origin`` file that contains the location of the | ||
``.fwork`` file, relative to the app bundle. | ||
|
||
For example, consider the case of an import ``from foo.bar import _whiz``, | ||
where ``_whiz`` is implemented with the binary module | ||
``sources/foo/bar/_whiz.abi3.so``, with ``sources`` being the location | ||
registered on ``sys.path``, relative to the application bundle. This module | ||
*must* be distributed as ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz`` | ||
(creating the framework name from the full import path of the module), with an | ||
``Info.plist`` file in the ``.framework`` directory identifying the binary as a | ||
framework. The ``foo.bar._whiz`` module would be represented in the original | ||
location with a ``sources/foo/bar/_whiz.abi3.fwork`` marker file, containing | ||
the path ``Frameworks/foo.bar._whiz/foo.bar._whiz``. The framework would also | ||
contain ``Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin``, containing | ||
the path to the ``.fwork`` file. | ||
|
||
When running on iOS, the Python interpreter will install a :class:`loader | ||
<importlib.AppleFrameworkLoader>` that is able to read and import ``.fwork`` | ||
files. Once imported, the ``__file__`` attribute of the binary module will | ||
report as the location of the ``.fwork`` file. However, the | ||
:class:`~importlib.machinery.ModuleSpec` for the loaded module will report the | ||
``origin`` as the location of the binary in the framework folder. | ||
|
||
Compiler stub binaries | ||
---------------------- | ||
|
||
Xcode doesn't expose explicit compilers for iOS; instead, it uses an ``xcrun`` | ||
script that resolves to a full compiler path (e.g., ``xcrun --sdk iphoneos | ||
clang`` to get the ``clang`` for an iPhone device). However, using this script | ||
poses 2 problems: | ||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
* The output of ``xcrun`` includes paths that are machine specific, resulting | ||
in a sysconfig module that cannot be shared between users; and | ||
|
||
* It results in ``CC``/``CPP``/``LD``/``AR`` definitions that include spaces. | ||
There is a lot of C ecosystem tooling that assumes that you can split a | ||
command line at the first space to get the path to the compiler executable; | ||
this isn't the case when using ``xcrun``. | ||
|
||
To avoid these problems, Python provided stubs for these tools. These stubs are | ||
shell script wrappers around the underingly ``xcrun`` tools, distributed in a | ||
``bin`` folder distributed alongside the compiled iOS framework. These scripts | ||
are relocatable, and will always resolve to the appropriate local system paths. | ||
By including these scripts in the bin folder that accompanies a framework, the | ||
contents of the ``sysconfig`` module becomes useful for end-users to compile | ||
their own modules. When compiling third-party Python modules for iOS, you | ||
should ensure these stub binaries are on your path. | ||
|
||
Installing Python on iOS | ||
======================== | ||
|
||
Tools for building iOS apps | ||
--------------------------- | ||
|
||
Building for iOS requires the use of Apple's Xcode tooling. It is strongly | ||
recommended that you use the most recent stable release of Xcode. This will | ||
require the use of the most (or second-most) recently released macOS version, | ||
as Apple does not maintain Xcode for older macOS versions. The Xcode Command | ||
Line Tools are not sufficient for iOS development; you need a *full* Xcode | ||
install. | ||
|
||
If you want to run your code on the iOS simulator, you'll also need to install | ||
an iOS Simulator Platform. You should be prompted to select an iOS Simulator | ||
Platform when you first run Xcode. Alternatively, you can add an iOS Simulator | ||
Platform by selecting an open the Platforms tab of the Xcode Settings panel. | ||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
.. _adding-ios: | ||
|
||
Adding iOS to an XCode project | ||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
------------------------------ | ||
|
||
Python can be added to any iOS project, using either Swift or Objective C. The | ||
following examples will use Objective C; if you are using Swift, you may find a | ||
library like `PythonKit <https://github.com/pvieito/PythonKit>`__ to be | ||
helpful. | ||
|
||
To add Python to an iOS Xcode project: | ||
|
||
1. Build or obtain a Python ``XCFramework``. See the instructions in | ||
``iOS/README.rst`` (in the CPython source distribution) for details on how | ||
to build a Python ``XCFramework``. At a minimum, you will need a build that | ||
supports `arm64-apple-ios`, plus one of either `arm64-apple-ios-simulator` | ||
or `x86_64-apple-ios-simulator`. | ||
|
||
2. Drag the ``XCframework`` into your iOS project. In the following | ||
instructions, we'll assume you've dropped the ``XCframework`` into the root | ||
of your project; however, you can use any other location that you want by | ||
adjusting paths as needed. | ||
|
||
3. Drag the ``iOS/Resources/dylib-Info-template.plist`` file into your project, | ||
and ensure it is associated with the app target. | ||
|
||
4. Add your application code as a folder in your Xcode project. In the | ||
following instructions, we'll assume that your user code is in a folder | ||
named ``app`` in the root of your project; you can use any other location by | ||
adjusting paths as needed. Ensure that this folder is associated with your | ||
app target. | ||
|
||
5. Select the app target by selecting the root node of your Xcode project, then | ||
the target name in the sidebar that appears. | ||
|
||
6. In the "General" settings, under "Frameworks, Libraries and Embedded | ||
Content", add ``Python.xcframework``, with "Embed & Sign" selected. | ||
|
||
7. In the "Build Settings" tab, modify the following: | ||
|
||
- Build Options | ||
|
||
* User script sandboxing: No | ||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* Enable Testability: Yes | ||
|
||
- Search Paths | ||
|
||
* Framework Search Paths: ``$(PROJECT_DIR)`` | ||
* Header Search Paths: ``"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"`` | ||
|
||
- Apple Clang - Warnings - All languages | ||
|
||
* Quoted Include in Framework Header: No | ||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
8. Add a build step that copies the Python standard library into your app. In | ||
the "Build Phases" tab, add a new "Run Script" build step *before* the | ||
"Embed Frameworks" step, but *after* the "Copy Bundle Resources" step. Name | ||
the step "Install Target Specific Python Standard Library", disable the | ||
"Based on dependency analysis" checkbox, and set the script content to: | ||
|
||
.. code-block:: bash | ||
|
||
set -e | ||
|
||
mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" | ||
if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then | ||
echo "Installing Python modules for iOS Simulator" | ||
rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" | ||
else | ||
echo "Installing Python modules for iOS Device" | ||
rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" | ||
fi | ||
|
||
Note that the name of the simulator "slice" in the XCframework may be | ||
different, depending the CPU architectures your ``XCFramework`` supports. | ||
|
||
9. Add a second build step that processes the binary extension modules in the | ||
standard library into "Framework" format. Add a "Run Script" build step | ||
*directly after* the one you added in step 8, named "Prepare Python Binary | ||
Modules". It should also have "Based on dependency analysis" unchecked, with | ||
the following script content: | ||
|
||
.. code-block:: bash | ||
|
||
set -e | ||
|
||
install_dylib () { | ||
INSTALL_BASE=$1 | ||
FULL_EXT=$2 | ||
|
||
# The name of the extension file | ||
EXT=$(basename "$FULL_EXT") | ||
# The location of the extension file, relative to the bundle | ||
RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} | ||
# The path to the extension file, relative to the install base | ||
PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} | ||
# The full dotted name of the extension module, constructed from the file path. | ||
FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); | ||
# A bundle identifier; not actually used, but required by Xcode framework packaging | ||
FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") | ||
# The name of the framework folder. | ||
FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" | ||
|
||
# If the framework folder doesn't exist, create it. | ||
if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then | ||
echo "Creating framework for $RELATIVE_EXT" | ||
mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" | ||
cp "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" | ||
plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" | ||
plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" | ||
fi | ||
|
||
echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" | ||
mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" | ||
# Create a placeholder .fwork file where the .so was | ||
echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork | ||
# Create a back reference to the .so file location in the framework | ||
echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" | ||
} | ||
|
||
PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") | ||
echo "Install Python $PYTHON_VER standard library extension modules..." | ||
find "$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload" -name "*.so" | while read FULL_EXT; do | ||
install_dylib python/lib/$PYTHON_VER/lib-dynload/ "$FULL_EXT" | ||
done | ||
|
||
# Clean up dylib template | ||
rm -f "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" | ||
|
||
echo "Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." | ||
find "$CODESIGNING_FOLDER_PATH/Frameworks" -name "*.framework" -exec /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "{}" \; | ||
|
||
10. Add Objective C code to initialize and use a Python interpreter in embedded | ||
mode. You should ensure that: | ||
|
||
* :c:member:`UTF-8 mode <PyPreConfig.utf8_mode>` is *enabled*; | ||
* :c:member:`Buffered stdio <PyConfig.buffered_stdio>` is *disabled*; | ||
* :c:member:`Writing bytecode <PyConfig.write_bytecode>` is *disabled*; | ||
* :c:member:`Signal handlers <PyConfig.install_signal_handlers>` are *enabled*; | ||
* ``PYTHONHOME`` for the interpreter is configured to point at the | ||
``python`` subfolder of your app's bundle; and | ||
* The ``PYTHONPATH`` for the interpreter includes: | ||
|
||
- the ``python/lib/python3.X`` subfolder of your app's bundle, | ||
- the ``python/lib/python3.X/lib-dynload`` subfolder of your app's bundle, and | ||
- the ``app`` subfolder of your app's bundle | ||
|
||
Your app's bundle location can be determined using ``[[NSBundle mainBundle] | ||
resourcePath]``. | ||
|
||
Steps 8, 9 and 10 of these instructions assume that you have a single folder of | ||
pure Python application code, named ``app``. If you have third-party binary | ||
modules in your app, some additional steps will be required: | ||
|
||
* You need to ensure that any folders containing third-party binaries are | ||
either associated with the app target, or copied in as part of step 8. Step 8 | ||
should also purge any binaries that are not appropriate for the platform a | ||
specific build is targetting (i.e., delete any device binaries if you're | ||
building app app targeting the simulator). | ||
|
||
* Any folders that contain third-party binaries must be processed into | ||
framework form by step 9. The invocation of ``install_dylib`` that processes | ||
the ``lib-dynload`` folder can be copied and adapted for this purpose. | ||
|
||
* If you're using a separate folder for third-party packages, ensure that folder | ||
is included as part of the ``PYTHONPATH`` configuration in step 10. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.