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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

### Improvements

* **Smaller Android apps with no native-packaging config.** `flet build apk`/`aab` consume serious_python's new Android packaging: Python extension modules load **memory-mapped directly from the APK** (no extraction to disk), and pure Python ships in stored asset zips read via `zipimport`, so the standard library is no longer duplicated per ABI. Apps no longer need `useLegacyPackaging` / `keepDebugSymbols` — the `flet build` Android template drops them; just use `minSdk 23+`. New `--android-extract-packages` flag and `[tool.flet.android].extract_packages` ship "path-hungry" packages — those that read bundled data via `__file__` / `pkg_resources` instead of `importlib.resources` — extracted to disk instead of inside the zip (most packages, including `certifi`, are zip-safe and need no entry). Requires `serious_python` with the native-mmap packaging (dart_bridge 1.4.0).
* Pyodide is no longer pre-baked into the `flet build` template. Each `flet build web` / `flet publish` run downloads the matching `pyodide-core-<version>.tar.bz2` (plus the runtime `micropip` and `packaging` wheels) into a per-version cache at `~/.flet/pyodide/<version>/` and copies the files into the build output. Subsequent builds reuse the cache; the older `0.27.5` bundle previously shipped in the cookiecutter template is gone ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner.
* The supported Python / Pyodide / dart_bridge versions are loaded on demand from `flet-dev/python-build`'s date-keyed `manifest.json` (fetched once and cached under `~/.flet/cache`), the single source of truth shared with `serious_python` — replacing flet's hand-mirrored version table. `flet build` forwards only `SERIOUS_PYTHON_VERSION` and lets `serious_python` derive the full version / build date / dart_bridge version from its own committed snapshot. The module exposes `get_supported_python_versions()` / `get_default_python_version()` (the previous `SUPPORTED_PYTHON_VERSIONS` / `DEFAULT_PYTHON_VERSION` constants are removed) ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner.
* `flet --version` shows just the Flet and Flutter versions; the static `Pyodide: …` line and the global `flet.version.pyodide_version` export are removed (the supported Python / Pyodide set now lives in python-build's manifest, not the CLI output) ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner.
Expand Down
2 changes: 2 additions & 0 deletions sdk/python/_typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ AACHE = "AACHE"
ROUTEROS = "ROUTEROS"
# OpenType variable font axis tag for width
wdth = "wdth"
# Python package name (Mozilla CA bundle)
certifi = "certifi"
48 changes: 48 additions & 0 deletions sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@
"v{version}/flet-build-template.zip"
)

# Android (serious_python native-mmap packaging): pure Python ships in stored zips
# read via zipimport, which breaks packages that read bundled data through a real
# filesystem path (__file__ / pkg_resources) instead of importlib.resources. Such
# packages are shipped extracted to disk via --android-extract-packages or
# [tool.flet.android].extract_packages.
#
# The default set is empty: the common offenders read their data via
# importlib.resources, which is zip-safe (e.g. certifi.where() works from the zip —
# importlib.resources.as_file() extracts cacert.pem to a temp file on demand). Add
# real offenders here as they are found.
ANDROID_DEFAULT_EXTRACT_PACKAGES: list[str] = []


