diff --git a/src/serious_python/CHANGELOG.md b/src/serious_python/CHANGELOG.md index e0941792..b7a6e814 100644 --- a/src/serious_python/CHANGELOG.md +++ b/src/serious_python/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.9.7 + +* Fix app restart on Android 10. +* Redirect Python output to logcat. + ## 0.9.6 * Make zipDirectory call asynchronous. diff --git a/src/serious_python/pubspec.yaml b/src/serious_python/pubspec.yaml index 9b5bc1d5..0b95a5e6 100644 --- a/src/serious_python/pubspec.yaml +++ b/src/serious_python/pubspec.yaml @@ -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: 0.9.6 +version: 0.9.7 platforms: ios: diff --git a/src/serious_python_android/CHANGELOG.md b/src/serious_python_android/CHANGELOG.md index 5d4c08f1..0e9642e6 100644 --- a/src/serious_python_android/CHANGELOG.md +++ b/src/serious_python_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.9.7 + +* Fix app restart on Android 10. +* Redirect Python output to logcat. + ## 0.9.6 * Make zipDirectory call asynchronous. diff --git a/src/serious_python_android/android/build.gradle b/src/serious_python_android/android/build.gradle index d4bc782d..940713b1 100644 --- a/src/serious_python_android/android/build.gradle +++ b/src/serious_python_android/android/build.gradle @@ -1,5 +1,5 @@ group 'com.flet.serious_python_android' -version '0.9.6' +version '0.9.7' def python_version = '3.12' diff --git a/src/serious_python_android/lib/serious_python_android.dart b/src/serious_python_android/lib/serious_python_android.dart index a3e84dcf..53d6089b 100644 --- a/src/serious_python_android/lib/serious_python_android.dart +++ b/src/serious_python_android/lib/serious_python_android.dart @@ -8,6 +8,7 @@ import 'package:path/path.dart' as p; import 'package:serious_python_platform_interface/serious_python_platform_interface.dart'; import 'src/cpython.dart'; +import 'src/log.dart'; /// An implementation of [SeriousPythonPlatform] that uses method channels. class SeriousPythonAndroid extends SeriousPythonPlatform { @@ -33,10 +34,9 @@ class SeriousPythonAndroid extends SeriousPythonPlatform { List? modulePaths, Map? environmentVariables, bool? sync}) async { - Future setenv(String key, String value) async { - await methodChannel.invokeMethod( - 'setEnvironmentVariable', {'name': key, 'value': value}); - } + Future setenv(String key, String value) => + methodChannel.invokeMethod( + 'setEnvironmentVariable', {'name': key, 'value': value}); // load libpyjni.so to get JNI reference try { @@ -44,13 +44,13 @@ class SeriousPythonAndroid extends SeriousPythonPlatform { .invokeMethod('loadLibrary', {'libname': 'pyjni'}); await setenv("FLET_JNI_READY", "1"); } catch (e) { - debugPrint("Warning: Unable to load libpyjni.so library: $e"); + spDebug("Unable to load libpyjni.so library: $e"); } // unpack python bundle final nativeLibraryDir = await methodChannel.invokeMethod('getNativeLibraryDir'); - debugPrint("getNativeLibraryDir: $nativeLibraryDir"); + spDebug("getNativeLibraryDir: $nativeLibraryDir"); var bundlePath = "$nativeLibraryDir/libpythonbundle.so"; var sitePackagesZipPath = "$nativeLibraryDir/libpythonsitepackages.so"; @@ -60,7 +60,7 @@ class SeriousPythonAndroid extends SeriousPythonPlatform { } var pythonLibPath = await extractFileZip(bundlePath, targetPath: "python_bundle"); - debugPrint("pythonLibPath: $pythonLibPath"); + spDebug("pythonLibPath: $pythonLibPath"); var programDirPath = p.dirname(appPath); @@ -74,22 +74,22 @@ class SeriousPythonAndroid extends SeriousPythonPlatform { if (await File(sitePackagesZipPath).exists()) { var sitePackagesPath = await extractFileZip(sitePackagesZipPath, targetPath: "python_site_packages"); - debugPrint("sitePackagesPath: $sitePackagesPath"); + spDebug("sitePackagesPath: $sitePackagesPath"); moduleSearchPaths.add(sitePackagesPath); } - setenv("PYTHONINSPECT", "1"); - setenv("PYTHONDONTWRITEBYTECODE", "1"); - setenv("PYTHONNOUSERSITE", "1"); - setenv("PYTHONUNBUFFERED", "1"); - setenv("LC_CTYPE", "UTF-8"); - setenv("PYTHONHOME", pythonLibPath); - setenv("PYTHONPATH", moduleSearchPaths.join(":")); + await setenv("PYTHONINSPECT", "1"); + await setenv("PYTHONDONTWRITEBYTECODE", "1"); + await setenv("PYTHONNOUSERSITE", "1"); + await setenv("PYTHONUNBUFFERED", "1"); + await setenv("LC_CTYPE", "UTF-8"); + await setenv("PYTHONHOME", pythonLibPath); + await setenv("PYTHONPATH", moduleSearchPaths.join(":")); // set environment variables if (environmentVariables != null) { for (var v in environmentVariables.entries) { - setenv(v.key, v.value); + await setenv(v.key, v.value); } } diff --git a/src/serious_python_android/lib/src/cpython.dart b/src/serious_python_android/lib/src/cpython.dart index 441ad98e..04f735c0 100644 --- a/src/serious_python_android/lib/src/cpython.dart +++ b/src/serious_python_android/lib/src/cpython.dart @@ -3,14 +3,46 @@ import 'dart:ffi'; import 'dart:isolate'; import 'package:ffi/ffi.dart'; -import 'package:flutter/foundation.dart'; import 'package:path/path.dart' as p; import 'gen.dart'; +import 'log.dart'; export 'gen.dart'; CPython? _cpython; +String? _logcatForwardingError; +const _logcatInitScript = r''' +import sys, logging + +# Make this init idempotent across Dart isolate restarts. +if not getattr(sys, "__serious_python_logcat_configured__", False): + sys.__serious_python_logcat_configured__ = True + + from ctypes import cdll + liblog = cdll.LoadLibrary("liblog.so") + ANDROID_LOG_INFO = 4 + + def _log_to_logcat(msg, level=ANDROID_LOG_INFO): + if not msg: + return + if isinstance(msg, bytes): + msg = msg.decode("utf-8", errors="replace") + liblog.__android_log_write(level, b"serious_python", msg.encode("utf-8")) + + class _LogcatWriter: + def write(self, msg): + _log_to_logcat(msg.strip()) + def flush(self): + pass + + sys.stdout = sys.stderr = _LogcatWriter() + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter(logging.Formatter("%(levelname)s %(message)s")) + root = logging.getLogger() + root.handlers[:] = [handler] + root.setLevel(logging.DEBUG) +'''; CPython getCPython(String dynamicLibPath) { return _cpython ??= _cpython = CPython(DynamicLibrary.open(dynamicLibPath)); @@ -46,21 +78,35 @@ Future runPythonProgramInIsolate(List arguments) async { var programDirPath = p.dirname(pythonProgramPath); var programModuleName = p.basenameWithoutExtension(pythonProgramPath); - debugPrint("dynamicLibPath: $dynamicLibPath"); - debugPrint("programDirPath: $programDirPath"); - debugPrint("programModuleName: $programModuleName"); + spDebug("dynamicLibPath: $dynamicLibPath"); + spDebug("programDirPath: $programDirPath"); + spDebug("programModuleName: $programModuleName"); final cpython = getCPython(dynamicLibPath); + spDebug("CPython loaded"); + if (cpython.Py_IsInitialized() != 0) { + spDebug("Python already initialized, skipping execution."); + sendPort.send(""); + return ""; + } + cpython.Py_Initialize(); - debugPrint("after Py_Initialize()"); + spDebug("after Py_Initialize()"); var result = ""; + final logcatSetupError = _setupLogcatForwarding(cpython); + if (logcatSetupError != null) { + cpython.Py_Finalize(); + sendPort.send(logcatSetupError); + return logcatSetupError; + } + if (script != "") { // run script final scriptPtr = script.toNativeUtf8(); int sr = cpython.PyRun_SimpleString(scriptPtr.cast()); - debugPrint("PyRun_SimpleString for script result: $sr"); + spDebug("PyRun_SimpleString for script result: $sr"); malloc.free(scriptPtr); if (sr != 0) { result = getPythonError(cpython); @@ -76,7 +122,7 @@ Future runPythonProgramInIsolate(List arguments) async { } cpython.Py_Finalize(); - debugPrint("after Py_Finalize()"); + spDebug("after Py_Finalize()"); sendPort.send(result); @@ -94,7 +140,7 @@ String getPythonError(CPython cpython) { cpython.Py_DecRef(tracebackModuleNamePtr.cast()); if (tracebackModulePtr != nullptr) { - //debugPrint("Traceback module loaded"); + //spDebug("Traceback module loaded"); final formatFuncName = "format_exception".toNativeUtf8(); final pFormatFunc = cpython.PyObject_GetAttrString( @@ -127,3 +173,20 @@ String getPythonError(CPython cpython) { return "Error loading traceback module."; } } + +String? _setupLogcatForwarding(CPython cpython) { + if (_logcatForwardingError != null) { + return _logcatForwardingError; + } + + final setupPtr = _logcatInitScript.toNativeUtf8(); + final result = cpython.PyRun_SimpleString(setupPtr.cast()); + malloc.free(setupPtr); + + if (result != 0) { + _logcatForwardingError = getPythonError(cpython); + return _logcatForwardingError; + } + + return null; +} diff --git a/src/serious_python_android/lib/src/log.dart b/src/serious_python_android/lib/src/log.dart new file mode 100644 index 00000000..2cfa01ec --- /dev/null +++ b/src/serious_python_android/lib/src/log.dart @@ -0,0 +1,10 @@ +import 'package:flutter/foundation.dart'; + +void spDebug(String message) { + if (message.startsWith('[serious_python]')) { + debugPrint(message); + } else { + debugPrint('[serious_python] $message'); + } +} + diff --git a/src/serious_python_android/pubspec.yaml b/src/serious_python_android/pubspec.yaml index d0eae473..33dc3564 100644 --- a/src/serious_python_android/pubspec.yaml +++ b/src/serious_python_android/pubspec.yaml @@ -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: 0.9.6 +version: 0.9.7 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/src/serious_python_darwin/CHANGELOG.md b/src/serious_python_darwin/CHANGELOG.md index ef480644..8d5b22d3 100644 --- a/src/serious_python_darwin/CHANGELOG.md +++ b/src/serious_python_darwin/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.9.7 + +* Fix app restart on Android 10. +* Redirect Python output to logcat. + ## 0.9.6 * Make zipDirectory call asynchronous. diff --git a/src/serious_python_darwin/darwin/serious_python_darwin.podspec b/src/serious_python_darwin/darwin/serious_python_darwin.podspec index 96771d89..5313acec 100644 --- a/src/serious_python_darwin/darwin/serious_python_darwin.podspec +++ b/src/serious_python_darwin/darwin/serious_python_darwin.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'serious_python_darwin' - s.version = '0.9.6' + s.version = '0.9.7' 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. diff --git a/src/serious_python_darwin/pubspec.yaml b/src/serious_python_darwin/pubspec.yaml index a7527666..fbed2e3c 100644 --- a/src/serious_python_darwin/pubspec.yaml +++ b/src/serious_python_darwin/pubspec.yaml @@ -2,7 +2,7 @@ name: serious_python_darwin description: iOS and macOS implementations of the serious_python plugin homepage: https://flet.dev repository: https://github.com/flet-dev/serious-python -version: 0.9.6 +version: 0.9.7 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/src/serious_python_linux/CHANGELOG.md b/src/serious_python_linux/CHANGELOG.md index 49f50392..a12caaf1 100644 --- a/src/serious_python_linux/CHANGELOG.md +++ b/src/serious_python_linux/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.9.7 + +* Fix app restart on Android 10. +* Redirect Python output to logcat. + ## 0.9.6 * Make zipDirectory call asynchronous. diff --git a/src/serious_python_linux/pubspec.yaml b/src/serious_python_linux/pubspec.yaml index 7b9a2aa6..6a8c49a5 100644 --- a/src/serious_python_linux/pubspec.yaml +++ b/src/serious_python_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: serious_python_linux description: Linux implementations of the serious_python plugin homepage: https://flet.dev repository: https://github.com/flet-dev/serious-python -version: 0.9.6 +version: 0.9.7 environment: sdk: '>=3.1.3 <4.0.0' diff --git a/src/serious_python_platform_interface/CHANGELOG.md b/src/serious_python_platform_interface/CHANGELOG.md index 545a5e03..e4610308 100644 --- a/src/serious_python_platform_interface/CHANGELOG.md +++ b/src/serious_python_platform_interface/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.9.7 + +* Fix app restart on Android 10. +* Redirect Python output to logcat. + ## 0.9.6 * Make zipDirectory call asynchronous. diff --git a/src/serious_python_platform_interface/lib/src/utils.dart b/src/serious_python_platform_interface/lib/src/utils.dart index a45e7f34..b84bd3a8 100644 --- a/src/serious_python_platform_interface/lib/src/utils.dart +++ b/src/serious_python_platform_interface/lib/src/utils.dart @@ -15,6 +15,12 @@ Future extractAssetOrFile(String path, Directory(p.join(supportDir.path, "flet", targetPath ?? p.dirname(path))); String assetHash = ""; + // read asset hash from asset + try { + assetHash = (await rootBundle.loadString("$path.hash")).trim(); + // ignore: empty_catches + } catch (e) {} + String destHash = ""; var hashFile = File(p.join(destDir.path, ".hash")); @@ -25,11 +31,6 @@ Future extractAssetOrFile(String path, await destDir.delete(recursive: true); } else { if (checkHash) { - // read asset hash from asset - try { - assetHash = (await rootBundle.loadString("$path.hash")).trim(); - // ignore: empty_catches - } catch (e) {} if (await hashFile.exists()) { destHash = (await hashFile.readAsString()).trim(); } @@ -72,6 +73,7 @@ Future extractAssetOrFile(String path, debugPrint("Finished unpacking application archive in ${stopwatch.elapsed}"); if (checkHash) { + debugPrint("Writing hash file: ${hashFile.path}, hash: $assetHash"); await hashFile.writeAsString(assetHash); } diff --git a/src/serious_python_platform_interface/pubspec.yaml b/src/serious_python_platform_interface/pubspec.yaml index e8e59615..4405431f 100644 --- a/src/serious_python_platform_interface/pubspec.yaml +++ b/src/serious_python_platform_interface/pubspec.yaml @@ -2,7 +2,7 @@ name: serious_python_platform_interface description: A common platform interface for the serious_python plugin. homepage: https://flet.dev repository: https://github.com/flet-dev/serious-python -version: 0.9.6 +version: 0.9.7 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/src/serious_python_windows/CHANGELOG.md b/src/serious_python_windows/CHANGELOG.md index 55054601..291b781f 100644 --- a/src/serious_python_windows/CHANGELOG.md +++ b/src/serious_python_windows/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.9.7 + +* Fix app restart on Android 10. +* Redirect Python output to logcat. + ## 0.9.6 * Make zipDirectory call asynchronous. diff --git a/src/serious_python_windows/pubspec.yaml b/src/serious_python_windows/pubspec.yaml index 87798abd..4ebe443e 100644 --- a/src/serious_python_windows/pubspec.yaml +++ b/src/serious_python_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: serious_python_windows description: Windows implementations of the serious_python plugin homepage: https://flet.dev repository: https://github.com/flet-dev/serious-python -version: 0.9.6 +version: 0.9.7 environment: sdk: '>=3.1.3 <4.0.0'