Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Doc/using/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,16 @@ See :source:`Platforms/Apple/iOS/README.md`.

Specify the name for the framework (default: ``Python``).

An iOS build configured without ``--enable-framework`` produces a static
``libpython``, for embedding directly in an app binary. Such a build cannot
load extension modules at runtime, and so does not support binary wheels; it
requires ``MODULE_BUILDTYPE=static`` and :option:`--disable-test-modules`, and
rejects :option:`--enable-shared`. See
:source:`Platforms/Apple/iOS/README.md` for the full list of limitations.

.. versionadded:: 3.16
iOS builds may be configured without a framework.


Cross Compiling Options
-----------------------
Expand Down
7 changes: 7 additions & 0 deletions Doc/using/ios.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ should ensure these stub binaries are on your path.
Installing Python on iOS
========================

The official iOS release artefact is a framework build, distributed as an
``XCFramework``; this is the configuration described in the rest of this
document, and the only one that supports binary extension modules. Static
builds, where ``libpython`` and every extension module are linked directly into
the app binary, are also possible, with limitations; see
:source:`Platforms/Apple/iOS/README.md` for details.

Tools for building iOS apps
---------------------------

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
iOS builds may now be configured without a framework, producing a static
``libpython`` for embedding in an app binary. Such a build cannot load extension
modules at runtime, and requires ``MODULE_BUILDTYPE=static`` and
``--disable-test-modules`` to be requested explicitly. A shared iOS build must
still be a framework build.
80 changes: 75 additions & 5 deletions Platforms/Apple/iOS/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,13 @@ Python build for a single framework, the following options are available.
installed. If `DIR` is not specified, the framework will be installed into
a subdirectory of the `iOS/Frameworks` folder.

This argument *must* be provided when configuring iOS builds. iOS does not
support non-framework builds.
This argument is required for any iOS build that will be distributed, and
for any build that needs to load binary extension modules.

