From 02e9853dba44ef90dba867e67b1b8e46afea8ed0 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 16 Oct 2026 13:53:55 -0700 Subject: [PATCH 1/2] Bump Pythons to 3.12.15/3.13.16/3.14.8; iOS 15.0 / macOS 12.0 minimums; dart_bridge 1.11.0 --- .github/workflows/build-python-version.yml | 6 +-- darwin/build_ios.py | 47 +++++++++++++++++++--- darwin/build_macos.py | 2 +- darwin/ios_patches/3.13/Python.patch | 24 ++++++----- darwin/xcframework_utils.sh | 2 +- manifest.json | 14 +++---- 6 files changed, 69 insertions(+), 26 deletions(-) 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.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/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"], From 1e0a790e73683be269f55adf848b3c009961b193 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 16 Oct 2026 14:22:26 -0700 Subject: [PATCH 2/2] 3.12 iOS patch: use ASN1_STRING accessors in _ssl.c so the build-python compiles against OpenSSL 4 --- darwin/ios_patches/3.12/Python.patch | 47 ++++++++++++++++++++++++++++ darwin/ios_patches/README.md | 8 +++++ 2 files changed, 55 insertions(+) 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/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.