Skip to content

Commit abd90cb

Browse files
committed
Bump to 6.2.0: Python 3.15 (3.15.2) as a pre-release line
Adds 3.15 to the generated version tables. They are generated from python-build's `support-py3.15` manifest on top of the pinned release date 20261027, which is otherwise identical to that release's manifest: CPython 3.15.2, PBS 20261026, Pyodide 315.0.0a2 with wheel tag pyemscripten-2026.5-wasm32, and all three Android ABIs. The row is `prerelease: true`, so 3.15 is opt-in (`--python-version 3.15` / SERIOUS_PYTHON_VERSION=3.15) and 3.14 stays the default. Pyodide 3.15 is still an alpha and pypi.flet.dev has no cp315 mobile wheels yet. The 3.15 runtimes and the Android `-py3.15` dart_bridge binaries exist only as python-build / dart-bridge CI artifacts until those repos cut releases. To build against them: * Android: new SERIOUS_PYTHON_ANDROID_PYTHON_DIST=<dir> takes the `python-android-dart-<full>-<abi>.tar.gz` archives from <dir> instead of the release, the counterpart of the existing SERIOUS_PYTHON_DART_BRIDGE_DIST. Seeding the cache doesn't work here because the Gradle download task re-checks the release URL (onlyIfModified) and 404s. SERIOUS_PYTHON_BUILD_DIST doesn't fit either, because it skips the stdlib/site-packages staging. * Darwin/iOS/Linux/Windows already skip the download when the cached file exists. * `.github/scripts/seed_unreleased_runtimes.sh <platform>` does both from GitHub Actions run artifacts. Assets the pinned releases already publish are left alone, so it is a no-op for 3.12-3.14. CI: * 3.15 joins every bridge_example matrix (macOS and iOS x cocoapods/spm, Android, Windows, Linux amd64/arm64). Each job runs the seeding step with PYTHON_BUILD_RUN_ID / DART_BRIDGE_RUN_ID set to the python-build and dart-bridge `support-py3.15` runs. Clear both once 3.15 is released. * version_tables_in_sync compares against PYTHON_BUILD_MANIFEST_URL (the python-build branch manifest) when set, again on top of the pinned date. Windows: CPython 3.15 bundles Tcl/Tk 9 (`tcl90.dll`, `tcl9tk90.dll`, `libtommath.dll`). The Tk cleanup now drops them like it drops the 8.6 DLLs. No Dart or native logic needed to change for the new minor. Wheel tags, the extension-suffix regex, the sitecustomize shims and _sp_bootstrap's importlib hooks are minor-agnostic and unchanged in 3.15.2.
1 parent 718186c commit abd90cb

24 files changed

