Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8d84d6f
Make setenv calls awaitable in Python init
FeodorFitsner Dec 10, 2025
3f4adc0
Ensure single CPython interpreter per process
FeodorFitsner Dec 11, 2025
f31ee2b
Remove 'Warning:' prefix from debug message
FeodorFitsner Dec 11, 2025
e3e46f9
Revert "Ensure single CPython interpreter per process"
FeodorFitsner Dec 11, 2025
15be21d
Refactor debug logging and Python runtime init
FeodorFitsner Dec 12, 2025
5f6c029
Add logcat forwarding for Python stdout/stderr
FeodorFitsner Dec 12, 2025
8633a01
Ensure GIL is held for all Python C API calls
FeodorFitsner Dec 12, 2025
a272d79
Refactor GIL usage in Python execution and error handling
FeodorFitsner Dec 12, 2025
78c3e0f
Add AppLifecycleListener to finalize Python on detach
FeodorFitsner Dec 12, 2025
6823613
Move Py_FinalizeEx call to end of isolate function
FeodorFitsner Dec 12, 2025
cc1a237
Comment out Py_FinalizeEx call in runPythonProgramInIsolate
FeodorFitsner Dec 12, 2025
a0371f9
Ensure Python interpreter finalization after execution
FeodorFitsner Dec 12, 2025
5763e5b
Add safe interpreter finalization for CPython
FeodorFitsner Dec 12, 2025
0fdfe40
Fix memory deallocation for native UTF-8 strings
FeodorFitsner Dec 12, 2025
919926d
Update interpreter finalization logic for Android
FeodorFitsner Dec 12, 2025
aa32d6b
Comment out GIL management in _withGIL function
FeodorFitsner Dec 12, 2025
524ce73
Enable GIL management and interpreter finalization
FeodorFitsner Dec 12, 2025
4a7f724
Improve CPython isolate handling and logcat setup
FeodorFitsner Dec 12, 2025
2776a74
Refactor Python execution to use sub-interpreters
FeodorFitsner Dec 13, 2025
09b66cc
Add process termination support for Android plugin
FeodorFitsner Dec 13, 2025
ccf5ffd
Enable GIL management in _withGIL function
FeodorFitsner Dec 13, 2025
60f6756
Refactor CPython FFI execution and error handling
FeodorFitsner Dec 15, 2025
88a064c
Replace debugPrint with spDebug and add log utility
FeodorFitsner Dec 15, 2025
de95343
Add debug logs for CPython loading and initialization
FeodorFitsner Dec 15, 2025
6700068
Add debug print for missing asset hash file
FeodorFitsner Dec 16, 2025
9b6f2c1
Add debug prints for asset hash extraction and writing
FeodorFitsner Dec 16, 2025
3ee0cc6
Refactor asset hash reading in extractAssetOrFile
FeodorFitsner Dec 16, 2025
656063f
Remove terminate method from AndroidPlugin
FeodorFitsner Dec 16, 2025
b69fc2d
Remove unused imports from AndroidPlugin.java
FeodorFitsner Dec 16, 2025
16e983a
Remove terminate method from SeriousPythonAndroid
FeodorFitsner Dec 16, 2025
df1397f
Bump version to 0.9.7 and update changelogs
FeodorFitsner Dec 16, 2025
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
5 changes: 5 additions & 0 deletions src/serious_python/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions src/serious_python_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_android/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group 'com.flet.serious_python_android'
version '0.9.6'
version '0.9.7'

def python_version = '3.12'

Expand Down
32 changes: 16 additions & 16 deletions src/serious_python_android/lib/serious_python_android.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -33,24 +34,23 @@ class SeriousPythonAndroid extends SeriousPythonPlatform {
List<String>? modulePaths,
Map<String, String>? environmentVariables,
bool? sync}) async {
Future setenv(String key, String value) async {
await methodChannel.invokeMethod<String>(
'setEnvironmentVariable', {'name': key, 'value': value});
}
Future<void> setenv(String key, String value) =>
methodChannel.invokeMethod<String>(
'setEnvironmentVariable', {'name': key, 'value': value});

// load libpyjni.so to get JNI reference
try {
await methodChannel
.invokeMethod<String>('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<String>('getNativeLibraryDir');
debugPrint("getNativeLibraryDir: $nativeLibraryDir");
spDebug("getNativeLibraryDir: $nativeLibraryDir");

var bundlePath = "$nativeLibraryDir/libpythonbundle.so";
var sitePackagesZipPath = "$nativeLibraryDir/libpythonsitepackages.so";
Expand All @@ -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);

Expand All @@ -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);
}
}

Expand Down
79 changes: 71 additions & 8 deletions src/serious_python_android/lib/src/cpython.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -46,21 +78,35 @@ Future<String> runPythonProgramInIsolate(List<Object> 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<Char>());
debugPrint("PyRun_SimpleString for script result: $sr");
spDebug("PyRun_SimpleString for script result: $sr");
malloc.free(scriptPtr);
if (sr != 0) {
result = getPythonError(cpython);
Expand All @@ -76,7 +122,7 @@ Future<String> runPythonProgramInIsolate(List<Object> arguments) async {
}

cpython.Py_Finalize();
debugPrint("after Py_Finalize()");
spDebug("after Py_Finalize()");

sendPort.send(result);

Expand All @@ -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(
Expand Down Expand Up @@ -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<Char>());
malloc.free(setupPtr);

if (result != 0) {
_logcatForwardingError = getPythonError(cpython);
return _logcatForwardingError;
}

return null;
}
10 changes: 10 additions & 0 deletions src/serious_python_android/lib/src/log.dart
Original file line number Diff line number Diff line change
@@ -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');
}
}

2 changes: 1 addition & 1 deletion src/serious_python_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions src/serious_python_darwin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_darwin/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions src/serious_python_linux/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_linux/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
5 changes: 5 additions & 0 deletions src/serious_python_platform_interface/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
12 changes: 7 additions & 5 deletions src/serious_python_platform_interface/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ Future<String> 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"));

Expand All @@ -25,11 +31,6 @@ Future<String> 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();
}
Expand Down Expand Up @@ -72,6 +73,7 @@ Future<String> 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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_platform_interface/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions src/serious_python_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_windows/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Loading