diff --git a/.github/workflows/build-python-version.yml b/.github/workflows/build-python-version.yml
index 8dddee5..3a65c8a 100644
--- a/.github/workflows/build-python-version.yml
+++ b/.github/workflows/build-python-version.yml
@@ -14,9 +14,9 @@ on:
required: true
type: choice
options:
- - 3.12.14
- - 3.13.15
- - 3.14.7
+ - 3.12.15
+ - 3.13.16
+ - 3.14.8
env:
PYTHON_VERSION: ${{ inputs.python_version || github.event.inputs.python_version }}
diff --git a/darwin/build_ios.py b/darwin/build_ios.py
index 8087272..0b3b909 100644
--- a/darwin/build_ios.py
+++ b/darwin/build_ios.py
@@ -38,6 +38,11 @@
_LOG = None # open file handle; set via set_log()
+# Minimum iOS version for every slice. Xcode 27 rejects deployment targets below 15.0,
+# and Flutter 3.47 / Flet 1.1.0 use the same floor. CPython's configure hardcodes
+# 13.0 as its default (see build()), so it's rewritten to this value.
+IOS_DEPLOYMENT_TARGET = "15.0"
+
def set_log(path: Path) -> None:
"""Tee all subsequent run()/_emit output to a build log file."""
@@ -139,7 +144,7 @@ def build(
# and upstream's iOS PY_STDLIB_MOD_SET_NA (3.14+); a no-op if the string isn't
# present.
configure = src / "configure"
- configure.write_text(
+ configure_text = (
configure.read_text()
.replace(
"py_cv_module__multiprocessing=n/a", "py_cv_module__multiprocessing=yes"
@@ -147,6 +152,21 @@ def build(
.replace("py_cv_module__posixshmem=n/a", "py_cv_module__posixshmem=yes")
)
+ # Raise the iOS deployment target. The Apple tool configures an unversioned
+ # --host=arm64-apple-ios, so configure falls back to its hardcoded 13.0 default
+ # (and overwrites IPHONEOS_DEPLOYMENT_TARGET from the environment), then derives
+ # -mios[-simulator]-version-min, the framework's MinimumOSVersion and the
+ # sysconfig platform tag from it. The same line exists in 3.14+ upstream, 3.13
+ # upstream and the 3.12 back-port patch; fail loudly if upstream ever changes it.
+ default_target = "IPHONEOS_DEPLOYMENT_TARGET:=13.0}"
+ if configure_text.count(default_target) != 1:
+ raise SystemExit(f"Expected exactly one {default_target!r} in {configure}")
+ configure.write_text(
+ configure_text.replace(
+ default_target, f"IPHONEOS_DEPLOYMENT_TARGET:={IOS_DEPLOYMENT_TARGET}}}"
+ )
+ )
+
# iOS's SDK declares pipe2/dup3 in headers but doesn't provide them, and on recent
# SDKs configure mis-detects them (esp. the simulator), then the build fails with
# "call to undeclared function 'pipe2'". The Apple tool doesn't expose configure
@@ -182,8 +202,8 @@ def _strip(paths: list) -> None:
def _embed_ios_version_in_host_triple(sysconfig_path: Path) -> None:
"""Rewrite HOST_GNU_TYPE in a _sysconfigdata file to embed IPHONEOS_DEPLOYMENT_TARGET
- (e.g. ``aarch64-apple-ios`` -> ``aarch64-apple-ios13.0``,
- ``...-ios-simulator`` -> ``...-ios13.0-simulator``)."""
+ (e.g. ``aarch64-apple-ios`` -> ``aarch64-apple-ios15.0``,
+ ``...-ios-simulator`` -> ``...-ios15.0-simulator``)."""
text = sysconfig_path.read_text()
m = re.search(r"'IPHONEOS_DEPLOYMENT_TARGET':\s*'([^']+)'", text)
if not m:
@@ -235,11 +255,24 @@ def reshape(version: str, short: str, src: Path, root: Path) -> None:
# the _sysconfigdata lacks the iOS deployment version. mobile-forge's crossenv recovers
# the iOS release by splitting HOST_GNU_TYPE (when _PYTHON_HOST_PLATFORM isn't set), so a
# version-less triple yields an empty release and pip's iOS-tag code crashes. Embed the
- # version (e.g. aarch64-apple-ios -> aarch64-apple-ios13.0), matching beeware. Fixing the
+ # version (e.g. aarch64-apple-ios -> aarch64-apple-ios15.0), matching beeware. Fixing the
# xcframework copies here means the dart-install + platform-config copies inherit it.
for sc in dst_xcf.glob(f"*/lib-*/{pyver}/_sysconfigdata__ios_*.py"):
_embed_ios_version_in_host_triple(sc)
+ # The Apple tool also ships its testbed's dylib Info.plist template inside the
+ # xcframework (build/iOS-dylib-Info-template.plist), with MinimumOSVersion
+ # hardcoded to 13.0. Keep it consistent with the slices.
+ dylib_plist_template = dst_xcf / "build" / "iOS-dylib-Info-template.plist"
+ if dylib_plist_template.is_file():
+ dylib_plist_template.write_text(
+ re.sub(
+ r"(MinimumOSVersion\s*)[^<]*()",
+ rf"\g<1>{IOS_DEPLOYMENT_TARGET}\g<2>",
+ dylib_plist_template.read_text(),
+ )
+ )
+
shared_stdlib = dst_xcf / "lib" / pyver
if not (shared_stdlib / "os.py").exists():
raise SystemExit(f"Expected shared stdlib at {shared_stdlib}")
@@ -277,7 +310,11 @@ def reshape(version: str, short: str, src: Path, root: Path) -> None:
support = root / "support" / short / "iOS"
support.mkdir(parents=True, exist_ok=True)
- lines = [f"Python version: {version}", "Build: custom", "Min iOS version: 13.0"]
+ lines = [
+ f"Python version: {version}",
+ "Build: custom",
+ f"Min iOS version: {IOS_DEPLOYMENT_TARGET}",
+ ]
if deps:
lines.append("---------------------")
lines += [f"{name}: {ver}" for name, ver in sorted(deps.items())]
diff --git a/darwin/build_macos.py b/darwin/build_macos.py
index c2ba5ac..d7651f2 100644
--- a/darwin/build_macos.py
+++ b/darwin/build_macos.py
@@ -37,7 +37,7 @@
DEFAULT_XZ_VERSION = "5.8.3"
# zstd backs the _zstd module added in CPython 3.14 (PEP 784); built only for 3.14+.
DEFAULT_ZSTD_VERSION = "1.5.7"
-DEFAULT_DEPLOYMENT_TARGET = "11.0"
+DEFAULT_DEPLOYMENT_TARGET = "12.0"
ARCHES = ("x86_64", "arm64")
# A minimal PATH keeps Homebrew / user Python installs from leaking into the build.
diff --git a/darwin/ios_patches/3.12/Python.patch b/darwin/ios_patches/3.12/Python.patch
index 4ccfda8..9583cb1 100644
--- a/darwin/ios_patches/3.12/Python.patch
+++ b/darwin/ios_patches/3.12/Python.patch
@@ -9282,6 +9282,53 @@ index 7ca3dc62c01..a1e28beff42 100644
-rm -f python-config.py python-config
# Make things extra clean, before making a distribution:
+diff --git a/Modules/_ssl.c b/Modules/_ssl.c
+index 91ca136..364cf9e 100644
+--- a/Modules/_ssl.c
++++ b/Modules/_ssl.c
+@@ -1339,14 +1339,14 @@ _get_peer_alt_names (_sslmodulestate *state, X509 *certificate) {
+ }
+ PyTuple_SET_ITEM(t, 0, v);
+
+- if (name->d.ip->length == 4) {
+- unsigned char *p = name->d.ip->data;
++ if (ASN1_STRING_length(name->d.ip) == 4) {
++ const unsigned char *p = ASN1_STRING_get0_data(name->d.ip);
+ v = PyUnicode_FromFormat(
+ "%d.%d.%d.%d",
+ p[0], p[1], p[2], p[3]
+ );
+- } else if (name->d.ip->length == 16) {
+- unsigned char *p = name->d.ip->data;
++ } else if (ASN1_STRING_length(name->d.ip) == 16) {
++ const unsigned char *p = ASN1_STRING_get0_data(name->d.ip);
+ v = PyUnicode_FromFormat(
+ "%X:%X:%X:%X:%X:%X:%X:%X",
+ p[0] << 8 | p[1],
+@@ -1477,8 +1477,9 @@ _get_aia_uri(X509 *certificate, int nid) {
+ continue;
+ }
+ uri = ad->location->d.uniformResourceIdentifier;
+- ostr = PyUnicode_FromStringAndSize((char *)uri->data,
+- uri->length);
++ ostr = PyUnicode_FromStringAndSize(
++ (const char *)ASN1_STRING_get0_data(uri),
++ ASN1_STRING_length(uri));
+ if (ostr == NULL) {
+ goto fail;
+ }
+@@ -1544,8 +1545,9 @@ _get_crl_dp(X509 *certificate) {
+ continue;
+ }
+ uri = gn->d.uniformResourceIdentifier;
+- ouri = PyUnicode_FromStringAndSize((char *)uri->data,
+- uri->length);
++ ouri = PyUnicode_FromStringAndSize(
++ (const char *)ASN1_STRING_get0_data(uri),
++ ASN1_STRING_length(uri));
+ if (ouri == NULL)
+ goto done;
+
diff --git a/Modules/getpath.c b/Modules/getpath.c
index 0a310000751..83a2bc469ae 100644
--- a/Modules/getpath.c
diff --git a/darwin/ios_patches/3.13/Python.patch b/darwin/ios_patches/3.13/Python.patch
index 60e5a23..3c06b9e 100644
--- a/darwin/ios_patches/3.13/Python.patch
+++ b/darwin/ios_patches/3.13/Python.patch
@@ -7102,16 +7102,16 @@ index 0df4751..cf32adf 100755
# checks for alternative programs
-@@ -4709,6 +4907,16 @@ case $ac_sys_system in #(
+@@ -4714,6 +4912,16 @@ case $host in #(
as_fn_append CFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"
as_fn_append LDFLAGS " -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"
;; #(
-+ tvOS) :
++ *-apple-tvos*) :
+
+ as_fn_append CFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"
+ as_fn_append LDFLAGS " -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"
+ ;; #(
-+ watchOS) :
++ *-apple-watchos*) :
+
+ as_fn_append CFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"
+ as_fn_append LDFLAGS " -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"
@@ -7785,7 +7785,7 @@ index a016a43..698553b 100644
# checks for alternative programs
-@@ -944,11 +1127,19 @@ AS_CASE([$host],
+@@ -944,15 +1127,25 @@ AS_CASE([$host],
],
)
@@ -7793,17 +7793,23 @@ index a016a43..698553b 100644
+dnl Add the compiler flag for the iOS/tvOS/watchOS minimum supported OS
+dnl version. visionOS doesn't use an explicit -mxros-version-min option -
+dnl it encodes the min version into the target triple.
- AS_CASE([$ac_sys_system],
- [iOS], [
+ AS_CASE([$host],
+ [*-apple-ios*-simulator], [
+ AS_VAR_APPEND([CFLAGS], [" -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"])
+ AS_VAR_APPEND([LDFLAGS], [" -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"])
+ ],
+ [*-apple-ios*], [
AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"])
AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET}"])
-+ ],[tvOS], [
+ ],
++ [*-apple-tvos*], [
+ AS_VAR_APPEND([CFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"])
+ AS_VAR_APPEND([LDFLAGS], [" -mtvos-version-min=${TVOS_DEPLOYMENT_TARGET}"])
-+ ],[watchOS], [
++ ],
++ [*-apple-watchos*], [
+ AS_VAR_APPEND([CFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"])
+ AS_VAR_APPEND([LDFLAGS], [" -mwatchos-version-min=${WATCHOS_DEPLOYMENT_TARGET}"])
- ],
++ ],
)
@@ -1136,6 +1327,9 @@ AC_MSG_CHECKING([for multiarch])
diff --git a/darwin/ios_patches/README.md b/darwin/ios_patches/README.md
index a6e9d3d..750d15d 100644
--- a/darwin/ios_patches/README.md
+++ b/darwin/ios_patches/README.md
@@ -35,5 +35,13 @@ When that happens:
built nor shipped — that is exactly why the 3.12 patch here has had its
`Lib/test/test_genericpath.py` hunk removed for 3.12.13).
+The 3.12 patch also carries one fix that isn't about iOS: `Modules/_ssl.c` reads
+`ASN1_STRING` fields through `ASN1_STRING_length()` / `ASN1_STRING_get0_data()` instead of
+the struct members, as 3.13+ does upstream. OpenSSL 4 made `ASN1_STRING` opaque, and the
+native build-python that the Apple tool compiles first picks up whatever OpenSSL Homebrew
+links (`openssl@4` on current GitHub macOS runners), so without it the 3.12 build fails in
+`_ssl.c`. The accessors exist since OpenSSL 1.1.0, so the iOS slices (OpenSSL 3.x) are
+unaffected.
+
A minor-version bump (e.g. adding 3.15) means refreshing from the corresponding upstream
back-port branch.
diff --git a/darwin/xcframework_utils.sh b/darwin/xcframework_utils.sh
index 15545d0..51663fc 100644
--- a/darwin/xcframework_utils.sh
+++ b/darwin/xcframework_utils.sh
@@ -93,7 +93,7 @@ create_plist() {
DTXcodeBuild
$xcode_build
MinimumOSVersion
- 13.0
+ 15.0
UIDeviceFamily
1
diff --git a/manifest.json b/manifest.json
index c00ae02..9226f9d 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,26 +1,26 @@
{
"default_python_version": "3.14",
- "dart_bridge_version": "1.10.0",
+ "dart_bridge_version": "1.11.0",
"pythons": {
"3.12": {
- "full_version": "3.12.14",
- "standalone_release_date": "20260901",
+ "full_version": "3.12.15",
+ "standalone_release_date": "20261001",
"pyodide_version": "0.27.8",
"pyodide_platform_tag": "pyodide-2024.0-wasm32",
"android_abis": ["arm64-v8a", "x86_64", "armeabi-v7a"],
"prerelease": false
},
"3.13": {
- "full_version": "3.13.15",
- "standalone_release_date": "20260901",
+ "full_version": "3.13.16",
+ "standalone_release_date": "20261001",
"pyodide_version": "0.29.5",
"pyodide_platform_tag": "pyemscripten-2025.0-wasm32",
"android_abis": ["arm64-v8a", "x86_64", "armeabi-v7a"],
"prerelease": false
},
"3.14": {
- "full_version": "3.14.7",
- "standalone_release_date": "20260901",
+ "full_version": "3.14.8",
+ "standalone_release_date": "20261001",
"pyodide_version": "314.0.7",
"pyodide_platform_tag": "pyemscripten-2026.0-wasm32",
"android_abis": ["arm64-v8a", "x86_64", "armeabi-v7a"],