Lines changed: 287 additions & 18 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env bash
2+
# Make runtimes that the pinned releases don't publish yet available to a build,
3+
# by taking them from GitHub Actions runs of flet-dev/python-build and
4+
# flet-dev/dart-bridge.
5+
#
6+
# serious_python downloads its Python runtime from the python-build release in
7+
# `python_build_release_date` and its dart_bridge binary from dart-bridge
8+
# release `v<dart_bridge_version>` (the generated python_versions.properties).
9+
# A new Python minor is added to those tables before either release exists, so
10+
# a build of that minor needs the files from CI artifacts instead:
11+
#
12+
# * Darwin / iOS / Linux / Windows skip the download when the file is already
13+
# in serious_python's cache, so the artifact is placed there.
14+
# * Android re-checks the URL even when cached, so the archives are put in a
15+
# directory exported as SERIOUS_PYTHON_ANDROID_PYTHON_DIST (and the
16+
# dart_bridge .so files as SERIOUS_PYTHON_DART_BRIDGE_DIST).
17+
#
18+
# Files the releases already have are left alone, so this is a no-op for
19+
# released minors and when both run ids are empty.
20+
#
21+
# Usage: seed_unreleased_runtimes.sh <Darwin|iOS|Android|Linux|Windows>
22+
# Env: SERIOUS_PYTHON_VERSION (short version), PYTHON_BUILD_RUN_ID, DART_BRIDGE_RUN_ID, GH_TOKEN,
23+
# FLET_CACHE_DIR (optional), GITHUB_ENV / RUNNER_TEMP (set by Actions).
24+
set -euo pipefail
25+
26+
platform="${1:?platform}"
27+
short="${SERIOUS_PYTHON_VERSION:?SERIOUS_PYTHON_VERSION is required}"
28+
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
29+
props="$root/src/serious_python_linux/linux/python_versions.properties"
30+
31+
prop() { sed -n "s/^$1=//p" "$props" | tr -d '\r'; }
32+
full=$(prop "$short.full_version")
33+
date=$(prop python_build_release_date)
34+
dart_bridge=$(prop dart_bridge_version)
35+
if [ -z "$full" ]; then
36+
echo "::error::$props has no $short.full_version"
37+
exit 1
38+
fi
39+
40+
cache="${FLET_CACHE_DIR:-$HOME/.flet/cache}"
41+
pb_cache="$cache/python-build/v$full-$date"
42+
tmp="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/sp-unreleased"
43+
mkdir -p "$tmp"
44+
45+
release_assets() { # repo tag
46+
gh release view "$2" --repo "$1" --json assets --jq '.assets[].name' 2>/dev/null || true
47+
}
48+
pb_assets=$(release_assets flet-dev/python-build "$date")
49+
db_assets=$(release_assets flet-dev/dart-bridge "v$dart_bridge")
50+
has() { grep -qxF "$2" <<<"$1"; }
51+
52+
# download_artifact <repo> <run id> <artifact> -> prints the directory it is in.
53+
download_artifact() {
54+
local dir="$tmp/$3"
55+
if [ ! -d "$dir" ]; then
56+
if [ -z "$2" ]; then
57+
echo "::error::$1 has not released $3's files and no run id is set to take them from" >&2
58+
return 1
59+
fi
60+
echo "Downloading artifact $3 from $1 run $2" >&2
61+
gh run download "$2" --repo "$1" --name "$3" --dir "$dir" >&2
62+
fi
63+
echo "$dir"
64+
}
65+
66+
# seed_python <artifact> <file> <dest dir>
67+
seed_python() {
68+
if has "$pb_assets" "$2"; then
69+
return 0
70+
fi
71+
local dir
72+
dir=$(download_artifact flet-dev/python-build "${PYTHON_BUILD_RUN_ID:-}" "$1")
73+
mkdir -p "$3"
74+
cp "$dir/$2" "$3/$2"
75+
echo "Seeded $3/$2 (python-build run ${PYTHON_BUILD_RUN_ID})"
76+
}
77+
78+
export_env() {
79+
echo "$1=$2"
80+
if [ -n "${GITHUB_ENV:-}" ]; then
81+
echo "$1=$2" >> "$GITHUB_ENV"
82+
fi
83+
}
84+
85+
case "$platform" in
86+
Darwin)
87+
seed_python "darwin-unsigned-$full" "python-macos-dart-$full.tar.gz" "$pb_cache"
88+
;;
89+
iOS)
90+
seed_python "darwin-unsigned-$full" "python-ios-dart-$full.tar.gz" "$pb_cache"
91+
;;
92+
Linux)
93+
case "$(uname -m)" in
94+
x86_64 | amd64) arch=x86_64 ;;
95+
aarch64 | arm64) arch=aarch64 ;;
96+
*) echo "::error::unsupported Linux arch $(uname -m)"; exit 1 ;;
97+
esac
98+
seed_python "python-linux-$full" "python-linux-dart-$full-$arch.tar.gz" "$pb_cache"
99+
;;
100+
Windows)
101+
seed_python "python-windows-$full" "python-windows-for-dart-$full.zip" "$pb_cache"
102+
;;
103+
Android)
104+
abis=$(prop "$short.android_abis" | tr ',' ' ')
105+
py_dist="$tmp/android-python-dist"
106+
bridge_dist="$tmp/android-dart-bridge-dist"
107+
need_py=0
108+
need_bridge=0
109+
for abi in $abis; do
110+
has "$pb_assets" "python-android-dart-$full-$abi.tar.gz" || need_py=1
111+
has "$db_assets" "libdart_bridge-android-$abi-py$short.so" || need_bridge=1
112+
done
113+
if [ "$need_py" = 1 ]; then
114+
# SERIOUS_PYTHON_ANDROID_PYTHON_DIST replaces the download for every
115+
# ABI, so the directory gets all of them.
116+
dir=$(download_artifact flet-dev/python-build "${PYTHON_BUILD_RUN_ID:-}" "python-android-$full")
117+
mkdir -p "$py_dist"
118+
for abi in $abis; do
119+
cp "$dir/python-android-dart-$full-$abi.tar.gz" "$py_dist/"
120+
done
121+
export_env SERIOUS_PYTHON_ANDROID_PYTHON_DIST "$py_dist"
122+
fi
123+
if [ "$need_bridge" = 1 ]; then
124+
mkdir -p "$bridge_dist"
125+
for abi in $abis; do
126+
name="libdart_bridge-android-$abi-py$short"
127+
dir=$(download_artifact flet-dev/dart-bridge "${DART_BRIDGE_RUN_ID:-}" "$name")
128+
cp "$dir/$name.so" "$bridge_dist/"
129+
done
130+
export_env SERIOUS_PYTHON_DART_BRIDGE_DIST "$bridge_dist"
131+
fi
132+
;;
133+
*)
134+
echo "::error::unknown platform $platform"
135+
exit 1
136+
;;
137+
esac

