diff --git a/src/serious_python/CHANGELOG.md b/src/serious_python/CHANGELOG.md index b7a6e81..09cd00d 100644 --- a/src/serious_python/CHANGELOG.md +++ b/src/serious_python/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.8 + +* Fix logging on Android. + ## 0.9.7 * Fix app restart on Android 10. diff --git a/src/serious_python/pubspec.yaml b/src/serious_python/pubspec.yaml index 0b95a5e..04deeb6 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.7 +version: 0.9.8 platforms: ios: diff --git a/src/serious_python_android/CHANGELOG.md b/src/serious_python_android/CHANGELOG.md index 0e9642e..1f5d9c5 100644 --- a/src/serious_python_android/CHANGELOG.md +++ b/src/serious_python_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.8 + +* Fix logging on Android. + ## 0.9.7 * Fix app restart on Android 10. diff --git a/src/serious_python_android/android/build.gradle b/src/serious_python_android/android/build.gradle index 940713b..570e65b 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.7' +version '0.9.8' def python_version = '3.12' diff --git a/src/serious_python_android/lib/src/cpython.dart b/src/serious_python_android/lib/src/cpython.dart index 04f735c..d81522c 100644 --- a/src/serious_python_android/lib/src/cpython.dart +++ b/src/serious_python_android/lib/src/cpython.dart @@ -12,8 +12,22 @@ export 'gen.dart'; CPython? _cpython; String? _logcatForwardingError; +Future _pythonRunQueue = Future.value(); + +Future _enqueuePythonRun(Future Function() action) { + final completer = Completer(); + _pythonRunQueue = _pythonRunQueue.then((_) async { + try { + completer.complete(await action()); + } catch (e, st) { + completer.completeError(e, st); + } + }); + return completer.future; +} + const _logcatInitScript = r''' -import sys, logging +import logging,sys # Make this init idempotent across Dart isolate restarts. if not getattr(sys, "__serious_python_logcat_configured__", False): @@ -41,7 +55,7 @@ if not getattr(sys, "__serious_python_logcat_configured__", False): handler.setFormatter(logging.Formatter("%(levelname)s %(message)s")) root = logging.getLogger() root.handlers[:] = [handler] - root.setLevel(logging.DEBUG) + root.setLevel(logging.ERROR) '''; CPython getCPython(String dynamicLibPath) { @@ -50,31 +64,33 @@ CPython getCPython(String dynamicLibPath) { Future runPythonProgramFFI(bool sync, String dynamicLibPath, String pythonProgramPath, String script) async { - final receivePort = ReceivePort(); - if (sync) { - // sync run - return await runPythonProgramInIsolate( - [receivePort.sendPort, dynamicLibPath, pythonProgramPath, script]); - } else { - var completer = Completer(); - // async run - final isolate = await Isolate.spawn(runPythonProgramInIsolate, - [receivePort.sendPort, dynamicLibPath, pythonProgramPath, script]); - receivePort.listen((message) { - receivePort.close(); - isolate.kill(); - completer.complete(message); - }); - return completer.future; - } + return _enqueuePythonRun(() async { + spDebug( + "Python run start (sync=$sync, script=${script.isNotEmpty}, program=$pythonProgramPath)"); + if (sync) { + // Sync run: do not involve ports (avoids GC/close races). + final result = + _runPythonProgram(dynamicLibPath, pythonProgramPath, script); + spDebug("Python run done (resultLength=${result.length})"); + return result; + } else { + // Async run: use Isolate.run() to avoid manual port lifecycle issues. + try { + final result = await Isolate.run( + () => _runPythonProgram(dynamicLibPath, pythonProgramPath, script)); + spDebug("Python run done (resultLength=${result.length})"); + return result; + } catch (e, st) { + final message = "Dart error running Python: $e\n$st"; + spDebug(message); + return message; + } + } + }); } -Future runPythonProgramInIsolate(List arguments) async { - final sendPort = arguments[0] as SendPort; - final dynamicLibPath = arguments[1] as String; - final pythonProgramPath = arguments[2] as String; - final script = arguments[3] as String; - +String _runPythonProgram( + String dynamicLibPath, String pythonProgramPath, String script) { var programDirPath = p.dirname(pythonProgramPath); var programModuleName = p.basenameWithoutExtension(pythonProgramPath); @@ -85,8 +101,8 @@ Future runPythonProgramInIsolate(List arguments) async { final cpython = getCPython(dynamicLibPath); spDebug("CPython loaded"); if (cpython.Py_IsInitialized() != 0) { - spDebug("Python already initialized, skipping execution."); - sendPort.send(""); + spDebug( + "Python already initialized and another program is running, skipping execution."); return ""; } @@ -98,7 +114,6 @@ Future runPythonProgramInIsolate(List arguments) async { final logcatSetupError = _setupLogcatForwarding(cpython); if (logcatSetupError != null) { cpython.Py_Finalize(); - sendPort.send(logcatSetupError); return logcatSetupError; } @@ -124,53 +139,88 @@ Future runPythonProgramInIsolate(List arguments) async { cpython.Py_Finalize(); spDebug("after Py_Finalize()"); - sendPort.send(result); - return result; } String getPythonError(CPython cpython) { - // get error object - var exPtr = cpython.PyErr_GetRaisedException(); + final exPtr = cpython.PyErr_GetRaisedException(); + if (exPtr == nullptr) return "Unknown Python error (no exception set)."; + + try { + final formatted = _formatPythonException(cpython, exPtr); + if (formatted != null && formatted.isNotEmpty) return formatted; + + final fallback = _pyObjectToDartString(cpython, exPtr); + return fallback ?? "Unknown Python error (failed to stringify exception)."; + } finally { + cpython.Py_DecRef(exPtr); + // Defensive: formatting can set a new Python error. + cpython.PyErr_Clear(); + } +} - // use 'traceback' module to format exception +String? _formatPythonException( + CPython cpython, Pointer exceptionPtr) { + // Uses `traceback.format_exception(exc)` (Python 3.10+ signature). final tracebackModuleNamePtr = "traceback".toNativeUtf8(); - var tracebackModulePtr = + final tracebackModulePtr = cpython.PyImport_ImportModule(tracebackModuleNamePtr.cast()); - cpython.Py_DecRef(tracebackModuleNamePtr.cast()); - - if (tracebackModulePtr != nullptr) { - //spDebug("Traceback module loaded"); - - final formatFuncName = "format_exception".toNativeUtf8(); - final pFormatFunc = cpython.PyObject_GetAttrString( - tracebackModulePtr, formatFuncName.cast()); - cpython.Py_DecRef(tracebackModuleNamePtr.cast()); - - if (pFormatFunc != nullptr && cpython.PyCallable_Check(pFormatFunc) != 0) { - // call `traceback.format_exception()` method - final pArgs = cpython.PyTuple_New(1); - cpython.PyTuple_SetItem(pArgs, 0, exPtr); - - // result is a list - var listPtr = cpython.PyObject_CallObject(pFormatFunc, pArgs); - - // get and combine list items - var exLines = []; - var listSize = cpython.PyList_Size(listPtr); - for (var i = 0; i < listSize; i++) { - var itemObj = cpython.PyList_GetItem(listPtr, i); - var itemObjStr = cpython.PyObject_Str(itemObj); - var s = - cpython.PyUnicode_AsUTF8(itemObjStr).cast().toDartString(); - exLines.add(s); + malloc.free(tracebackModuleNamePtr); + if (tracebackModulePtr == nullptr) return null; + + try { + final formatFuncNamePtr = "format_exception".toNativeUtf8(); + final formatFuncPtr = cpython.PyObject_GetAttrString( + tracebackModulePtr, formatFuncNamePtr.cast()); + malloc.free(formatFuncNamePtr); + if (formatFuncPtr == nullptr) return null; + + try { + if (cpython.PyCallable_Check(formatFuncPtr) == 0) return null; + + final listPtr = cpython.PyObject_CallOneArg(formatFuncPtr, exceptionPtr); + if (listPtr == nullptr) return null; + + try { + final listSize = cpython.PyList_Size(listPtr); + if (listSize < 0) return null; + + final buffer = StringBuffer(); + for (var i = 0; i < listSize; i++) { + final itemObj = cpython.PyList_GetItem(listPtr, i); // borrowed ref + if (itemObj == nullptr) continue; + + final line = _pyUnicodeToDartString(cpython, itemObj) ?? + _pyObjectToDartString(cpython, itemObj); + if (line == null) continue; + buffer.write(line); + } + return buffer.toString(); + } finally { + cpython.Py_DecRef(listPtr); } - return exLines.join(""); - } else { - return "traceback.format_exception() method not found."; + } finally { + cpython.Py_DecRef(formatFuncPtr); } - } else { - return "Error loading traceback module."; + } finally { + cpython.Py_DecRef(tracebackModulePtr); + } +} + +String? _pyUnicodeToDartString( + CPython cpython, Pointer unicodeObjPtr) { + final cStr = cpython.PyUnicode_AsUTF8(unicodeObjPtr); + if (cStr == nullptr) return null; + return cStr.cast().toDartString(); +} + +String? _pyObjectToDartString(CPython cpython, Pointer objPtr) { + final strObj = cpython.PyObject_Str(objPtr); + if (strObj == nullptr) return null; + try { + return _pyUnicodeToDartString(cpython, strObj); + } finally { + cpython.Py_DecRef(strObj); } } diff --git a/src/serious_python_android/pubspec.yaml b/src/serious_python_android/pubspec.yaml index 33dc356..cff7d31 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.7 +version: 0.9.8 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 8d5b22d..198f887 100644 --- a/src/serious_python_darwin/CHANGELOG.md +++ b/src/serious_python_darwin/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.8 + +* Fix logging on Android. + ## 0.9.7 * Fix app restart on Android 10. diff --git a/src/serious_python_darwin/darwin/serious_python_darwin.podspec b/src/serious_python_darwin/darwin/serious_python_darwin.podspec index 5313ace..85f8ab9 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.7' + s.version = '0.9.8' 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 fbed2e3..c7c8543 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.7 +version: 0.9.8 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 a12caaf..49a447b 100644 --- a/src/serious_python_linux/CHANGELOG.md +++ b/src/serious_python_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.8 + +* Fix logging on Android. + ## 0.9.7 * Fix app restart on Android 10. diff --git a/src/serious_python_linux/pubspec.yaml b/src/serious_python_linux/pubspec.yaml index 6a8c49a..3b33cea 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.7 +version: 0.9.8 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 e461030..a01d0f6 100644 --- a/src/serious_python_platform_interface/CHANGELOG.md +++ b/src/serious_python_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.8 + +* Fix logging on Android. + ## 0.9.7 * Fix app restart on Android 10. diff --git a/src/serious_python_platform_interface/pubspec.yaml b/src/serious_python_platform_interface/pubspec.yaml index 4405431..f0203c3 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.7 +version: 0.9.8 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 291b781..b291d8c 100644 --- a/src/serious_python_windows/CHANGELOG.md +++ b/src/serious_python_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.8 + +* Fix logging on Android. + ## 0.9.7 * Fix app restart on Android 10. diff --git a/src/serious_python_windows/pubspec.yaml b/src/serious_python_windows/pubspec.yaml index 4ebe443..ada16c0 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.7 +version: 0.9.8 environment: sdk: '>=3.1.3 <4.0.0'