https://github.com/python/cpython/commit/465b3b80f98e0b1b3c116cab6f00cb9f4311c3cf
commit: 465b3b80f98e0b1b3c116cab6f00cb9f4311c3cf
branch: main
author: Clément Péron <[email protected]>
committer: freakboy3742 <[email protected]>
date: 2026-09-08T07:54:41Z
summary:

gh-156109: Allow static, non-framework iOS builds (#156110)

Allows a static build of iOS for users that want to build and embed a libPython
into an iOS binary. This configuration can't use shared modules or binary 
wheels, but it can be useful under some circumstances, so there's no reason to
explicitly exclude it.

Co-authored-by: Claude Opus 5 <[email protected]>
Co-authored-by: Russell Keith-Magee <[email protected]>

files:
A Misc/NEWS.d/next/Build/2026-08-20-13-32-54.gh-issue-156109.ZTJHi9.rst
M Doc/using/configure.rst
M Doc/using/ios.rst
M Platforms/Apple/iOS/README.md
M configure
M configure.ac

diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst
index 8b4940ceb9521a7..88b5f35a7967967 100644
--- a/Doc/using/configure.rst
+++ b/Doc/using/configure.rst
@@ -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
 -----------------------
diff --git a/Doc/using/ios.rst b/Doc/using/ios.rst
index 31d9e2f2c816e73..80b9b3c3a07f063 100644
--- a/Doc/using/ios.rst
+++ b/Doc/using/ios.rst
@@ -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
 ---------------------------
 
diff --git 
a/Misc/NEWS.d/next/Build/2026-08-20-13-32-54.gh-issue-156109.ZTJHi9.rst 
b/Misc/NEWS.d/next/Build/2026-08-20-13-32-54.gh-issue-156109.ZTJHi9.rst
new file mode 100644
index 000000000000000..4b8c1c197ddf5db
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2026-08-20-13-32-54.gh-issue-156109.ZTJHi9.rst
@@ -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.
diff --git a/Platforms/Apple/iOS/README.md b/Platforms/Apple/iOS/README.md
index faeeead1df03a2e..4bf083fb25ea065 100644
--- a/Platforms/Apple/iOS/README.md
+++ b/Platforms/Apple/iOS/README.md
@@ -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`
 
@@ -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:
 ```
@@ -216,6 +223,68 @@ 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.
+
+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 \
+    --prefix=/path/to/install/dir \
+    --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`.
+Unlike a framework build, `--prefix` is not set for you, so specify it
+explicitly - otherwise `libpython` will be installed into `/usr/local`.
+
+#### 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
diff --git a/configure b/configure
index 423e3724cfc9131..ae3f0450dc7af1b 100755
--- a/configure
+++ b/configure
@@ -4441,29 +4441,25 @@ then :
 
        case $enableval in
        no)
-               case $ac_sys_system in
-                       iOS) as_fn_error $? "iOS builds must use 
--enable-framework" "$LINENO" 5 ;;
-                       *)
-                               PYTHONFRAMEWORK=
-                               PYTHONFRAMEWORKDIR=no-framework
-                               PYTHONFRAMEWORKPREFIX=
-                               PYTHONFRAMEWORKINSTALLDIR=
-                               PYTHONFRAMEWORKINSTALLNAMEPREFIX=
-                               RESSRCDIR=
-                               FRAMEWORKINSTALLFIRST=
-                               FRAMEWORKINSTALLLAST=
-                               FRAMEWORKALTINSTALLFIRST=
-                               FRAMEWORKALTINSTALLLAST=
-                               FRAMEWORKPYTHONW=
-                               INSTALLTARGETS="commoninstall bininstall 
maninstall"
-
-                               if test "x${prefix}" = "xNONE"; then
-                                       
FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-                               else
-                                       FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-                               fi
-                               enable_framework=
-               esac
+               PYTHONFRAMEWORK=
+               PYTHONFRAMEWORKDIR=no-framework
+               PYTHONFRAMEWORKPREFIX=
+               PYTHONFRAMEWORKINSTALLDIR=
+               PYTHONFRAMEWORKINSTALLNAMEPREFIX=
+               RESSRCDIR=
+               FRAMEWORKINSTALLFIRST=
+               FRAMEWORKINSTALLLAST=
+               FRAMEWORKALTINSTALLFIRST=
+               FRAMEWORKALTINSTALLLAST=
+               FRAMEWORKPYTHONW=
+               INSTALLTARGETS="commoninstall bininstall maninstall"
+
+               if test "x${prefix}" = "xNONE"; then
+                       FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+               else
+                       FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
+               fi
+               enable_framework=
                ;;
        *)
                PYTHONFRAMEWORKPREFIX="${enableval}"
@@ -4558,28 +4554,24 @@ then :
 
 else case e in #(
   e)
-       case $ac_sys_system in
-               iOS) as_fn_error $? "iOS builds must use --enable-framework" 
"$LINENO" 5 ;;
-               *)
-                       PYTHONFRAMEWORK=
-                       PYTHONFRAMEWORKDIR=no-framework
-                       PYTHONFRAMEWORKPREFIX=
-                       PYTHONFRAMEWORKINSTALLDIR=
-                       PYTHONFRAMEWORKINSTALLNAMEPREFIX=
-                       RESSRCDIR=
-                       FRAMEWORKINSTALLFIRST=
-                       FRAMEWORKINSTALLLAST=
-                       FRAMEWORKALTINSTALLFIRST=
-                       FRAMEWORKALTINSTALLLAST=
-                       FRAMEWORKPYTHONW=
-                       INSTALLTARGETS="commoninstall bininstall maninstall"
-                       if test "x${prefix}" = "xNONE" ; then
-                               FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-                       else
-                               FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-                       fi
-                       enable_framework=
-       esac
+       PYTHONFRAMEWORK=
+       PYTHONFRAMEWORKDIR=no-framework
+       PYTHONFRAMEWORKPREFIX=
+       PYTHONFRAMEWORKINSTALLDIR=
+       PYTHONFRAMEWORKINSTALLNAMEPREFIX=
+       RESSRCDIR=
+       FRAMEWORKINSTALLFIRST=
+       FRAMEWORKINSTALLLAST=
+       FRAMEWORKALTINSTALLFIRST=
+       FRAMEWORKALTINSTALLLAST=
+       FRAMEWORKPYTHONW=
+       INSTALLTARGETS="commoninstall bininstall maninstall"
+       if test "x${prefix}" = "xNONE" ; then
+               FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+       else
+               FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
+       fi
+       enable_framework=
  ;;
 esac
 fi
@@ -8027,6 +8019,9 @@ printf "%s\n" "#define Py_ENABLE_SHARED 1" >>confdefs.h
       
RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
       ;;
     iOS)
+      if test -z "$PYTHONFRAMEWORK"; then
+        as_fn_error $? "iOS shared builds must use --enable-framework; an iOS 
app can only load a signed framework, never a bare dylib" "$LINENO" 5
+      fi
       LDLIBRARY='libpython$(LDVERSION).dylib'
       ;;
     AIX*)
@@ -14540,7 +14535,11 @@ printf "%s\n" "#define THREAD_STACK_SIZE 
0x$stack_size" >>confdefs.h
                        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";;
@@ -28288,8 +28287,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
 
@@ -35111,6 +35110,13 @@ fi
 
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TEST_MODULES" >&5
 printf "%s\n" "$TEST_MODULES" >&6; }
+if test "$ac_sys_system" = "iOS" && test -z "$PYTHONFRAMEWORK"
+then :
+  if test "$TEST_MODULES" != "no"
+then :
+  as_fn_error $? "iOS non-framework builds must use --disable-test-modules" 
"$LINENO" 5
+fi
+fi
 
 
 # Check for --with-build-details-suffix
@@ -35401,6 +35407,14 @@ case $host_cpu in #(
 esac
 
 
+if test "$ac_sys_system" = "iOS" && test -z "$PYTHONFRAMEWORK"
+then :
+  if test "$MODULE_BUILDTYPE" != "static"
+then :
+  as_fn_error $? "iOS non-framework builds must use MODULE_BUILDTYPE=static" 
"$LINENO" 5
+fi
+fi
+
 
 MODULE_BLOCK=
 
diff --git a/configure.ac b/configure.ac
index d754522bb029be3..1c42900cb975c35 100644
--- a/configure.ac
+++ b/configure.ac
@@ -578,29 +578,25 @@ 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
-                               PYTHONFRAMEWORKPREFIX=
-                               PYTHONFRAMEWORKINSTALLDIR=
-                               PYTHONFRAMEWORKINSTALLNAMEPREFIX=
-                               RESSRCDIR=
-                               FRAMEWORKINSTALLFIRST=
-                               FRAMEWORKINSTALLLAST=
-                               FRAMEWORKALTINSTALLFIRST=
-                               FRAMEWORKALTINSTALLLAST=
-                               FRAMEWORKPYTHONW=
-                               INSTALLTARGETS="commoninstall bininstall 
maninstall"
-
-                               if test "x${prefix}" = "xNONE"; then
-                                       
FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-                               else
-                                       FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-                               fi
-                               enable_framework=
-               esac
+               PYTHONFRAMEWORK=
+               PYTHONFRAMEWORKDIR=no-framework
+               PYTHONFRAMEWORKPREFIX=
+               PYTHONFRAMEWORKINSTALLDIR=
+               PYTHONFRAMEWORKINSTALLNAMEPREFIX=
+               RESSRCDIR=
+               FRAMEWORKINSTALLFIRST=
+               FRAMEWORKINSTALLLAST=
+               FRAMEWORKALTINSTALLFIRST=
+               FRAMEWORKALTINSTALLLAST=
+               FRAMEWORKPYTHONW=
+               INSTALLTARGETS="commoninstall bininstall maninstall"
+
+               if test "x${prefix}" = "xNONE"; then
+                       FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+               else
+                       FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
+               fi
+               enable_framework=
                ;;
        *)
                PYTHONFRAMEWORKPREFIX="${enableval}"
@@ -688,28 +684,24 @@ AC_ARG_ENABLE([framework],
                        esac
                esac
        ],[
-       case $ac_sys_system in
-               iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;;
-               *)
-                       PYTHONFRAMEWORK=
-                       PYTHONFRAMEWORKDIR=no-framework
-                       PYTHONFRAMEWORKPREFIX=
-                       PYTHONFRAMEWORKINSTALLDIR=
-                       PYTHONFRAMEWORKINSTALLNAMEPREFIX=
-                       RESSRCDIR=
-                       FRAMEWORKINSTALLFIRST=
-                       FRAMEWORKINSTALLLAST=
-                       FRAMEWORKALTINSTALLFIRST=
-                       FRAMEWORKALTINSTALLLAST=
-                       FRAMEWORKPYTHONW=
-                       INSTALLTARGETS="commoninstall bininstall maninstall"
-                       if test "x${prefix}" = "xNONE" ; then
-                               FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-                       else
-                               FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
-                       fi
-                       enable_framework=
-       esac
+       PYTHONFRAMEWORK=
+       PYTHONFRAMEWORKDIR=no-framework
+       PYTHONFRAMEWORKPREFIX=
+       PYTHONFRAMEWORKINSTALLDIR=
+       PYTHONFRAMEWORKINSTALLNAMEPREFIX=
+       RESSRCDIR=
+       FRAMEWORKINSTALLFIRST=
+       FRAMEWORKINSTALLLAST=
+       FRAMEWORKALTINSTALLFIRST=
+       FRAMEWORKALTINSTALLLAST=
+       FRAMEWORKPYTHONW=
+       INSTALLTARGETS="commoninstall bininstall maninstall"
+       if test "x${prefix}" = "xNONE" ; then
+               FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
+       else
+               FRAMEWORKUNIXTOOLSPREFIX="${prefix}"
+       fi
+       enable_framework=
 ])
 AC_SUBST([PYTHONFRAMEWORK])
 AC_SUBST([PYTHONFRAMEWORKIDENTIFIER])
@@ -1676,6 +1668,9 @@ if test $enable_shared = "yes"; then
       
RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}
       ;;
     iOS)
+      if test -z "$PYTHONFRAMEWORK"; then
+        AC_MSG_ERROR([iOS shared builds must use --enable-framework; an iOS 
app can only load a signed framework, never a bare dylib])
+      fi
       LDLIBRARY='libpython$(LDVERSION).dylib'
       ;;
     AIX*)
@@ -3836,7 +3831,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";;
@@ -6778,8 +6777,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
 
@@ -8169,6 +8168,11 @@ AC_ARG_ENABLE([test-modules],
   AS_VAR_IF([enable_test_modules], [yes], [TEST_MODULES=yes], 
[TEST_MODULES=no])
 ], [TEST_MODULES=yes])
 AC_MSG_RESULT([$TEST_MODULES])
+dnl Some test modules can only be built as shared libraries (see
+dnl Modules/Setup.stdlib.in), which a non-framework iOS build cannot do.
+AS_IF([test "$ac_sys_system" = "iOS" && test -z "$PYTHONFRAMEWORK"],
+  [AS_IF([test "$TEST_MODULES" != "no"],
+    [AC_MSG_ERROR([iOS non-framework builds must use 
--disable-test-modules])])])
 AC_SUBST([TEST_MODULES])
 
 # Check for --with-build-details-suffix
@@ -8408,6 +8412,12 @@ AS_CASE([$host_cpu],
 )
 AC_SUBST([MODULE_BUILDTYPE])
 
+dnl A non-framework iOS build has no framework for a shared extension module
+dnl to link against, so every extension module must be built into libpython.
+AS_IF([test "$ac_sys_system" = "iOS" && test -z "$PYTHONFRAMEWORK"],
+  [AS_IF([test "$MODULE_BUILDTYPE" != "static"],
+    [AC_MSG_ERROR([iOS non-framework builds must use 
MODULE_BUILDTYPE=static])])])
+
 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"])])

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to