‎.github/workflows/ci.yml‎

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ env:
3030
# older flet whose transitive deps (e.g. msgpack) have wheels for the
3131
# target python_version / platform, leaving the two halves mismatched.
3232
FLET_VERSION: "0.85.3"
33+
# GitHub Actions runs that supply runtimes the pinned releases don't publish
34+
# yet (see .github/scripts/seed_unreleased_runtimes.sh): the python-build and
35+
# dart-bridge `support-py3.15` runs that built Python 3.15.2 and its Android
36+
# dart_bridge binaries. Clear both once python-build and dart-bridge releases
37+
# carry 3.15; the seeding step is a no-op for released files.
38+
PYTHON_BUILD_RUN_ID: "42319861949"
39+
DART_BRIDGE_RUN_ID: "42321036158"
3340

3441
permissions:
3542
contents: read
@@ -54,10 +61,21 @@ jobs:
5461

5562
- name: Regenerate version tables and check for drift
5663
working-directory: src/serious_python
64+
env:
65+
# Unreleased minors: compare against python-build's `support-py3.15`
66+
# branch manifest (on top of the pinned release date) instead of the
67+
# release's. Clear once a python-build release lists 3.15.
68+
PYTHON_BUILD_MANIFEST_URL: "https://raw.githubusercontent.com/flet-dev/python-build/support-py3.15/manifest.json"
5769
run: |
5870
set -euo pipefail
5971
flutter pub get >/dev/null
60-
dart run serious_python:gen_version_tables
72+
if [ -n "$PYTHON_BUILD_MANIFEST_URL" ]; then
73+
curl -fsSL -o "$RUNNER_TEMP/manifest.json" "$PYTHON_BUILD_MANIFEST_URL"
74+
date=$(sed -n 's/^const pythonReleaseDate = "\([0-9]*\)";$/\1/p' lib/src/python_versions.dart)
75+
dart run serious_python:gen_version_tables --manifest "$RUNNER_TEMP/manifest.json" --release-date "$date"
76+
else
77+
dart run serious_python:gen_version_tables
78+
fi
6179
if ! git diff --quiet; then
6280
echo "::error::Generated Python version tables are out of date."
6381
echo "The committed lib/src/python_versions.dart and the four"
@@ -105,7 +123,7 @@ jobs:
105123
strategy:
106124
fail-fast: false
107125
matrix:
108-
python_version: ['3.12', '3.13', '3.14']
126+
python_version: ['3.12', '3.13', '3.14', '3.15']
109127
build_system: ['cocoapods', 'spm']
110128
env:
111129
SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }}
@@ -133,6 +151,12 @@ jobs:
133151
# Avoid release-time cache poisoning.
134152
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
135153

154+
- name: Seed unreleased runtimes
155+
shell: bash
156+
env:
157+
GH_TOKEN: ${{ github.token }}
158+
run: bash .github/scripts/seed_unreleased_runtimes.sh Darwin
159+
136160
- name: Configure ${{ matrix.build_system }}
137161
run: |
138162
if [ "${{ matrix.build_system }}" = "spm" ]; then
@@ -202,7 +226,7 @@ jobs:
202226
strategy:
203227
fail-fast: false
204228
matrix:
205-
python_version: ['3.12', '3.13', '3.14']
229+
python_version: ['3.12', '3.13', '3.14', '3.15']
206230
build_system: ['cocoapods', 'spm']
207231
env:
208232
SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }}
@@ -229,6 +253,12 @@ jobs:
229253
# Avoid release-time cache poisoning.
230254
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
231255

256+
- name: Seed unreleased runtimes
257+
shell: bash
258+
env:
259+
GH_TOKEN: ${{ github.token }}
260+
run: bash .github/scripts/seed_unreleased_runtimes.sh iOS
261+
232262
- name: Setup iOS Simulator
233263
id: simulator
234264
uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5 # v5
@@ -287,7 +317,7 @@ jobs:
287317
matrix:
288318
# x86_64 matches the emulator architecture; only build/install for
289319
# that ABI to keep CI fast.
290-
python_version: ['3.12', '3.13', '3.14']
320+
python_version: ['3.12', '3.13', '3.14', '3.15']
291321
env:
292322
SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }}
293323
steps:
@@ -313,6 +343,12 @@ jobs:
313343
# Avoid release-time cache poisoning.
314344
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
315345