class BaseBuildCommand(BaseFlutterCommand):
"""
Expand Down Expand Up @@ -505,6 +517,15 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
default=[],
help="The list of Python packages to install from source distributions",
)
parser.add_argument(
"--android-extract-packages",
dest="android_extract_packages",
nargs="+",
default=[],
help="Android only: Python packages (relative paths) to ship extracted "
"to disk instead of inside the app zip — for packages that read bundled "
"data via __file__ / pkg_resources rather than importlib.resources",
)
parser.add_argument(
"--python-version",
dest="python_version",
Expand Down Expand Up @@ -2037,6 +2058,26 @@ def package_python_app(self):
source_packages
)

# android-extract-packages: path-hungry packages shipped extracted to disk
# instead of inside the zip (serious_python Android native-mmap packaging).
# A built-in default set covers commonly-broken packages; the user list
# (CLI / pyproject) is merged on top. Consumed by the serious_python_android
# Gradle split during `flutter build`, so the env var is set on build_env
# (see _run_flutter_command), not on the package step.
self.android_extract_packages: list[str] = []
if self.package_platform == "Android":
user_extract_packages = (
self.options.android_extract_packages
or self.get_pyproject(
f"tool.flet.{self.config_platform}.extract_packages"
)
or self.get_pyproject("tool.flet.extract_packages")
or []
)
self.android_extract_packages = list(
dict.fromkeys(ANDROID_DEFAULT_EXTRACT_PACKAGES + user_extract_packages)
)

if self.get_bool_setting(self.options.compile_app, "compile.app", False):
package_args.append("--compile-app")

Expand Down Expand Up @@ -2232,6 +2273,13 @@ def _run_flutter_command(self):
self.build_dir / "site-packages"
)

# Path-hungry packages to ship extracted to disk: consumed by the
# serious_python_android Gradle split during `flutter build`.
if self.package_platform == "Android" and self.android_extract_packages:
build_env["SERIOUS_PYTHON_ANDROID_EXTRACT_PACKAGES"] = ",".join(
self.android_extract_packages
)

if self.package_platform == "Emscripten" and not self.template_data["no_wasm"]:
build_args.append("--wasm")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,10 @@ android {
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion

packaging {
jniLibs {
useLegacyPackaging = true
keepDebugSymbols += listOf(
"*/arm64-v8a/libpython*.so",
"*/armeabi-v7a/libpython*.so",
"*/x86/libpython*.so",
"*/x86_64/libpython*.so",
)
}
}
// No native-library packaging config is needed: serious_python loads Python
// extension modules directly from the APK (memory-mapped) and ships pure
// Python in stored asset zips. Modern packaging (the default at minSdk 23+)
// is all that's required.

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ dependencies:

flet:
path: ../../../../../packages/flet
# PythonBridge (in-process dart_bridge FFI transport) ships on the
# serious-python `dart-bridge` branch. Swap back to a version pin
# (`serious_python: ^2.0.0`) once it's published to pub.dev.
# PythonBridge (in-process dart_bridge FFI transport) + the Android
# native-mmap packaging ship on the serious-python `android-native-mmap`
# branch. Swap back to a version pin (`serious_python: ^2.0.0`) once it's
# published to pub.dev.
serious_python:
git:
url: https://github.com/flet-dev/serious-python.git
ref: dart-bridge
ref: android-native-mmap
path: src/serious_python

# MsgPack codec used by the dart_bridge FletBackendChannel implementation
Expand Down
39 changes: 39 additions & 0 deletions website/docs/publish/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,45 @@ source_packages = ["package1", "package2"]
</TabItem>
</Tabs>

### Android extract packages

:::note
[Android](android.md) only.
:::

On Android, pure Python ships in a stored zip read in place (`zipimport`) and native modules are
loaded memory-mapped from the APK. Packages that read their bundled **data files** through a real
filesystem path — `__file__` / `pkg_resources` instead of
[`importlib.resources`](https://docs.python.org/3/library/importlib.resources.html) — don't work
from inside the zip. List such "path-hungry" packages here to ship them **extracted to disk**
instead.

Most packages that bundle data (including `certifi`) read it through `importlib.resources`, which
is zip-safe, so they need no entry here — only add packages that actually fail to find their data
when imported from the zip.

#### Resolution order

1. [`--android-extract-packages`](../cli/flet-build.md#--android-extract-packages)
2. `[tool.flet.android].extract_packages`
3. `[tool.flet].extract_packages`

#### Example

<Tabs groupId="flet-build--pyproject-toml">
<TabItem value="flet-build" label="flet build">
```bash
flet build apk --android-extract-packages package1 package2
```
</TabItem>
<TabItem value="pyproject-toml" label="pyproject.toml">
```toml
[tool.flet.android]
extract_packages = ["package1", "package2"]
```
</TabItem>
</Tabs>

### Icons

:::note[Platform support]
Expand Down
Loading