fix(android): bundle every ABI's _sysconfigdata into the ABI-common stdlib.zip#218
Merged
Merged
Conversation
…tdlib.zip The pure stdlib.zip is built once from the primary ABI (abis.first(), e.g. arm64-v8a), but _sysconfigdata__<arch> is arch-specific: each ABI ships its own (e.g. _sysconfigdata__android_x86_64-linux-android) and CPython imports the one matching the running device at startup (sysconfig, pulled in by ctypes). So on a non-primary ABI (e.g. an x86_64 emulator) the embedded interpreter crashed with 'ModuleNotFoundError: No module named _sysconfigdata__android_x86_64-linux-android'. The primary splitStdlib task now also harvests every other ABI's stdlib/_sysconfigdata* from its libpythonbundle.so into stdlib.zip (depending on the other ABIs' untar and holding non-primary tasks until the primary has read their bundles).
FeodorFitsner
added a commit
to flet-dev/flet
that referenced
this pull request
Jun 26, 2026
…anch Temporarily override serious_python_android + serious_python_platform_interface to flet-dev/serious-python#218 (fix/android-x86_64-sysconfigdata) so the android x86_64 CI leg validates the fix end-to-end (embedded Python no longer crashes with ModuleNotFoundError: _sysconfigdata__android_x86_64-linux-android). Locally confirmed: pubspec.lock resolves to the branch and stdlib.zip now ships both aarch64 and x86_64 _sysconfigdata. Revert to the pub.dev release once #218 ships.
…id dup) The previous harvest matched stdlib/_sysconfigdata*, which also catches the generic, ABI-identical _sysconfigdata__linux_ that some Python versions (e.g. 3.12) ship in every ABI. The primary ABI already adds that via the stdlib loop, so re-adding it from a non-primary ABI threw 'java.util.zip.ZipException: duplicate entry: _sysconfigdata__linux_.pyc'. Match only the per-ABI _sysconfigdata__android_<arch> modules (unique per ABI), which is exactly what CPython imports on-device. Add CHANGELOG entry (4.1.1).
FeodorFitsner
added a commit
that referenced
this pull request
Jun 29, 2026
Lockstep version bump across all packages (pubspecs, darwin podspec, android gradle) and CHANGELOG entries. Contains the two Android fixes since v4.1.0: * Guard getLongVersionCode() for API < 28 to prevent launch crash on Android 8.1 and below (#219). * Bundle every ABI's _sysconfigdata into the ABI-common stdlib.zip, fixing a non-primary-ABI startup ModuleNotFoundError (#218).
FeodorFitsner
added a commit
to flet-dev/flet
that referenced
this pull request
Jun 30, 2026
* fix(controls): preserve concrete value type when constructing ValueKey
`ValueKey(controlKey.value)` produced `ValueKey<Object>(value)` because
`controlKey.value` is statically typed `Object`. Flutter's `ValueKey.==`
is runtimeType-strict, so `ValueKey<Object>('foo')` never equals
`ValueKey<String>('foo')` — making `find.byKey(Key('foo'))` /
`find.byKey(ValueKey('foo'))` in flutter_test fail to locate any
Flet-rendered control by user-assigned key.
Switch-dispatch on the runtime type so a String value yields
`ValueKey<String>`, int → `ValueKey<int>`, etc. This matches what
`Key('foo')` (factory for `ValueKey<String>('foo')`) and analogous
test-side constructions produce.
Repro: flet_example in flet-dev/serious-python on the dart-bridge
branch — its integration_test/app_test.dart with
`find.byKey(Key('increment'))` for an IconButton with
`key="increment"` was finding 0 widgets until this fix.
* feat(transport): add dart_bridge in-process transport (alongside socket)
Adds a third transport (`FletDartBridgeServer` + Dart-side channel-builder
injection) that exchanges Flet's MsgPack protocol over the in-process
`dart_bridge` byte channel — the prebuilt-binary FFI bridge consumed by
serious_python plugins via `package:serious_python/bridge.dart`.
Coexists with the existing UDS / TCP socket transport. Activation:
- Python: `FLET_DART_BRIDGE_PORT=<port>` env var + `is_embedded()` true.
- Dart: pass `FletApp(channelBuilder: ...)` — the embedder constructs a
`FletBackendChannel` impl wrapping a `PythonBridge` and feeds it in.
`flet` package itself stays Python-independent: it does NOT depend on
`serious_python` or know about `PythonBridge`. The whole PythonBridge
wiring lives in the embedder's code (proven by a forthcoming
`flet_ffi_example` in serious-python). What lands here in `flet` is just
the seam.
Python side:
- New `flet/messaging/flet_dart_bridge_server.py` — `FletDartBridgeServer`
with the same protocol dispatch as `FletSocketServer`, lazy-imported so
non-embedded runs never load `dart_bridge`. Inbound: `__on_bytes`
enqueues payloads from the C-callback thread onto an asyncio.Queue
drained by `__inbound_loop`. Outbound: `send_message` calls
`dart_bridge.send_bytes(port, packb(...))`.
- `flet/app.py`: `run_async` selection block grows a third arm:
if is_embedded() and FLET_DART_BRIDGE_PORT: dart_bridge
elif is_socket_server: socket (existing)
else: web (existing)
- New helper `__run_dart_bridge_server` modelled on `__run_socket_server`.
Dart side:
- New `FletBackendChannelBuilder` typedef in
`transport/flet_backend_channel.dart`.
- `FletApp` accepts optional `channelBuilder`; `FletBackend` honours it in
`connect()` and skips the URL-scheme factory when present. URL-based
routing for socket / websocket / mock / Pyodide is unchanged.
Wire protocol — unchanged. Same `[ClientAction, body]` MsgPack frames,
same encoder/decoder, same Session dispatch. Only the byte transport
differs.
* feat(transport): export FletBackendChannel + msgpack helpers from flet.dart (lets embedders implement channelBuilder)
* fix(transport): park embedded dart_bridge run loop until host shutdown
The dart_bridge transport has no accept loop equivalent — start() registers a
byte handler with libdart_bridge and returns immediately. Without an explicit
wait, run_async() falls through to conn.close() as soon as main() returns,
killing the bridge before any Dart-side frame can arrive. The embedded
interpreter then exits even though the Flutter host is still running.
Mirror the existing url_prefix/socket-server arm: wait on the terminate event
when is_embedded() and FLET_DART_BRIDGE_PORT are both set.
* templates(build): migrate from sockets to PythonBridge FFI transport
Switches the production transport in `flet build`'s generated app from
TCP/UDS sockets to the in-process dart_bridge FFI channel that the
serious-python `dart-bridge` branch exposes. Web mode (websocket) and
developer mode (external Python process over TCP/UDS) stay unchanged —
PythonBridge only makes sense when the Python interpreter is embedded
in the same OS process as Flutter.
main.dart:
* Two long-lived PythonBridge instances created in prepareApp():
`_bridge` carries the MsgPack-framed Flet protocol; `_exitBridge`
is a dedicated outbound channel for Python's exit code (replaces
the legacy stdout-callback ServerSocket).
* pageUrl = `dartbridge://<port>`; env exports FLET_DART_BRIDGE_PORT
and FLET_DART_BRIDGE_EXIT_PORT. The Python flet package's app.py
picks up FLET_DART_BRIDGE_PORT and starts FletDartBridgeServer
instead of FletSocketServer.
* `_DartBridgeBackendChannel` (lifted from flet_ffi_example): wraps
PythonBridge as a FletBackendChannel — streaming msgpack decoder
on inbound, encoder + 30s retry loop on outbound. Injected into
FletApp via the `channelBuilder` parameter added in the flet PR.
* runPythonApp drops the ServerSocket setup; subscribes to
`_exitBridge.messages` and reuses the existing error-screen /
`exit(code)` handling unchanged.
* Dropped the now-unused `getUnusedPort` helper.
python.dart:
* Drops the `socket` callback channel and FLET_PYTHON_CALLBACK_SOCKET_ADDR.
* `flet_exit` posts the exit code as raw UTF-8 bytes via
`dart_bridge.send_bytes(FLET_DART_BRIDGE_EXIT_PORT, ...)`.
* stdout/stderr → FLET_APP_CONSOLE file redirection preserved (the
Dart side reads it for the error screen on `flet_exit(100)`).
pubspec.yaml:
* `serious_python` pinned to the dart-bridge branch via git ref —
1.0.1 on pub.dev predates PythonBridge. Swap to a version pin
once serious_python ships a release carrying the bridge API.
* Added `msgpack_dart: ^1.0.1` for the channel's wire codec.
Dev mode (--debug + page URL in args) still creates no bridges and
FletApp resolves transport via its URL-scheme factory; web mode reads
Uri.base unchanged.
* Add path for serious-python git dependency
Add a `path: src/serious_python` entry to the serious-python git dependency in sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml. This directs the package resolver to the subdirectory within the referenced repo (ref: dart-bridge) so the Dart package is loaded from src/serious_python instead of the repository root.
* Bump 3.13.14 / 3.14.6 / Pyodide 314.0.0; thread date-based python-build vars
Mirror the serious-python registry bump:
* 3.12 row: Astral PBS date 20260610 (CPython 3.12.13 unchanged).
* 3.13 row: CPython 3.13.14, PBS date 20260610.
* 3.14 row: CPython 3.14.6, PBS date 20260610, Pyodide 314.0.0 GA.
* All three rows gain `python_build_date: "20260611"` for the new
date-keyed flet-dev/python-build release scheme.
The 3.13 wheel platform tag was also wrong — `pyodide-2025.0-wasm32`
where it should have been `pyemscripten-2025.0-wasm32` (the prefix
transition happened at Pyodide 0.28/0.29, not at 314.0). `flet build web
--python-version 3.13` would have failed to match Pyodide-built native
wheels. Fixed in the registry and called out in the 0.86.0 changelog.
`build_base.py` now exports two new env vars alongside the existing
`SERIOUS_PYTHON_VERSION` so the serious-python platform plugin build
scripts can construct the new URL form (`…/<YYYYMMDD>/python-*-<full>-*`):
* SERIOUS_PYTHON_FULL_VERSION → python_release.standalone
* SERIOUS_PYTHON_BUILD_DATE → python_release.python_build_date
Both are set in `package_env` (for `serious_python:main package`) and
`build_env` (for the subsequent `flutter build`).
Breaking-changes docs for 0.86: new 0.86.0 section in the index plus two
new guide pages covering (a) the default-Python bump 3.12 → 3.14 with
three pin options, and (b) the removal of `flet.version.pyodide_version`
/ `PYODIDE_VERSION` with the registry-lookup replacement. The dart_bridge
default-transport migration guide is intentionally not in this commit;
it'll be authored separately.
Publish docs tables (`publish/index.md`, `publish/web/static-website`)
updated to the new patch + Pyodide versions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(transport): DataChannel API for high-throughput widget byte streams
Adds dedicated byte channels (`ft.DataChannel`) that let widgets exchange
bulk binary data (image frames, audio buffers, ML tensors) with their
Python counterpart without going through the MsgPack control protocol.
Architecture:
* `package:flet` exposes abstract `DataChannel` + `DataChannelFactory`.
Embedders inject a fast-path factory; absent that, a built-in
`ProtocolMuxedDataChannelFactory` muxes channel bytes over the active
Flet protocol transport.
* Python side: `ft.DataChannel` ABC with `_DartBridgeDataChannel`
(embedded native, dedicated PythonBridge) and `_ProtocolMuxedDataChannel`
(muxed fallback) impls. `Control.get_data_channel(id)` resolves a
channel allocated on the Dart side.
* Handshake: control-level event `data_channel_open` carrying
`{channel_name, channel_id}` — push-driven, no polling, no race.
Wire format change (breaking):
* All transports now prefix every packet with a 1-byte type
discriminator: `0x00` = MsgPack-encoded Flet protocol frame,
`0x01` = raw DataChannel frame (`[channel_id:u32 LE][bytes]`).
* Stream-oriented transports (UDS/TCP) gain a 4-byte little-endian
length prefix; message-oriented transports (WebSocket, postMessage,
dart_bridge) keep native message boundaries.
* `StreamingMsgpackDeserializer` removed — every inbound packet is one
complete MsgPack value, decoded via one-shot `msgpack.deserialize`.
Same simplification on the Python side: `Unpacker.feed` loops →
`msgpack.unpackb(payload)`.
Updated all four Connection subclasses (`FletSocketServer`,
`FletDartBridgeServer`, `flet_web.fastapi.FletApp`, `PyodideConnection`)
and all five Dart transports (socket, WebSocket, JavaScript/postMessage,
mock, JS stub) to the new framing. Pyodide outbound uses Transferable
ArrayBuffer for zero-copy across the worker boundary.
Three smoke tests in `packages/flet/test/transport/data_channel_test.dart`
cover factory allocation, inbound routing by channel id, and the outbound
muxed packet shape.
* feat(flet-charts): migrate MatplotlibChartCanvas to DataChannel
Replaces the `_invoke_method`-based `apply_full` / `apply_diff` / `clear`
plumbing with a dedicated `DataChannel` carrying 1-byte-opcode frames
(0x01=full PNG, 0x02=diff PNG, 0x03=clear). PNG bytes no longer pay
MsgPack encode/decode — they flow at memory-bandwidth-class speed in
embedded native mode and at near-bandwidth speed in dev/web modes (raw-
byte frames muxed over the protocol transport).
Backpressure follows the WebAgg pattern: Dart sends a 1-byte `[0xFF]`
ack back over the same channel after each apply chain resolves; the
canvas exposes `set_on_frame_applied(callback)` so `MatplotlibChart`
clears `_waiting` only after Dart confirms the frame painted, mirroring
mpl.js's `img.onload → waiting=false` flow. Without this gate,
interactive drags pile up frames in the Dart-side queue and replay in a
burst.
The 3D example (`examples/.../matplotlib_chart/three_d/main.py`) adds a
status bar showing avg full/diff frame size, total bytes transferred,
sliding-window transfer speed, FPS, and per-stage latency (dart-side
paint vs mpl-side render+idle) so users can see where time is spent.
GPU / CPU strategy code in both State subclasses is unchanged — only
the source of frames switched from `_invokeMethod(...)` to the channel
listener.
* refactor(build): split native FFI runtime out of main.dart for web compat
`flet build web` was failing to compile with errors like "Type 'Pointer'
not found" because the build template's `main.dart` unconditionally
imported `package:serious_python/bridge.dart` and
`package:serious_python/serious_python.dart`, both of which transitively
pull in `dart:ffi` types via `package:serious_python_platform_interface`.
`dart:ffi` isn't available in the web compile target.
Extract everything that touches `serious_python` into a separate
`native_runtime.dart`:
* `initBridges(envVars) → pageUrl` — creates the protocol + exit
PythonBridge instances and stamps env vars.
* `channelBuilder`, `dataChannelFactory` getters for the embedded
PythonBridge-backed transports.
* `runPython(...)` — wraps `SeriousPython.runProgram` + the exit-bridge
listener.
* `extractAppAssets(...)` — wraps `extractAssetZip`.
* The `_DartBridgeBackendChannel`, `_PythonBridgeDataChannel`, and
`_PythonBridgeDataChannelFactory` impls.
`main.dart` now uses a conditional import:
import 'native_runtime_stub.dart'
if (dart.library.ffi) 'native_runtime.dart' as nrt;
On web, the stub (`native_runtime_stub.dart`) is selected — every
entry point either returns null or throws `UnsupportedError`, and is
guarded behind `kIsWeb` so the stub is never reached at runtime. The
result: `flet build web` compiles cleanly without `dart:ffi` ever
entering the compile graph.
No behavior change on native (mobile/desktop) builds — they pick up the
real `native_runtime.dart` via the conditional and execute the same code
that lived in `main.dart` before.
* fix(web): switch Pyodide worker to module type + pyodide.mjs
Pyodide >= 0.29 (and 314.0.0, the Python 3.14 line) throws "Classic web
workers are not supported" inside any worker where `importScripts` is
callable. python-worker.js was spawned as a classic worker, so booting
the Python 3.13 / 3.14 lines surfaced a hard error before any user code
ran.
Switch to module workers across both the flet web client and the
`flet build` template:
* `new Worker(url, { type: "module" })` — module workers don't expose
`importScripts`, so Pyodide's check passes.
* `importScripts(pyodideUrl)` → `const { loadPyodide } = await
import(pyodideUrl)` — the dynamic-import form module workers must
use.
* All `pyodideUrl` defaults flip from `pyodide.js` to `pyodide.mjs` —
the ES-module variant has the named export the dynamic import expects.
URL injection paths:
* `flet publish` / `flet run --web` go through `patch_index.py`, which
now injects `pyodide.mjs` URLs (both CDN and `--no-cdn` branches).
* `flet build web` uses the cookiecutter template's index.html, which
was hardcoded at `/pyodide/pyodide.js` regardless of `--no-cdn`.
Replace with a Jinja conditional that honors `cookiecutter.no_cdn`
and uses the new `cookiecutter.pyodide_version` variable for the
jsdelivr CDN URL. `build_base.py` populates `pyodide_version` from
the resolved `python_release.pyodide`.
Forward-compatible across all three supported Pyodide lines:
0.27.7 (Python 3.12), 0.29.4 (Python 3.13), 314.0.0 (Python 3.14).
Older lines accept module workers too; 0.29+ require them.
* docs(0.86.0): DataChannel + protocol framing breaking-change guide
* CHANGELOG: new features (DataChannel API), improvements (length-prefix
framing + type-byte discriminator, StreamingMsgpackDeserializer
removed), breaking changes (wire format on stream transports, mixed
flet versions across `flet run` CLI and runtime no longer supported).
* New breaking-changes guide
`data-channel-protocol-upgrade.md` — migration notes for users with
custom backends speaking the Flet protocol, plus a heads-up for
anyone subclassing `MatplotlibChartCanvas` (the Dart-side
`_invokeMethod` handler no longer fires).
* Add the new guide to the 0.86.0 entry in the breaking-changes index.
* perf(web): transfer Pyodide-worker bytes to main via Transferable ArrayBuffer
The worker→main `postMessage` path was structured-cloning every bulk
payload (matplotlib PNG frames, etc.) — measurable cost at ~300 KB
per frame. Switch to Transferable: extract the Uint8Array's
underlying ArrayBuffer and pass it in the second argument to
postMessage. Main thread receives the buffer with ownership transferred,
no copy.
The matching main→worker (Dart→Python) direction already used
Transferable since the DataChannel landing. Both directions are now
zero-copy across the worker boundary on Pyodide.
This does not move the matplotlib bottleneck — that's WASM-compute-bound
on mplot3d — but trims a few ms of structured-clone cost per frame and
makes the perf budget closer to what the dart_bridge embedded path
delivers natively.
* fix(flet-charts): restore await-based backpressure for matplotlib frames
The sync `apply_full` + side-channel `_on_frame_applied` callback was
losing matplotlib "draw" events in pyodide mode. Sequence:
1. `_receive_loop` reads frame bytes, calls `apply_full(bytes)` — sync,
returns immediately.
2. Loop iterates, reads next event from `_receive_queue`.
3. Next event is a `"draw"` notification matplotlib emitted just after
the previous frame (figure dirty again from mouse drag).
4. Gate check: `_waiting=True` (ack hasn't arrived from Dart yet) →
**drop the event**.
5. Ack arrives 200+ ms later, `_waiting=False`, but the queue is empty
and matplotlib doesn't re-emit "draw" until next mouse event.
Result in pyodide: ~1.5 fps observed, vs the 0.85 `_invoke_method`
implementation's much higher rate. The 0.85 pattern wasn't faster
because it lacked an ack — it had one (the INVOKE_METHOD reply). It
was faster because `await self._invoke_method(...)` **blocked the
`_receive_loop`** during the round-trip, so matplotlib events queued
naturally in `_receive_queue` and were processed in order after the
await returned, rather than being eagerly drained against a stale gate.
Fix: re-introduce the await pattern at the canvas level.
* `MatplotlibChartCanvas.apply_full / apply_diff / clear` are now
async. Each enqueues a per-frame `asyncio.Future`, sends the channel
packet, and awaits the future.
* `_on_dart_message` resolves the head future when `[0xFF]` arrives.
* `MatplotlibChart._receive_loop` awaits each `apply_*` call —
matplotlib events that arrive during the wait stay queued and are
processed after the ack returns. Same behaviour shape as 0.85's
`_invoke_method` round-trip, but over the DataChannel transport
(no msgpack on the bulk payload).
* `set_on_frame_applied(cb)` is preserved as a pure observer callback
for instrumentation (e.g. the 3D example's stats panel) — no longer
load-bearing for backpressure.
The 3D example's `apply_full` / `apply_diff` wrappers updated to
`async def` + `await` accordingly.
* ci: fix web client build after flet.version.pyodide_version removal
The multi-version Python PR (#6577) removed flet.version.pyodide_version
but the 'Get Pyodide version' step still read it, failing every
'Build Flet Client for Web' run. Resolve the version from the
flet_cli.utils.python_versions registry instead (default release's
Pyodide), and replace the hand-rolled tarball + wheel downloads with
flet_cli.utils.pyodide.ensure_pyodide — the hardcoded
micropip-0.8.0/packaging-24.2 filenames would have silently broken on
the new Pyodide line (3.14's lock resolves micropip 0.11.1), since
curl without -f writes 404 pages into the .whl files.
Cherry-picked from 2d8f4a1 on fix-android-arch-filtering.
Co-authored-by: ndonkoHenri <robotcoder4@protonmail.com>
* docs(breaking-changes): drop dead /docs/extending-flet/data-channels link
The 0.86 protocol-framing breaking-change guide linked to a DataChannel
API reference page that doesn't exist yet — there's no extending-flet/
folder, and no DataChannel doc has been authored. Docusaurus' broken-link
scan failed the docs build on every push. Replace the link with prose
pointing at the data_channel.py module docstring; dedicated reference
pages can land in a follow-up once the API doc generator covers it.
* ci: bump Node 20 actions to Node 24 versions
GitHub Actions emitted Node.js 20 deprecation warnings on every job in
run 27457389406. Node 20 will be removed from runners 2026-09-16. Bump
the affected actions to their latest Node 24 majors across all workflows:
- actions/checkout@v4 → v6
- actions/setup-node@v4 → v6 (v6 limited auto-cache to npm, the website
uses yarn via corepack — no caching behavior change)
- actions/upload-artifact@v4 / v5.0.0 → v7
- actions/download-artifact@v4 → v8
- astral-sh/setup-uv@v6 → v8.2.0 (v8 dropped the major @v8 tag for
supply-chain reasons, full tag required)
- dart-lang/setup-dart@<old SHA> → v1.7.2
All six actions' action.yml now declare `runs.using: node24`.
* fix(tester): preserve ValueKey value type in find_by_key
`ValueKey(controlKey.value)` produces `ValueKey<Object>` because
`controlKey.value` is statically typed Object. Flutter's `ValueKey.==`
is runtimeType-strict, so `ValueKey<Object>('foo')` never equals
the `ValueKey<String>('foo')` that ControlWidget assigns to the
rendered widget — making `find_by_key("foo")` from Python tests
find 0 widgets.
Mirrors the fix already applied in control_widget.dart (7367050).
Switch-dispatch on the runtime type so String → ValueKey<String>,
int → ValueKey<int>, etc.
Resolves the cascade of "RangeError: no indices are valid: 0" and
"assert 0 == 1" failures across apps, controls/core, controls/material,
controls/cupertino, and controls/theme integration suites.
* fix(tests): update example imports after folder rename in #6545
#6545 renamed 131 example folders (mostly basic/ → descriptive
control name, plus example_1/2/3, nested_themes_1/2 collapsing, and
removing the basic/ wrapper where there was only one example) but the
matching imports in packages/flet/integration_tests/examples/ were
never updated. Test collection failed with ModuleNotFoundError on
every affected suite (examples/apps, examples/extensions, and
examples/controls/{core,cupertino,material}).
Rewrites the 45 test files referencing those modules to the new paths
derived from the rename history of commit 1b2e914.
* Docusaurus 3.10.1 and Node.js 24
* feat(cli): add 'flet --version --json' and source CI version/dep reads from it
Add a --json flag to 'flet --version' that emits a machine-readable
document (Flet/Flutter versions, supported Python/Pyodide table, Linux
build deps). CI workflows and the publish docs now read it via jq instead
of importing Flet internals with 'python -c'.
Move the canonical Linux apt dependency list from flet.utils.linux_deps
(runtime package) to flet_cli.utils.linux_deps (build tooling), where it
sits next to python_versions.py and is a same-package import for the CLI.
* ci: pin Windows runners to windows-2025-vs2026
GitHub is redirecting windows-latest to windows-2025-vs2026 by June 15,
2026. Pin the label explicitly in the Flet Build Test and Build & Publish
workflows to silence the redirect notice and make the image deterministic.
* Allow flutter_secure_storage updates (^10.0.0)
Update pubspec.yaml dependency for flutter_secure_storage from fixed 10.0.0 to caret ^10.0.0, allowing compatible minor/patch updates instead of pinning to a single patch version. This lets the package accept backwards-compatible releases without manual changes. Fix #6586
* Resolve Python/Pyodide versions from python-build's manifest
Drop flet's hand-mirrored SUPPORTED_PYTHON_VERSIONS table and load the
supported Python/Pyodide/dart_bridge set from python-build's date-keyed
manifest.json — the single source of truth shared with serious_python.
- python_versions.py: pin one PYTHON_BUILD_RELEASE_DATE; fetch that release's
manifest.json (cached immutably under ~/.flet/cache/python-build, offline
fallback to cache) and parse it lazily. Module constants become
get_supported_python_versions()/get_default_python_version(); resolution
logic unchanged. Dev/CI overrides: FLET_PYTHON_BUILD_RELEASE_DATE,
FLET_PYTHON_BUILD_MANIFEST.
- flet build: pass only SERIOUS_PYTHON_VERSION; serious_python derives the full
version, build date, and dart_bridge version from its committed snapshot.
Drops the SERIOUS_PYTHON_FULL_VERSION/SERIOUS_PYTHON_BUILD_DATE exports.
- flet --version: drop the Python/Pyodide matrix (stays offline); --json keeps
flet/flutter/linux_dependencies.
- ci.yml: read the default Pyodide version via the manifest-backed resolver
instead of jq over `flet --version --json`.
- Docs: update the removed-pyodide-version-export guide + CHANGELOG to the new
accessors; document the pin in CONTRIBUTING.
- Add offline tests driven by FLET_PYTHON_BUILD_MANIFEST.
* Pin screen_brightness_macos to 2.1.2 (SPM macOS deployment-target regression)
screen_brightness_macos 2.1.3 ("Fix: swift package manager warning") ships a
Package.swift declaring macOS 10.11, below FlutterFramework's 10.15 SPM floor,
so `flutter build macos` fails to resolve with Swift Package Manager enabled.
Pinning the app-facing screen_brightness alone doesn't help — the federated
macOS implementation is separately versioned. Override the impl to the last
good 2.1.2 in both the build template and the client app.
Upstream: aaassseee/screen_brightness#99
* flet build: clean build dir when the bundled Python version changes
Switching --python-version (or requires-python) between builds left the previous
version's compiled bytecode in the reused build directory's native bundles
(stdlib/site-packages .pyc), crashing the app at runtime with
`ImportError: bad magic number`. Record the resolved Python short version in the
build dir and, when it changes, wipe the build dir so the native bundles are
regenerated for the new interpreter.
* feat(testing): add `flet test` for on-device integration testing
Let Flet users write and run integration tests for their apps. Tests live in
`tests/` and drive the app running on-device (built monolithic app with embedded
Python over dart_bridge): find controls by key/text, tap, enter text, assert
state and screenshots.
- New `flet test` CLI command (mirrors `flet debug`): provisions a Flutter test
host via the build pipeline in test mode, packages the app's Python, then runs
pytest. Supports platform positional + `--device-id`, `-k`, `-u` (goldens), `-v`.
- pytest plugin shipped with `flet` (zero boilerplate): function-scoped
`flet_app` fixture (fresh app per test); also runs via plain `uv run pytest`,
with `FLET_TEST_PLATFORM`/`FLET_TEST_DEVICE`/`FLET_TEST_GOLDEN` env overrides.
- Independent tester channel: Dart `RemoteWidgetTester` <-> Python `RemoteTester`
over a raw socket with length-prefixed JSON frames, separate from Flet's
transport. The Flutter WidgetTester driver moved into `packages/flet` behind
`package:flet/testing.dart`, shared by host (`runFletHostTest`) and device
(`runFletDeviceTest`) modes.
- `flet create` scaffolds `tests/test_main.py` + pytest config; build template
gains a test_mode-gated integration_test entry point.
- Docs: getting-started/integration-testing guide + cli/flet-test reference.
* fix(testing): extract integration-test driver into flet_integration_test package
`dart pub publish --dry-run` for `packages/flet` failed: its lib/ imported the
dev-only `flutter_test`/`integration_test` packages, which pub forbids (packages
used in lib/ must be in `dependencies`). Putting the driver inside `flet` was the
wrong call — it can't ship to pub.dev that way.
Move the concrete Flutter driver (flutter_tester, flutter_test_finder,
device_test, host_test, remote_widget_tester, frame_decoder) into a new
`packages/flet_integration_test` package (publish_to: none) that depends on flet
+ integration_test. flet's published lib/ no longer references any test-only
package; the abstract Tester/TestFinder interfaces stay in flet as before.
- packages/flet: drop integration_test dev-dep, remove lib/testing.dart entry.
- packages/flet_integration_test: new package; cross-package imports of
Tester/TestFinder/Lock collapse to package:flet/flet.dart; redundant dart:io
imports dropped (flet re-exports it). Standard Flutter .gitignore.
- client + build template: import package:flet_integration_test instead of
package:flet/testing.dart; add it as a path dev-dependency (test_mode-gated in
the template).
- build_base: for local dev, rewrite the flet_integration_test path the same way
it already rewrites flet (it's publish_to:none, only resolvable from the repo).
Verified: flet `pub publish --dry-run` passes; flet_integration_test and client
integration_test analyze clean.
* fix(testing): inject test driver at build time instead of templating it
The build template referenced flet_integration_test by a repo-relative path
gated with a Jinja `{% if %}` block. That broke two things for the released
(zipped) template:
1. The release pipeline patches the template pubspec with patch_pubspec_version.py,
which does a yaml.safe_load round-trip. The uncommented `{% if %}` block made
the pubspec invalid YAML, so the patch/zip step would fail on tag.
2. A repo-relative path can't resolve once the template is zipped and shipped.
Stop putting the test dependencies in the template pubspec. flet-cli now injects
them after rendering (build_base.create_flutter_project), gated on test_mode:
- local dev: flet_integration_test by path (+ dependency_override), like flet.
- end user: flet_integration_test as a git dependency pinned to this flet
version's tag (the package is publish_to:none, never on pub.dev) — consistent
with how the template already pulls serious_python from git.
The template pubspec is now plain valid YAML again (the patch tooling round-trips
it cleanly) and a normal `flet build` never pulls the test driver.
flet_integration_test depends on flet by version (not path) with a local
dependency_override, so flet unifies to a single source across the graph whether
it's consumed by path (repo) or git (user); flutter_test becomes a regular dep so
test hosts get it transitively.
Verified: template pubspec parses; patch_pubspec_version.py round-trips it in both
release and dev modes; `flet test` provisioning injects the deps and
`flutter pub get` resolves; flet_integration_test analyzes clean.
* docs(testing): add Testing types reference + link API symbols in guide
Add a "Testing" section under Reference > Types with stubs for FletTestApp,
Tester, Finder and DisposalMode (website/docs/types/testing/), wired into the
sidebar. Replace the stale top-level mkdocs-style stubs (types/finder.md,
flettestapp.md, tester.md) that used the old `:::` syntax.
Link every API class/method/property mentioned in the integration-testing guide
to its reference page using the `[label][flet.testing.Symbol]` xref format, like
other docs.
* docs(testing): fix unresolved reST xrefs in FletTestApp docstrings
The new FletTestApp reference page surfaced two reST cross-references that didn't
resolve (caught by the docs build's reST xref check):
- `FletTestApp.tester` referenced the internal, undocumented
`flet.testing.remote_tester.RemoteTester` via :class: — changed to plain code.
- `create_gif` referenced `:meth:`Page.take_animation``; the documented symbol is
`flet.BasePage.take_animation` — corrected the target.
Verified: full docusaurus build + check_docs.sh pass (reST xrefs OK).
* ci(testing): add flet-test workflow + Counter test app
New `.github/workflows/flet-test.yml` runs `flet test` (on-device) across macOS,
iOS, Windows, Linux and Android in a single matrix, against a new
`sdk/python/examples/apps/flet_test_counter` app (keyed +/- buttons,
interaction-only test, no goldens). The embedded app is built with Python 3.14;
the host venv stays on 3.13. Per-platform handling: xvfb for Linux, a booted
simulator (UDID) for iOS, reactivecircus/android-emulator-runner (KVM) for
Android. Pins the in-repo flet packages like flet_build_test.
Allowlist UDID/udid in typos (legitimate iOS term + simctl JSON field).
Verified locally: `flet test macos --python-version 3.14` -> 1 passed.
* fix(testing): drive device-mode integration tests with benchmarkLive
After merging flet-0.86, `flet test` (device mode) hung: the very first
`WidgetTester.pump()` never returned, so the tester never connected and the run
timed out.
Root cause (isolated by single-variable bisection): the build template's #6616
`BootHost` boot structure (`runApp(StatefulWidget)` -> `initState` ->
`await prepareApp()` -> `setState`) deadlocks the default `fadePointers` frame
policy under `flutter test` — `pump()` schedules a frame and blocks on
`_pendingFrame` until it's drawn, but that frame never arrives during this boot.
Swapping only `BootHost` back to the old `runApp(FutureBuilder(...))` shape made
it pass reliably on the same Flutter 3.44.3, and the old structure on 3.44 was
fine — so it's the boot structure, not the Flutter bump or the boot screen
animation.
Fix lives entirely in the test driver (no shipping-app/template change):
- `runFletDeviceTest` sets `framePolicy = benchmarkLive` — Flutter's documented
policy "for running the test on a device". Its `pump()` doesn't wait on an
engine-drawn frame (just delays), while framework-requested frames (the app's
setState/animations, incl. Python's dart_bridge updates) still render.
- Because benchmarkLive pumps don't advance wall-clock or force frames, the
driver waits for *sustained* tree idle (the boot screen's CircularProgress
Indicator keeps it busy until Python renders the page) before connecting, and
`FlutterWidgetTester.pumpAndSettle` (gated on benchmarkLive only, so host mode
is untouched) pumps with real delays until the tree stays idle — so async
tap -> on_click -> control-update round-trips land before asserting.
Verified: counter app (+/- buttons, 0 -> 1 -> -1) passes 3/3 on macOS / Flutter
3.44.3; host-mode driver unchanged.
* fix(testing): use FutureBuilder boot path under test + propagate flutter test failures
Replaces the benchmarkLive approach (previous commit), which was wrong: it
un-blocked WidgetTester.pump() but its continuous redraw triggered a
rebuild-during-build (`!_dirty`) that *failed* the on-device flutter test — and
that failure was hidden by a false-green bug (below). Verified by comparison on
Flutter 3.44.3: BootHost+benchmarkLive => `!_dirty`, 0 passed/1 failed; the
FutureBuilder boot path => `All tests passed!`, clean, 3/3.
Two fixes:
- Template main.dart: under `FLET_TEST`, boot via the old
`runApp(FutureBuilder(future: prepareApp(), ...))` shape instead of #6616's
`BootHost`. BootHost's StatefulWidget/initState-async/setState boot deadlocks
`tester.pump()` (it blocks on a frame that never arrives during boot);
FutureBuilder is driven cleanly. Production builds are unchanged (still
BootHost). The app — embedded Python over dart_bridge, FletApp — is identical.
- FletTestApp.teardown: check the `flutter test` process exit code and raise if
non-zero. The host-side find/tap assertions can all pass while the on-device
`testWidgets` body fails (e.g. a widget exception), so pytest was reporting a
pass over a failed flutter run — a false green. Now surfaced.
Revert the benchmarkLive changes to the device driver (device_test.dart,
flutter_tester.dart) — default frame policy works with the FutureBuilder path.
Verified: counter (+/-, 0 -> 1 -> -1) genuinely passes 3/3 on macOS / 3.44.3
(`All tests passed!`, no `!_dirty`, pytest + flutter agree).
* fix(testing): use resolved Flutter exe path when spawning flutter test (Windows)
On Windows the device-mode run failed at fixture setup with
`FileNotFoundError [WinError 2]`: FletTestApp spawned `flutter test` via
`create_subprocess_exec("flutter", ...)`, but Windows' CreateProcess does no
PATHEXT lookup, so a bare "flutter" (really `flutter.bat`) isn't found.
`flet test` already resolves the real Flutter executable (full path, `.bat` on
Windows) for provisioning — pass it to the pytest subprocess as
`FLET_TEST_FLUTTER_EXE` (and propagate it via `_TEST_ENV_KEYS`), and have
FletTestApp use it as argv[0], falling back to a bare "flutter" on PATH.
* fix(testing): name the test binary after project_name (Windows/Linux desktop)
Windows and Linux `flet test` failed after a successful build with
`Unable to start executable ... Failed to find "<project_name>.exe/binary"`.
Root cause (the "doesn't build on desktop" hypothesis was wrong — Flutter does
build): the Windows/Linux runner sets the executable OUTPUT_NAME to
`artifact_name`, but `flutter test -d <desktop>` launches the binary by the
Flutter pubspec `name` (== project_name). When `[tool.flet] artifact` differs
from the project name (e.g. `flet-test-counter` vs `flet_test_counter`), the
built binary and the launched path don't match. macOS is unaffected (its `.app`
is located by the product/artifact name).
In test mode, pin `artifact_name = project_name` so the desktop binary's name
matches what the integration-test host launches. Verified: macOS still passes
(now builds `flet_test_counter.app`); fixes the Windows/Linux launch path.
* fix(test): pass serious_python native-build env to flet test
flet test spawns its own 'flutter test integration_test' (via FletTestApp)
instead of going through _run_flutter_command, so it never received the
serious_python build-time env that flet build sets. Most critically
SERIOUS_PYTHON_APP was unset, which makes the Android packageApp Gradle task
early-return and leave a stale app.zip (old-Python main.pyc) in the APK,
crashing the embedded runtime with 'ImportError: bad magic number'.
Extract the serious_python native-build env into a shared
_serious_python_build_env() and use it from both _run_flutter_command and
flet test's _flutter_path_env, so the two paths bundle an identical app and
can't drift. Adds SERIOUS_PYTHON_APP, SERIOUS_PYTHON_ANDROID_EXTRACT_PACKAGES
and SP_NATIVE_SET to the test env (and _TEST_ENV_KEYS).
* ci(flet-test): capture android logcat; force software GL on linux
Android: stream device-side logs (embedded Python stdout/stderr, Flet,
Flutter, native crashes) to a file during the run and dump them in a
collapsible group afterwards, so on-device failures are diagnosable from CI.
Linux: xvfb has no GPU, so the Flutter GTK app crashes on GL context
creation (exit 79); install Mesa's software GL (llvmpipe) and force it via
LIBGL_ALWAYS_SOFTWARE/GALLIUM_DRIVER.
* ci(flet-test): non-blocking android logcat dump; linux GL diagnostics
Android: stop streaming logcat live (verbose device logs bog down the
software emulator and stall the job); instead dump the relevant slice of the
ring buffer after the run with non-blocking 'adb logcat -d'.
Linux: add a failure-diagnostic step that reports the active GL renderer
(glxinfo) and runs the built bundle directly to surface its exit-79 crash
output, which the test harness otherwise swallows.
* fix(flet-test): wait for first render in counter test; robust CI diagnostics
The counter test asserted on the first frame, but on a device the embedded
Python cold start (interpreter init + import flet + main()) can take several
seconds — longer than the device driver's fixed warmup — so find_by_text('0')
ran before the app rendered and returned 0 on the slow CI emulator (passed
locally on a faster one). pump_and_settle only settles Flutter frames, not a
pending python->dart round-trip, so poll for the first render instead.
CI: make the android logcat dump run even on failure (|| CODE=$?) with a
tight python+crash filter that won't bog the emulator; cap the linux
bundle-diagnostic with timeout so it can't hang the job.
* ci(flet-test): non-blocking android logcat; disable AT-SPI a11y on linux
Android: drop the background 'adb logcat &' (a streaming child can keep the
emulator-runner script from finishing); dump the ring buffer after the run
with non-blocking 'adb logcat -d' instead.
Linux: the app and software GL are fine (glxinfo shows llvmpipe; the bundle
runs directly without crashing) — exit 79 is specific to the integration_test
path, which enables the semantics tree and makes GTK embed an ATK a11y socket
that doesn't exist under xvfb. Disable the AT-SPI bridge (NO_AT_BRIDGE/GTK_A11Y).
* fix(flet-test): kill android false-green; poll 60s for render; logcat to artifact
The android job reported success while pytest actually failed: the
emulator-runner ran the multi-line script such that 'exit $CODE' saw an empty
CODE (and a '\'-continuation in the logcat line broke, dumping the entire
unfiltered logcat = ~58k console lines). Run the script as a single folded
line so the test command is last and its exit code is the job's, and write a
filtered device log (embedded Python + crashes only) to a file via an EXIT
trap, uploaded as an artifact instead of streaming to the console.
Also: the counter never rendered within the 10s poll window on the slow CI
emulator (cold-start embedded Python is much slower there), so poll on a 60s
deadline instead of a fixed 40 attempts.
* test(flet-test): pull serious_python from x86_64 sysconfigdata fix branch
Temporarily override serious_python_android + serious_python_platform_interface
to flet-dev/serious-python#218 (fix/android-x86_64-sysconfigdata) so the android
x86_64 CI leg validates the fix end-to-end (embedded Python no longer crashes
with ModuleNotFoundError: _sysconfigdata__android_x86_64-linux-android).
Locally confirmed: pubspec.lock resolves to the branch and stdlib.zip now ships
both aarch64 and x86_64 _sysconfigdata. Revert to the pub.dev release once #218
ships.
* ci(flet-test): add 40-min job timeout so a hung emulator auto-cancels
The android on-device run can wedge (emulator goes offline) and run until the
default 6h limit; cap the job at 40 minutes.
* fix(testing): don't hang in RemoteTester.stop() waiting on a dead client
After an on-device test passes, teardown calls RemoteTester.stop(), which did
'await self._server.wait_closed()' with no timeout. wait_closed() blocks until
the active _handle_client finishes, but _read_loop blocks on readexactly() until
EOF — and the on-device app's socket close doesn't always deliver EOF to us
(seen on Linux), so the asyncio loop hung forever after 'All tests passed!' (the
flet test process never exits). Cancel the read task so _handle_client completes,
close the writer, and bound wait_closed() with a timeout.
* fix(testing): target generated Flutter device test driver
* ci(flet-test): capture x86_64 linux integration-test verbose diagnostic
The linux job fails with 'No tests were found' + exit 79 on the x86_64 official
flutter (passes on arm64). flet_test_app already uses the file-form target and
verifies app_test.dart is non-empty, so it's neither. Re-run the integration
test directly with --verbose (unreachable dummy server) to capture which build
target/entrypoint flutter uses, whether the testWidgets body runs, and the exit
reason; upload the full verbose log as an artifact.
* fix(testing): skip linux ready-to-show wait under flet test
* fix(testing): show linux test window without ready wait
* fix(testing): size hidden linux integration test surface
* ci(flet-test): remove linux diagnostic artifact
* refactor(testing): use directory target, keep generated-driver guard
The dir->file change in 17d368b was not what fixed Linux (the window-realize
/ ready-to-show fixes were; both the dir and file forms reported 'No tests were
found' until then). Revert the flutter test target to the directory form
('integration_test') and keep only the useful part: in device mode, validate
the generated integration_test/app_test.dart exists and is non-empty so a
missing/empty driver surfaces as a clear error instead of a confusing
'No tests were found'.
* test(flet-test): simplify counter test to plain pump_and_settle + assert
Drop the _find_text_when_ready polling helper. It was a band-aid for the
android render race, but the real cause was the serious_python x86_64 crash
(PR #218) — now fixed. Try the plain template-style test and let CI confirm the
counter renders in time on the slow emulator.
* Bump Flutter SDK to 3.44.4
Update .fvmrc to pin Flutter version 3.44.4 (patch bump from 3.44.3) to ensure a consistent SDK across development and CI environments.
* ci(flet-test): drop dead mesa-utils + obsolete a11y env
- mesa-utils was only used by glxinfo in the (removed) Diagnose Linux step.
- NO_AT_BRIDGE/GTK_A11Y were added mid-debugging but didn't fix Linux (the
window realize / ready-to-show change did); the Atk-CRITICAL warnings were
non-fatal. Remove them and the now-inaccurate comment. Keep the software-GL
env (xvfb has no GPU) and the android logcat artifact (only window into
on-device failures).
* feat(cli): install Flutter on arm64 Linux via git clone; add CI leg
Flutter publishes no prebuilt arm64 Linux SDK (releases are x64-only), so
flet-cli's install_flutter downloaded a broken x64 tarball on arm64 Linux. For
arm64 Linux, clone the SDK at the version tag instead; the first 'flutter' run
then fetches the arch-appropriate engine/Dart artifacts (how fvm/git installs
work).
Add a 'linux-arm64' CI leg (ubuntu-24.04-arm) that skips the Flutter setup
action so 'flet test' installs Flutter via this path, exercising it end-to-end.
* fix(cli): precache engine after arm64 Linux Flutter clone
A bare git clone has no bin/cache, so 'dart run serious_python:main' failed
with 'could not find package sky_engine ... solving failed'. Run
'flutter precache --linux' right after the clone to populate the engine
artifacts (sky_engine + the Linux desktop engine) the prebuilt archives ship.
* test(flet-test): matrix Python 3.12/3.13/3.14; app shows + test asserts version
- CI matrix now crosses each platform with python 3.12/3.13/3.14 (job env
PYTHON_VERSION/EXPECTED_PYTHON_VERSION from matrix; dropped the workflow_dispatch
python_version input the matrix supersedes).
- Counter app displays 'Python <platform.python_version()>'.
- test_counter asserts the app reports the expected major.minor
(EXPECTED_PYTHON_VERSION), falling back to 'any version shown' locally.
Validated on macOS (renders Python 3.14.6; 1 passed).
* chore: use released serious_python 4.1.1; drop temp git override
serious_python 4.1.1 (with the x86_64 _sysconfigdata fix, PR #218) is on
pub.dev. Bump the build template serious_python 4.1.0 -> 4.1.1 and remove the
temporary git override from flet_test_counter (the fix branch was deleted after
release, which broke 'flutter pub get' on fresh runners — the linux-arm64 legs
failed with 'could not find git ref fix/android-x86_64-sysconfigdata').
* test: remove test_flet_test_app.py unit test
It imported FletTestApp, which pulls in the screenshot-comparison deps
(numpy/Pillow/scikit-image) from the optional 'test' extra at module load.
The base unit-test suite installs flet without that extra, so collection
failed with ModuleNotFoundError: No module named 'numpy'. The
__flutter_test_target device-mode guard is exercised end-to-end by the
flet-test on-device workflow.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: ndonkoHenri <robotcoder4@protonmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The pure
stdlib.zipis ABI-common and built once from the primary ABI (abis.first(), e.g.arm64-v8a). But_sysconfigdata__<arch>is arch-specific: each ABI ships its own (e.g._sysconfigdata__android_x86_64-linux-android), and CPython imports the one matching the running device at startup (sysconfig._init_posix, pulled in byctypes).So on a non-primary ABI (e.g. an x86_64 emulator when arm64-v8a is primary) the embedded interpreter crashes on startup:
This was caught by Flet's new on-device
flet testCI running on an x86_64 Android emulator (passes on arm64 devices/emulators, where the primary ABI's sysconfigdata happens to match).Fix
The primary
splitStdlibtask now also harvests every other ABI'sstdlib/_sysconfigdata*from itslibpythonbundle.sointostdlib.zip— depending on the other ABIs' untar so their bundles exist to read, and holding non-primary tasks until the primary has read them (each task deletes its own bundle at the end).Validation
A patched APK build's
stdlib.zipnow contains both_sysconfigdata__android_aarch64-linux-android.pycand_sysconfigdata__android_x86_64-linux-android.pyc(previously only the aarch64 one).