346+
- name: Seed unreleased runtimes
347+
shell: bash
348+
env:
349+
GH_TOKEN: ${{ github.token }}
350+
run: bash .github/scripts/seed_unreleased_runtimes.sh Android
351+
316352
- name: Enable KVM
317353
run: |
318354
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
@@ -395,7 +431,7 @@ jobs:
395431
strategy:
396432
fail-fast: false
397433
matrix:
398-
python_version: ['3.12', '3.13', '3.14']
434+
python_version: ['3.12', '3.13', '3.14', '3.15']
399435
env:
400436
SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }}
401437
BRIDGE_EXAMPLE_TEST_PYDANTIC: "1"
@@ -422,6 +458,12 @@ jobs:
422458
# Avoid release-time cache poisoning.
423459
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
424460

461+
- name: Seed unreleased runtimes
462+
shell: bash
463+
env:
464+
GH_TOKEN: ${{ github.token }}
465+
run: bash .github/scripts/seed_unreleased_runtimes.sh Windows
466+
425467
- name: Package + run integration test
426468
working-directory: "src/serious_python/example/bridge_example"
427469
shell: bash
@@ -464,7 +506,7 @@ jobs:
464506
strategy:
465507
fail-fast: false
466508
matrix:
467-
python_version: ['3.12', '3.13', '3.14']
509+
python_version: ['3.12', '3.13', '3.14', '3.15']
468510
arch: [amd64, arm64]
469511
include:
470512
- arch: amd64
@@ -506,6 +548,12 @@ jobs:
506548
# Read-only on tag/release builds to avoid cache poisoning.
507549
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
508550

551+
- name: Seed unreleased runtimes
552+
shell: bash
553+
env:
554+
GH_TOKEN: ${{ github.token }}
555+
run: bash .github/scripts/seed_unreleased_runtimes.sh Linux
556+
509557
- name: Install Linux desktop build deps
510558
run: |
511559
sudo apt-get update --allow-releaseinfo-change

‎src/serious_python/CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 6.2.0
2+
3+
* **Python 3.15 (3.15.2) as a pre-release line.** Select it with `--python-version 3.15` / `SERIOUS_PYTHON_VERSION=3.15`. It is never the default: 3.14 stays the default until Pyodide 3.15 is final and `pypi.flet.dev` has cp315 mobile wheels. Web builds use Pyodide `315.0.0a2` (wheel tag `pyemscripten-2026.5-wasm32`). The 3.15 runtimes and Android `dart_bridge` binaries come from python-build and dart-bridge releases that are not cut yet. Until then, build 3.15 against their CI artifacts (see "Testing unreleased runtimes" in the README).
4+
* New `SERIOUS_PYTHON_ANDROID_PYTHON_DIST` env var points Android builds at a directory of runtime archives instead of the python-build release. See serious_python_android 6.2.0.
5+
16
## 6.1.3
27

