diff --git a/src/serious_python_android/CHANGELOG.md b/src/serious_python_android/CHANGELOG.md index 54eafbf5..e8022c7a 100644 --- a/src/serious_python_android/CHANGELOG.md +++ b/src/serious_python_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.1.1 + +* Fix the embedded interpreter crashing on startup on a **non-primary ABI** (e.g. an x86_64 emulator when `arm64-v8a` is the primary ABI) with `ModuleNotFoundError: No module named '_sysconfigdata__android_-linux-android'`. The ABI-common `stdlib.zip` was built from the primary ABI only, dropping every other ABI's arch-specific `_sysconfigdata__android_` module — which CPython imports at startup via `sysconfig` (pulled in by `ctypes`). The primary `splitStdlib` task now also harvests each other ABI's `_sysconfigdata__android_` into `stdlib.zip` (only that arch-specific module, so the generic ABI-identical `_sysconfigdata__linux_` shipped by some versions like 3.12 isn't duplicated). + ## 4.1.0 * Run the `extractAsset` / `unzipAsset` / `loadLibrary` method-channel handlers on a background `Executor` (posting the `Result` back on the main looper) instead of inline on the platform main thread. The first-launch asset unpack and the pyjnius native-library load no longer block Android's `Choreographer`, so Flutter's vsync isn't starved and on-screen animations (e.g. a boot/splash spinner) stay smooth while the app starts. diff --git a/src/serious_python_android/android/build.gradle.kts b/src/serious_python_android/android/build.gradle.kts index dc41a973..ba6e279f 100644 --- a/src/serious_python_android/android/build.gradle.kts +++ b/src/serious_python_android/android/build.gradle.kts @@ -259,6 +259,16 @@ for (abi in abis) { // (+ .soref markers in stdlib.zip), stdlib/* -> stdlib.zip root, then delete it. tasks.register("splitStdlib_$abi") { dependsOn("untarFile_$abi") + // The ABI-common stdlib.zip is built once (from the primary ABI) but must + // also carry every OTHER ABI's arch-specific `_sysconfigdata__` (see + // the harvest in doLast). Make the primary task depend on the other ABIs' + // untar so their bundles exist to read, and hold non-primary tasks until + // the primary has read them (each task deletes its own bundle at the end). + if (isPrimary) { + abis.forEach { other -> if (other != abi) dependsOn("untarFile_$other") } + } else { + mustRunAfter("splitStdlib_$primaryAbi") + } // The doLast rewrites jniLibs/ (mangled libs in, bundle out); declare it as a // tracked output and always re-run so AGP's native-libs merge re-packages. outputs.dir(jniDir) @@ -292,6 +302,39 @@ for (abi in abis) { } } } + // `_sysconfigdata__android_` 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). Since stdlib.zip is ABI-common and built from the + // primary ABI only, harvest every other ABI's into it — otherwise a + // non-primary ABI (e.g. an x86_64 emulator when arm64-v8a is primary) + // crashes with `ModuleNotFoundError: _sysconfigdata__android_...`. + // + // Match ONLY the `_sysconfigdata__android_` files (unique per ABI), + // not the generic, ABI-identical `_sysconfigdata__linux_` that some + // versions (e.g. 3.12) also ship — the primary already added that via the + // stdlib loop above, so harvesting it again would be a duplicate zip entry. + if (isPrimary) { + abis.filter { it != abi }.forEach { otherAbi -> + val otherBundle = File(file("src/main/jniLibs/$otherAbi"), "libpythonbundle.so") + if (otherBundle.exists()) { + ZipFile(otherBundle).use { ozf -> + val oen = ozf.entries() + while (oen.hasMoreElements()) { + val oe = oen.nextElement() + if (!oe.isDirectory && + oe.name.startsWith("stdlib/_sysconfigdata__android") + ) { + zip?.add( + oe.name.removePrefix("stdlib/"), + ozf.getInputStream(oe).readBytes(), + ) + } + } + } + } + } + } zip?.add("_sp_bootstrap.py", bootstrapPy.readBytes()) // finder at zip root // The dart-bridge Android shim (F) installs the finder before `site`. A // sitecustomize fallback can be re-enabled for bridges without that shim: