Skip to content
Merged
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
8 changes: 8 additions & 0 deletions src/serious_python/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 4.5.0

* **iOS/macOS:** the pre-built `Python`, `dart_bridge` and stdlib extension XCFrameworks are now staged byte-for-byte and never modified. `SERIOUS_PYTHON_BUNDLE_ID` now namespaces only the frameworks built from *your* app's wheels; the pre-built ones keep the stable `dev.flet.*` identifiers their publisher assigned.
* **This corrects 4.4.2.** Rewriting those identifiers was a hypothesis for the `ITMS-91065: Missing signature` App Store rejection (flet-dev/flet#6724) and it was wrong — the rejection reports on the publisher's signature of each XCFramework, which Xcode records in the IPA separately from your app's own signature, and the identifier rewrite destroyed it. See `serious_python_darwin` for the full explanation.
* **iOS:** use the **Swift Package Manager** packaging path for App Store submissions. The CocoaPods path copies inner `.framework` bundles and cannot produce the SDK-origin signature receipts Apple's scan looks for; it now warns during the build.
* New `SERIOUS_PYTHON_VERIFY_PROVIDER_SIGNATURES` (`warn` default / `require` / `off`) and `SERIOUS_PYTHON_EXPECTED_TEAM_ID` env vars verify those publisher signatures during packaging. Set `require` for release builds.
* Bundled python-build snapshot re-pinned to **20260729** (`dart_bridge` **1.6.1 → 1.7.0**). No Python version moved — **3.12.13 / 3.13.14 / 3.14.6** and Pyodide are unchanged from 20260727 — but every Apple XCFramework in that release is now signed by the Flet publishing team with a secure timestamp. See `serious_python_darwin` 4.5.0.

## 4.4.2

* **iOS:** new `SERIOUS_PYTHON_BUNDLE_ID` env var namespaces the generated frameworks' bundle identifiers under your app (`com.example.myapp.-ssl`) instead of a shared `org.python.*` default identical in every app built with serious_python. `flet build` sets it for you; set it yourself for a manual two-step build, in both places you set `SERIOUS_PYTHON_SITE_PACKAGES`.
Expand Down
61 changes: 55 additions & 6 deletions src/serious_python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,17 +198,22 @@ On **iOS**, also set `SERIOUS_PYTHON_BUNDLE_ID` to your app's bundle identifier:
export SERIOUS_PYTHON_BUNDLE_ID=com.example.myapp
```

Each Python C-extension ships as its own embedded framework, and this namespaces
their `CFBundleIdentifier`s under your app (`com.example.myapp.-ssl`) instead of a
shared `org.python.*` default that would be identical in every app built with
serious_python — matching what CPython's own iOS support does. A framework's bundle
identifier also becomes its code signing identifier, which Apple's App Store scan
appears to key on for the third-party SDK requirements. Set it in **both** places
Each native extension in your app's own dependencies ships as its own embedded
framework, and this namespaces their `CFBundleIdentifier`s under your app
(`com.example.myapp.-numpy-core-multiarray`) instead of a shared `org.python.*`
default — matching what CPython's own iOS support does. Set it in **both** places
`SERIOUS_PYTHON_SITE_PACKAGES` is set (the `package` command and the later `flutter
build`), since the CocoaPods `prepare_command` re-runs the darwin sync. If unset —
or if the value isn't a valid bundle identifier — the `org.python.*` defaults are
kept and a warning is printed. `flet build` sets this for you.

This applies **only** to frameworks built here from your wheels. `Python.xcframework`,
`dart_bridge.xcframework`, and the stdlib extension frameworks arrive pre-built and
**signed by their publisher**, with stable `dev.flet.python.*` / `dev.flet.dartbridge`
identifiers assigned upstream. serious_python stages those byte-for-byte and never
rewrites anything inside them — see [SDK-origin signatures](#sdk-origin-signatures)
below.

## Python app structure

By default, embedded Python program is run in a separate thread, to avoid UI blocking. Your Flutter app is not supposed to directly call Python functions or modules, but instead it should communicate via some API provided by a Python app, such as: REST API, sockets, SQLite database, files, etc.
Expand Down Expand Up @@ -266,6 +271,50 @@ The on-disk layout differs per platform, mostly because each OS has different ru

The CPython runtime, stdlib, and (on iOS) native extensions are bundled into `serious_python_darwin.framework` as resources. On **iOS**, the App Store forbids loose `.dylib`s, so every native extension `.so` is repackaged into a signed `.framework` inside an `.xcframework`, with a `.fwork` text marker left at the module's import path; CPython's `AppleFrameworkLoader` reads the marker and loads the framework binary. On **macOS**, native extensions stay as plain `.so`, merged into universal (`arm64`+`x86_64`) binaries at package time. `PYTHONHOME` is the framework's resource path; `sys.path` includes `<resources>/site-packages`, `<resources>/stdlib`, and `<resources>/stdlib/lib-dynload`.

#### SDK-origin signatures

Xcode records, for every `.xcframework` your app links against, whether **the
publisher** signed it and whether that signature carried a secure timestamp. The
result is written into the IPA as `Signatures/<name>.xcframework-ios.signature`,
and Apple's App Store scan reports a missing one as `ITMS-91065: Missing signature`.

This is **separate from your app's own signature.** Xcode re-signs every embedded
framework with your Apple Distribution identity at embed time and again at
`exportArchive`; that has no effect on the SDK-origin receipt. Equally, editing a
single file inside an `.xcframework` — even one `Info.plist` key — invalidates the
publisher's signature and turns the receipt back into `signed = false`.

So serious_python treats `Python.xcframework`, `dart_bridge.xcframework`, and the
stdlib extension frameworks as **immutable** once downloaded. They are copied
verbatim at every staging step, and each step re-checks a digest manifest recorded
at extraction time, so a reintroduced mutation fails the build instead of surfacing
as an App Store rejection weeks later.

`SERIOUS_PYTHON_VERIFY_PROVIDER_SIGNATURES` controls what happens when a publisher
signature is missing or invalid:

| value | behaviour |
| :--- | :--- |
| `warn` (default) | report and continue — lets you pin an older, pre-signing `python-build` / `dart-bridge` release |
| `require` | fail the build; use this for App Store submissions |
| `off` | skip the signature checks |

Set `SERIOUS_PYTHON_EXPECTED_TEAM_ID` to additionally require a specific Apple
Team ID on those signatures. The digest-manifest checks always run and are always
fatal — they cover this package's own behaviour, not the artifacts you pinned.

#### iOS packaging path: use SwiftPM

The **Swift Package Manager** path declares the publisher's `.xcframework`s as
`binaryTarget`s, which is what makes Xcode emit the `Signatures/` receipts above.

The CocoaPods path copies the *inner* `.framework` bundles out of the stdlib
xcframeworks in a `Pods-Runner-frameworks.sh` script phase, which discards the
outer-XCFramework provenance even when the source is correctly signed. It still
produces a working app, but it cannot produce complete SDK-origin receipts. **Use
the SwiftPM path for App Store submissions** until that path is replaced with real
vendored XCFramework declarations.

### Linux / Windows specifics

The CPython runtime (`libpython3.so` + `libpython<X.Y>.so` on Linux; `python3.dll` + `python<XY>.dll` on Windows), `libdart_bridge`, the stdlib, and native modules are copied next to your app's executable at build time. `PYTHONHOME` is the executable's directory. On Windows, extension modules (`.pyd`) and their dependent DLLs live in `<exe-dir>/DLLs/`, which is added to `sys.path`.
Expand Down
6 changes: 3 additions & 3 deletions src/serious_python/lib/src/python_versions.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// GENERATED by `dart run serious_python:gen_version_tables` from python-build's
// manifest.json (release 20260727). Do not edit by hand — edit python-build's
// manifest.json (release 20260729). Do not edit by hand — edit python-build's
// manifest.json, cut a release, bump `pythonReleaseDate`, and regenerate.

const pythonVersionEnvironmentVariable = "SERIOUS_PYTHON_VERSION";
Expand All @@ -10,8 +10,8 @@ const pyodideVersionEnvironmentVariable = "SERIOUS_PYTHON_PYODIDE_VERSION";
const dartBridgeVersionEnvironmentVariable = "DART_BRIDGE_VERSION";

/// python-build release the bundled runtimes come from (YYYYMMDD).
const pythonReleaseDate = "20260727";
const dartBridgeVersion = "1.6.1";
const pythonReleaseDate = "20260729";
const dartBridgeVersion = "1.7.0";
const defaultPythonVersion = "3.14";

class PythonRelease {
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python
description: A cross-platform plugin for adding embedded Python runtime to your Flutter apps.
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 4.4.2
version: 4.5.0

platforms:
ios:
Expand Down
4 changes: 4 additions & 0 deletions src/serious_python_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.5.0

* Re-pins the bundled python-build snapshot to **20260729** and `dart_bridge` to **1.7.0**. Both exist to provider-sign the Apple XCFrameworks (see `serious_python_darwin` 4.5.0). Nothing about Android changed: no Python version moved from 20260727 (**3.12.13 / 3.13.14 / 3.14.6**), and `dart_bridge`'s Android `.so`s are byte-identical to 1.6.1 — verified, not assumed.

## 4.4.2

* Re-pins the bundled python-build snapshot to **20260727**. Its only functional change is to the iOS frameworks' `Info.plist` (see `serious_python_darwin` 4.4.2); no versions moved and the Android runtime is unchanged from 20260726.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_android/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ buildscript {
}

group = "com.flet.serious_python_android"
version = "4.4.2"
version = "4.5.0"

rootProject.allprojects {
repositories {
Expand Down
6 changes: 3 additions & 3 deletions src/serious_python_android/android/python_versions.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# GENERATED by `dart run serious_python:gen_version_tables` from
# python-build manifest.json (release 20260727). Do not edit by hand.
# python-build manifest.json (release 20260729). Do not edit by hand.
default_python_version=3.14
dart_bridge_version=1.6.1
python_build_release_date=20260727
dart_bridge_version=1.7.0
python_build_release_date=20260729
3.12.full_version=3.12.13
3.12.android_abis=arm64-v8a,x86_64,armeabi-v7a
3.13.full_version=3.13.14
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_android
description: Android implementation of the serious_python plugin
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 4.4.2
version: 4.5.0

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
9 changes: 9 additions & 0 deletions src/serious_python_darwin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 4.5.0

* **iOS/macOS: provider-built XCFrameworks are now staged byte-for-byte, and their SDK-origin signatures are verified.** `Python.xcframework`, `dart_bridge.xcframework`, and every stdlib extension framework arrive pre-built from flet-dev/python-build and flet-dev/dart-bridge. serious_python no longer rewrites anything inside them: the `SERIOUS_PYTHON_BUNDLE_ID` pass now applies **only** to frameworks built here from the app's own wheels, which are generated in a separate directory, mutated there, and merged with the untouched provider artifacts afterwards. `reconcile_framework_install_names` (which rewrites Mach-O headers and re-signs ad-hoc) likewise never reaches a provider binary.
* **This retracts the reasoning in 4.4.2.** That entry claimed "a signature applied at the source can't matter", on the grounds that Xcode's `codesign -f` at embed and `exportArchive` overwrites it. The overwrite is real but the conclusion was wrong: Xcode *separately* records the state of each `.xcframework` as its publisher shipped it, and writes that into the IPA as `Signatures/<name>.xcframework-ios.signature` with `signed` and `isSecureTimestamp` flags. `ITMS-91065: Missing signature` reports on **those receipts**, not on the embedded copy. Namespacing `CFBundleIdentifier` under the host app was therefore not a fix, and was actively counterproductive — the plist edit invalidated the very signature the receipt reports on. The 4.4.2 identifier rewrite is reverted for provider frameworks and kept for locally built ones, where it is harmless and matches CPython's convention.
* **New: provider integrity is checked at every staging step.** A SHA-256 manifest of the provider XCFrameworks is recorded when they are extracted, and re-checked after site-package synchronization and after SwiftPM staging. Any file that changed fails the build. `SERIOUS_PYTHON_VERIFY_PROVIDER_SIGNATURES` (`warn` default / `require` / `off`) additionally checks that each provider XCFramework carries a real, securely timestamped publisher signature; `SERIOUS_PYTHON_EXPECTED_TEAM_ID` pins the expected Apple Team ID. The default is `warn` so that pinning an older, pre-signing `python-build` or `dart-bridge` release still builds — use `require` for App Store submissions.
* **iOS: use the Swift Package Manager path for App Store submissions.** The CocoaPods path installs the stdlib and site-package extensions by copying the *inner* `.framework` bundles in a `Pods-Runner-frameworks.sh` script phase; Xcode writes SDK-origin receipts only for xcframeworks it consumes as declared binary dependencies, so that path cannot produce complete receipts no matter how the source is signed. It now emits an Xcode `warning:` saying so. The SwiftPM path declares them as `binaryTarget`s and is unaffected.
* Provider identifiers are now assigned upstream and are stable: `dev.flet.python.runtime` for the Python runtime, `dev.flet.python.<module>` for each stdlib extension, `dev.flet.dartbridge` for the bridge. They no longer depend on the consuming app, which is what allows one publisher signature to cover every app.
* Re-pins the bundled python-build snapshot to **20260729** (`dart_bridge` **1.6.1 → 1.7.0**). No Python version moved — **3.12.13 / 3.13.14 / 3.14.6** and Pyodide are unchanged from 20260727. What did change is that every Apple XCFramework in that release, and `dart_bridge.xcframework` in 1.7.0, is now signed by the Flet publishing team's Apple Distribution identity with a secure timestamp: verified on the published artifacts as `Authority=Apple Distribution: Appveyor Systems Inc. (GXXRQJK434)`, a real `Timestamp=`, and an outer `_CodeSignature/CodeResources` — 56 XCFrameworks in `python-ios-dart-3.14.6`, plus the macOS and mobile-forge runtimes. See flet-dev/python-build#37 and flet-dev/dart-bridge#13.

## 4.4.2

* **iOS: framework bundle identifiers are now namespaced under the host app.** Every Python C-extension ships as its own embedded framework, and each carried a fixed `org.python.<module>` `CFBundleIdentifier` — `org.python.ssl`, `org.python.hashlib`, … — byte-identical in every app ever built with serious_python. Set `SERIOUS_PYTHON_BUNDLE_ID` to the app's bundle identifier and they become `com.example.myapp.-ssl` instead, matching what CPython's own iOS support does when it converts a `.so` into a framework. This matters because a framework's bundle identifier also becomes its **code signing identifier**, which is the one field that survives the `codesign -f` Xcode applies at embed and again at `exportArchive`. `Python.framework` is covered too — it was `org.python.python` in every app; nothing resolves it by identifier (the string appears in no shipped binary and there are no `CFBundleGetBundleWithIdentifier` lookups), so the rename is inert at runtime. `dart_bridge.framework` is deliberately left at `dev.flet.dartbridge`, which is already a vendor-owned identifier rather than a shared placeholder. Unset, or set to something that isn't a valid bundle identifier, leaves the old defaults in place and prints a warning. `flet build` supplies it from your app's configured bundle id (flet-dev/flet#6731).
Expand Down
11 changes: 11 additions & 0 deletions src/serious_python_darwin/darwin/bundle-python-frameworks-ios.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
echo "Bundle Python xcframeworks"

# This path installs the stdlib / site-package extensions by copying the INNER
# .framework bundles out of each .xcframework (see pods-runner-frameworks-addon.sh).
# Xcode only writes an SDK-origin receipt — Signatures/<name>.xcframework-ios.signature
# in the IPA — for xcframeworks it consumes as a declared binary dependency, so
# frameworks installed this way arrive with no provenance record regardless of how
# well the source xcframework was signed. That is what Apple reports as
# `ITMS-91065: Missing signature`.
#
# `warning:` is the prefix Xcode surfaces in the build log and issue navigator.
echo "warning: serious_python: the CocoaPods iOS path copies inner .framework bundles and cannot produce SDK-origin XCFramework signature receipts. Use the Swift Package Manager path for App Store submissions."

# modify Flutter packaging script to include user frameworks
if [ -n "$PODS_TARGET_SRCROOT" ]; then
echo "modify Flutter packaging script to include user frameworks: $PODS_TARGET_SRCROOT"
Expand Down
12 changes: 12 additions & 0 deletions src/serious_python_darwin/darwin/prepare_ios.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,15 @@ if [ ! -d "$dist/xcframeworks/dart_bridge.xcframework" ] || [ "$(cat "$db_marker
unzip -q "$dart_bridge_path" -d "$dist/xcframeworks/"
echo "$dart_bridge_version" > "$db_marker"
fi

# ---- provider provenance ---------------------------------------------------
# Both provider archives are now extracted. Verify their signatures at the point
# of arrival (so a bad download is caught here, not in an App Store rejection),
# and record a digest manifest of everything they contain. Every later staging
# step re-checks that manifest: the provider bundles must reach the app exactly
# as their publisher signed them, because editing any file inside one destroys
# the SDK-origin signature that the IPA's Signatures/ receipts report on.
. "$script_dir/xcframework_verify.sh"
spv_verify_provider "$dist/xcframeworks" "$dist/python-xcframeworks" || exit 1
spv_manifest_record "$dist/.provider-manifests/xcframeworks.sha256" "$dist/xcframeworks"
spv_manifest_record "$dist/.provider-manifests/python-xcframeworks.sha256" "$dist/python-xcframeworks"
13 changes: 13 additions & 0 deletions src/serious_python_darwin/darwin/prepare_macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,16 @@ if [ ! -d "$dist/xcframeworks/dart_bridge.xcframework" ] || [ "$(cat "$db_marker
unzip -q "$dart_bridge_path" -d "$dist/xcframeworks/"
echo "$dart_bridge_version" > "$db_marker"
fi

# ---- provider provenance ---------------------------------------------------
# Runs last, and in particular AFTER the Python.app removal above. That removal
# is a legacy safety net -- python-build has excluded Python.app from the shipped
# framework since the exclude list gained it, so on any current artifact it
# matches nothing -- but it does delete a file from inside a provider bundle, so
# it has to happen before the signature check and the digest snapshot rather
# than after. Nothing downstream of this point may modify these bundles: editing
# any file inside a signed XCFramework destroys the SDK-origin signature that the
# IPA's Signatures/ receipts report on.
. "$script_dir/xcframework_verify.sh"
spv_verify_provider "$dist/xcframeworks" || exit 1
spv_manifest_record "$dist/.provider-manifests/xcframeworks.sha256" "$dist/xcframeworks"
6 changes: 3 additions & 3 deletions src/serious_python_darwin/darwin/python_versions.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# GENERATED by `dart run serious_python:gen_version_tables` from
# python-build manifest.json (release 20260727). Do not edit by hand.
# python-build manifest.json (release 20260729). Do not edit by hand.
default_python_version=3.14
dart_bridge_version=1.6.1
python_build_release_date=20260727
dart_bridge_version=1.7.0
python_build_release_date=20260729
3.12.full_version=3.12.13
3.12.android_abis=arm64-v8a,x86_64,armeabi-v7a
3.13.full_version=3.13.14
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
Pod::Spec.new do |s|
s.name = 'serious_python_darwin'
s.version = '4.4.2'
s.version = '4.5.0'
s.summary = 'A cross-platform plugin for adding embedded Python runtime to your Flutter apps.'
s.description = <<-DESC
A cross-platform plugin for adding embedded Python runtime to your Flutter apps.
Expand Down
Loading
Loading