Omitting it builds a static `libpython` for embedding directly in an app
binary, instead of a `Python.framework`. That configuration comes with
significant restrictions; see [Building a static
Python](#building-a-static-python) below.

* `--with-framework-name=NAME`

Expand All @@ -113,9 +118,11 @@ framework to contain non-library content, so the iOS build will produce a
The `lib` folder will be needed at runtime to support the Python library.

If you want to use Python in a real iOS project, you need to produce multiple
`Python.framework` builds, one for each ABI and architecture. iOS builds of
Python *must* be constructed as framework builds. To support this, you must
provide the `--enable-framework` flag when configuring the build. The build
`Python.framework` builds, one for each ABI and architecture. Unless you are
statically linking Python into your app (see [Building a static
Python](#building-a-static-python) below), iOS builds of Python *must* be
constructed as framework builds. To support this, you must provide the
`--enable-framework` flag when configuring the build. The build
also requires the use of cross-compilation. The minimal commands for building
Python for the ARM64 iOS simulator will look something like:
```
Expand Down Expand Up @@ -216,6 +223,69 @@ target, provide the version number as part of the `--host` argument - for
example, `--host=arm64-apple-ios15.4-simulator` would compile an ARM64
simulator build with a deployment target of 15.4.

### Building a static Python

The official iOS release artefact is a framework build. However, if you are
embedding Python in an app that links `libpython` at compile time, you can
instead build a static `libpython3.x.a`, and link that archive directly into
your app binary.

The App Store requirement that binary modules be packaged as signed frameworks
does not apply to a static build, because a static build loads nothing at
runtime; but for the same reason, this configuration cannot use *any* binary
module that isn't compiled into the app binary. The restrictions that follow
from that must be opted into explicitly at configure time:

* `MODULE_BUILDTYPE=static` is required. There is no framework for a shared
extension module to link against, so every extension module, including the
ones in the standard library, must be linked into `libpython`.

* `--disable-test-modules` is required. Some test modules must be compiled as
shared libraries (see `Modules/Setup.stdlib.in`), so they cannot be built in
this configuration at all.

A non-framework build is selected by omitting `--enable-framework`;
`--disable-framework` is accepted as an explicit spelling of the same thing.
Such a build is also static by default, as `--enable-shared` is off unless
requested; `--enable-shared` without a framework is rejected, since an iOS app
can only load a signed framework, never a bare dylib.

The minimal commands for a static build targeting ARM64 iOS devices are then:
```
export PATH="$(pwd)/Platforms/Apple/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
./configure \
--disable-framework \
--disable-test-modules \
MODULE_BUILDTYPE=static \
--host=arm64-apple-ios \
--build=arm64-apple-darwin \
--with-build-python=/path/to/python.exe
make
make install
```
This produces a `libpython3.x.a` containing the interpreter and the standard
library's extension modules; `make install` installs that archive, along with
the standard library's Python source, into the location given by `--prefix`.

#### Limitations of a static build

* **Binary wheels cannot be used.** There is no `libpython` dylib for a
third-party extension module to link against, and a static Python has nothing
to `dlopen` in any case. Pure Python wheels work as normal; any package with a
C extension must be compiled into the app binary alongside `libpython`.

* **The standard library's extension modules are not loadable modules.** They
live in the archive, not in `.framework` bundles in the app's `Frameworks`
folder, so the packaging described in
[Using Python on iOS](https://docs.python.org/3/using/ios.html) does not apply
to them.

* **The test suite cannot be run as-is**, as the test modules are not built.

* This configuration is not covered by the `Platforms/Apple` build script, nor
by CPython's CI. It is not the configuration used to produce official
releases.

## Testing Python on iOS

### Testing a multi-architecture framework
Expand Down
30 changes: 25 additions & 5 deletions configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 20 additions & 5 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,6 @@ AC_ARG_ENABLE([framework],
case $enableval in
no)
case $ac_sys_system in
iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;;
*)
PYTHONFRAMEWORK=
PYTHONFRAMEWORKDIR=no-framework
Expand Down Expand Up @@ -689,7 +688,6 @@ AC_ARG_ENABLE([framework],
esac
],[
case $ac_sys_system in
iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;;
*)
PYTHONFRAMEWORK=
PYTHONFRAMEWORKDIR=no-framework
Expand Down Expand Up @@ -1695,6 +1693,10 @@ else # shared is disabled
fi
AC_MSG_RESULT([$LDLIBRARY])

if test "$ac_sys_system" = "iOS" && test "$PY_ENABLE_SHARED" = 1 && test -z "$PYTHONFRAMEWORK"; then
AC_MSG_ERROR([iOS builds must use --enable-framework; --enable-shared cannot be used without a framework, as an iOS app can only load a signed framework])
fi

# HOSTRUNNER - Program to run CPython for the host platform
AC_MSG_CHECKING([HOSTRUNNER])
if test -z "$HOSTRUNNER"
Expand Down Expand Up @@ -3836,7 +3838,11 @@ then
fi
LINKFORSHARED="$LINKFORSHARED"
elif test $ac_sys_system = "iOS"; then
LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)'
LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED"

if test "$enable_framework"; then
LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)'
fi
fi
;;
OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";;
Expand Down Expand Up @@ -6761,8 +6767,8 @@ if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MA
LIBPYTHON="-lpython${VERSION}${ABIFLAGS}"
fi

# On iOS the shared libraries must be linked with the Python framework
if test "$ac_sys_system" = "iOS"; then
# On iOS the shared libraries must be linked with the framework, when built
if test "$ac_sys_system" = "iOS" && test "$enable_framework"; then
MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)"
fi

Expand Down Expand Up @@ -8391,6 +8397,15 @@ AS_CASE([$host_cpu],
)
AC_SUBST([MODULE_BUILDTYPE])

dnl A non-framework iOS build can neither link nor load shared extension
dnl modules; some test modules can only be shared (see Modules/Setup.stdlib.in).
AS_IF([test "$ac_sys_system" = "iOS" && test -z "$PYTHONFRAMEWORK"], [
AS_IF([test "$MODULE_BUILDTYPE" != "static"],
[AC_MSG_ERROR([iOS builds must use --enable-framework; a non-framework build cannot build shared extension modules, and requires MODULE_BUILDTYPE=static])])
AS_IF([test "$TEST_MODULES" != "no"],
[AC_MSG_ERROR([iOS builds must use --enable-framework; a non-framework build cannot build the shared test modules, and requires --disable-test-modules])])
])

dnl _MODULE_BLOCK_ADD([VAR], [VALUE])
dnl internal: adds $1=quote($2) to MODULE_BLOCK
AC_DEFUN([_MODULE_BLOCK_ADD], [AS_VAR_APPEND([MODULE_BLOCK], ["$1=_AS_QUOTE([$2])$as_nl"])])
Expand Down
Loading