38
* Fix the `package` command hiding why a step failed when the error went to stdout, such as the `SyntaxError` from compiling an app file, which made `flet build` fail with no explanation unless run with `-vv` ([#262](https://github.com/flet-dev/serious-python/pull/262), [flet-dev/flet#7029](https://github.com/flet-dev/flet/issues/7029)).

‎src/serious_python/README.md‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ is specified.
2727
| 3.12 | 3.12.16 | 0.27.8 | `pyodide-2024.0-wasm32` |
2828
| 3.13 | 3.13.18 | 0.29.5 | `pyemscripten-2025.0-wasm32` |
2929
| 3.14 | 3.14.10 | 314.0.7 | `pyemscripten-2026.0-wasm32` |
30+
| 3.15 | 3.15.2 (pre-release) | 315.0.0a2 | `pyemscripten-2026.5-wasm32` |
3031

3132
The default is the latest stable row (currently **3.14**) when neither
3233
`--python-version` nor `SERIOUS_PYTHON_VERSION` is set. When running through
@@ -49,7 +50,9 @@ in each platform package (read by the native build configs). To bump versions
4950
see [CONTRIBUTING.md](CONTRIBUTING.md); never hand-edit the generated files.
5051
Pre-release CPython lines are marked `prerelease: true`, so they're opt-in via
5152
explicit `--python-version` (or `requires-python = "==3.15.*"`) without becoming
52-
the auto-resolved default.
53+
the auto-resolved default. 3.15 is such a line: CPython 3.15 itself is final,
54+
but its Pyodide is still an alpha and the mobile wheel index
55+
(`pypi.flet.dev`) has no cp315 builds yet.
5356

5457
## Usage
5558

@@ -134,7 +137,7 @@ dart run serious_python:main package --asset assets/myapp.zip app/src -p Emscrip
134137

135138
Pick which CPython line to bundle with the **`SERIOUS_PYTHON_VERSION`
136139
environment variable** (supported short versions today are **3.12**, **3.13**,
137-
**3.14**; the default is the latest stable):
140+
**3.14**, and **3.15** as a pre-release; the default is the latest stable):
138141

139142
```
140143
export SERIOUS_PYTHON_VERSION=3.13
@@ -162,6 +165,26 @@ and Pyodide releases.
162165
> `flutter clean`) so stale compiled bytecode from the previous version isn't
163166
> reused.
164167
168+
#### Testing unreleased runtimes
169+
170+
The native builds download the Python runtime from the pinned python-build
171+
release and dart_bridge from the pinned dart-bridge release. To build against
172+
files that aren't released yet (e.g. a python-build or dart-bridge CI run):
173+
174+
* **Darwin, iOS, Linux, Windows** skip the download when the file is already in
175+
the cache: place it at
176+
`~/.flet/cache/python-build/v<full>-<python_build_release_date>/<asset>` (or
177+
`dart-bridge/v<dart_bridge_version>/<asset>`; `FLET_CACHE_DIR` moves
178+
`~/.flet/cache`).
179+
* **Android** always re-checks the release URL, so point it at local files
180+
instead: `SERIOUS_PYTHON_ANDROID_PYTHON_DIST=<dir>` for
181+
`python-android-dart-<full>-<abi>.tar.gz` and
182+
`SERIOUS_PYTHON_DART_BRIDGE_DIST=<dir>` for
183+
`libdart_bridge-android-<abi>-py<X.Y>.so`.
184+
185+
`.github/scripts/seed_unreleased_runtimes.sh` does this from GitHub Actions run
186+
artifacts. CI uses it to test a Python minor before its runtimes are released.
187+
165188
#### Installing requirements
166189

167190
Python app dependencies are installed with the `--requirements` option (alias `-r`). The value is passed verbatim to `pip`, so any flag pip accepts works. Pass each dependency as its own option to support specifiers that contain commas:

‎src/serious_python/analysis_options.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
analyzer:
2+
exclude:
3+
- build/**
14
include: package:flutter_lints/flutter.yaml
25

36
# Additional information about this file can be found at

‎src/serious_python/lib/src/python_versions.dart‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,12 @@ const pythonReleases = <String, PythonRelease>{
6161
androidAbis: ["arm64-v8a", "x86_64", "armeabi-v7a"],
6262
prerelease: false,
6363
),
64+
"3.15": PythonRelease(
65+
standaloneVersion: "3.15.2",
66+
standaloneReleaseDate: "20261026",
67+
pyodideVersion: "315.0.0a2",
68+
pyodidePlatformTag: "pyemscripten-2026.5-wasm32",
69+
androidAbis: ["arm64-v8a", "x86_64", "armeabi-v7a"],
70+
prerelease: true,
71+
),
6472
};

‎src/serious_python/pubspec.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: serious_python
22
description: A cross-platform plugin for adding embedded Python runtime to your Flutter apps.
33
homepage: https://flet.dev
44
repository: https://github.com/flet-dev/serious-python
5-
version: 6.1.3
5+
version: 6.2.0
66

77
platforms:
88
ios:

‎src/serious_python_android/CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 6.2.0
2+
3+
* Python 3.15 runtimes (`arm64-v8a`, `armeabi-v7a`, `x86_64`), pre-release.
4+
* `SERIOUS_PYTHON_ANDROID_PYTHON_DIST=<dir>` makes the build take `python-android-dart-<full>-<abi>.tar.gz` from `<dir>` instead of downloading it from the python-build release. It mirrors `SERIOUS_PYTHON_DART_BRIDGE_DIST`. Unlike `SERIOUS_PYTHON_BUILD_DIST`, the archives still go through the normal stdlib/site-packages staging. The cached download can't be used for this because the build re-checks the release URL even when the file is cached.
5+
16
## 6.1.3
27

38
* Align with the serious_python **6.1.3** release ([#262](https://github.com/flet-dev/serious-python/pull/262)).

0 commit comments

Comments
 (0)