Skip to content

Commit c28bec3

Browse files
committed
Windows: absorb Python lifecycle into dart_bridge[_d].dll
Mirrors the darwin + android absorption. Python lifecycle code that used to live in serious_python_windows_plugin.cpp (Py_Initialize, PyRun_SimpleFile, std::thread worker) moves into dart_bridge.dll, downloaded as a prebuilt artifact from flet-dev/dart-bridge at CMake configure time. What moved: - CMakeLists.txt drops Python.h include + python<ver>.lib linking (no Python C API call sites left in this plugin). Adds file(DOWNLOAD)s for dart_bridge-windows-x86_64.dll (Release CRT) and dart_bridge_d-windows-x86_64.dll (Debug CRT). The right one for the current $<CONFIG> is added to serious_python_windows_bundled_libraries so Flutter's CONFIG-aware copy machinery picks it for each build mode. - serious_python_windows_plugin.{cpp,h} shrink to a getPlatformVersion method-channel handler. RunPythonProgram*/RunPythonScript* and the std::thread worker are gone. - serious_python_windows.dart's run() becomes one FFI call to serious_python_run via DartBridge.instance (which picks the Debug or Release DLL based on kDebugMode). path: ^1.9.0 added. Note: python3X.dll + python3.dll + Lib/ + DLLs/ + site-packages still get bundled — libdart_bridge.dll's import table references python3.dll (abi3 stub) which forwards to python3X.dll, and Python's import system needs Lib/site-packages on disk at runtime. CI: test-bridge-build.yml gains a test_bridge_example_windows job for all three Python versions.
1 parent a4ece59 commit c28bec3

6 files changed

Lines changed: 128 additions & 234 deletions

File tree

.github/workflows/test-bridge-build.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,47 @@ jobs:
175175
echo "Native libs inside APK:"
176176
unzip -l "$APK" | grep -E "lib/$ABI/" || echo "(no lib/$ABI/ entries)"
177177
fi
178+
179+
test_bridge_example_windows:
180+
name: Bridge example Windows round-trip (Python ${{ matrix.python_version }})
181+
runs-on: windows-latest
182+
strategy:
183+
fail-fast: false
184+
matrix:
185+
python_version: ['3.12', '3.13', '3.14']
186+
env:
187+
SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }}
188+
steps:
189+
- name: Checkout repository
190+
uses: actions/checkout@v4
191+
192+
- name: Setup Flutter
193+
uses: kuhnroyal/flutter-fvm-config-action/setup@v3
194+
with:
195+
path: '.fvmrc'
196+
cache: true
197+
198+
- name: Package + run integration test
199+
working-directory: "src/serious_python/example/bridge_example"
200+
run: |
201+
dart run serious_python:main package app/src --platform Windows --python-version ${{ matrix.python_version }}
202+
flutter test integration_test -d windows
203+
204+
- name: Diagnostics on failure
205+
if: failure()
206+
shell: bash
207+
working-directory: "src/serious_python/example/bridge_example"
208+
run: |
209+
set +e
210+
DBG_DIR=build/windows/x64/runner/Debug
211+
echo "=== runner/Debug dir ==="
212+
ls -la $DBG_DIR/ || true
213+
echo
214+
echo "=== dart_bridge*.dll bundled? ==="
215+
ls -la $DBG_DIR/dart_bridge*.dll || echo "(no dart_bridge dll bundled)"
216+
echo
217+
echo "=== runner/Debug/Lib (Python stdlib) ==="
218+
ls $DBG_DIR/Lib | head -20 || true
219+
echo
220+
echo "=== runner/Debug/site-packages ==="
221+
ls -la $DBG_DIR/site-packages/ || true

src/serious_python_windows/lib/serious_python_windows.dart

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,24 @@ import 'dart:io';
22

33
import 'package:flutter/foundation.dart';
44
import 'package:flutter/services.dart';
5+
import 'package:path/path.dart' as p;
56
import 'package:serious_python_platform_interface/serious_python_platform_interface.dart';
67

8+
/// Windows implementation of [SeriousPythonPlatform].
9+
///
10+
/// Python lifecycle (env, sys.path, Py_Initialize, run, finalize, sync/async)
11+
/// lives in `serious_python_run`, packaged as `dart_bridge.dll` (Release CRT)
12+
/// or `dart_bridge_d.dll` (Debug CRT) and bundled next to the .exe by this
13+
/// plugin's CMakeLists.txt. The correct DLL is picked at runtime based on
14+
/// [kDebugMode].
15+
///
16+
/// This class derives PYTHONHOME from `Platform.resolvedExecutable` (the
17+
/// runner .exe directory, where the bundled CPython lives) and dispatches a
18+
/// single FFI call to `serious_python_run`.
719
class SeriousPythonWindows extends SeriousPythonPlatform {
8-
/// The method channel used to interact with the native platform.
920
@visibleForTesting
1021
final methodChannel = const MethodChannel('serious_python_windows');
1122

12-
/// Registers this class as the default instance of [SeriousPythonPlatform]
1323
static void registerWith() {
1424
SeriousPythonPlatform.instance = SeriousPythonWindows();
1525
}
@@ -18,7 +28,7 @@ class SeriousPythonWindows extends SeriousPythonPlatform {
1828
Future<String?> getPlatformVersion() async {
1929
final version =
2030
await methodChannel.invokeMethod<String>('getPlatformVersion');
21-
return "$version ${Platform.resolvedExecutable}";
31+
return '$version ${Platform.resolvedExecutable}';
2232
}
2333

2434
@override
@@ -27,14 +37,41 @@ class SeriousPythonWindows extends SeriousPythonPlatform {
2737
List<String>? modulePaths,
2838
Map<String, String>? environmentVariables,
2939
bool? sync}) async {
30-
final Map<String, dynamic> arguments = {
31-
'exePath': Platform.resolvedExecutable,
32-
'appPath': appPath,
33-
'script': script,
34-
'modulePaths': modulePaths,
35-
'environmentVariables': environmentVariables,
36-
'sync': sync
40+
final exeDir = p.dirname(Platform.resolvedExecutable);
41+
final appDir = p.dirname(appPath);
42+
43+
final pythonPaths = <String>[
44+
...?modulePaths,
45+
appDir,
46+
p.join(appDir, '__pypackages__'),
47+
p.join(exeDir, 'site-packages'),
48+
p.join(exeDir, 'DLLs'),
49+
p.join(exeDir, 'Lib'),
50+
p.join(exeDir, 'Lib', 'site-packages'),
51+
];
52+
53+
final env = <String, String>{
54+
'PYTHONINSPECT': '1',
55+
'PYTHONDONTWRITEBYTECODE': '1',
56+
'PYTHONNOUSERSITE': '1',
57+
'PYTHONUNBUFFERED': '1',
58+
'LC_CTYPE': 'UTF-8',
59+
'PYTHONHOME': exeDir,
60+
'PYTHONPATH': pythonPaths.join(';'),
61+
...?environmentVariables,
3762
};
38-
return await methodChannel.invokeMethod<String>('runPython', arguments);
63+
64+
final rc = runPython(
65+
bridge: DartBridge.instance,
66+
appPath: script == null ? appPath : null,
67+
script: script,
68+
modulePaths: pythonPaths,
69+
environmentVariables: env,
70+
sync: sync ?? false,
71+
);
72+
73+
// sync=true: rc is the Python exit code. sync=false: rc is the spawn
74+
// result (0 = worker thread started successfully).
75+
return rc != 0 ? 'Python exited with code $rc' : null;
3976
}
4077
}

src/serious_python_windows/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies:
1414
plugin_platform_interface: ^2.1.8
1515
serious_python_platform_interface:
1616
path: ../serious_python_platform_interface
17+
path: ^1.9.0
1718

1819
dev_dependencies:
1920
flutter_test:

src/serious_python_windows/windows/CMakeLists.txt

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ else()
1212
set(PYTHON_VERSION "3.14")
1313
endif()
1414
string(REPLACE "." "" PYTHON_VERSION_NODOT "${PYTHON_VERSION}")
15+
if(DEFINED ENV{DART_BRIDGE_VERSION})
16+
set(DART_BRIDGE_VERSION "$ENV{DART_BRIDGE_VERSION}")
17+
else()
18+
set(DART_BRIDGE_VERSION "1.2.1")
19+
endif()
1520
project(${PROJECT_NAME} LANGUAGES CXX)
1621

1722
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
@@ -22,6 +27,11 @@ cmake_policy(VERSION 3.14...3.25)
2227
# not be changed
2328
set(PLUGIN_NAME "serious_python_windows_plugin")
2429

30+
# ---- python-windows-for-dart (runtime DLLs + stdlib) ----------------------
31+
# python3X.dll, python3.dll, Lib/, DLLs/ — required at runtime so the embedded
32+
# CPython (driven by libdart_bridge.dll) can find its stdlib and the abi3 stub
33+
# python3.dll can forward to python3X.dll. We no longer need the .lib / headers
34+
# here because no Python C API call sites live in this plugin anymore.
2535
set(PYTHON_PACKAGE ${CMAKE_BINARY_DIR}/python)
2636
set(PYTHON_URL "https://github.com/flet-dev/python-build/releases/download/v${PYTHON_VERSION}/python-windows-for-dart-${PYTHON_VERSION}.zip")
2737
set(PYTHON_FILE ${CMAKE_BINARY_DIR}/python-windows-for-dart.zip)
@@ -30,6 +40,26 @@ if (NOT EXISTS ${PYTHON_FILE})
3040
file(ARCHIVE_EXTRACT INPUT ${PYTHON_FILE} DESTINATION ${PYTHON_PACKAGE})
3141
endif()
3242

43+
# ---- dart_bridge prebuilt DLLs --------------------------------------------
44+
# Release CRT (.dll) is what Release Flutter builds consume; Debug CRT
45+
# (_d.dll) is what `fvm flutter run` / Debug builds consume. Both ship in
46+
# every release of flet-dev/dart-bridge; we bundle both and let Flutter's
47+
# CONFIG-aware copy machinery pick the right one at app launch time.
48+
set(DART_BRIDGE_DIR ${CMAKE_BINARY_DIR}/dart_bridge)
49+
set(DART_BRIDGE_RELEASE_DLL "${DART_BRIDGE_DIR}/dart_bridge.dll")
50+
set(DART_BRIDGE_DEBUG_DLL "${DART_BRIDGE_DIR}/dart_bridge_d.dll")
51+
file(MAKE_DIRECTORY ${DART_BRIDGE_DIR})
52+
if(NOT EXISTS "${DART_BRIDGE_RELEASE_DLL}")
53+
file(DOWNLOAD
54+
"https://github.com/flet-dev/dart-bridge/releases/download/v${DART_BRIDGE_VERSION}/dart_bridge-windows-x86_64.dll"
55+
"${DART_BRIDGE_RELEASE_DLL}")
56+
endif()
57+
if(NOT EXISTS "${DART_BRIDGE_DEBUG_DLL}")
58+
file(DOWNLOAD
59+
"https://github.com/flet-dev/dart-bridge/releases/download/v${DART_BRIDGE_VERSION}/dart_bridge_d-windows-x86_64.dll"
60+
"${DART_BRIDGE_DEBUG_DLL}")
61+
endif()
62+
3363
# Any new source files that you add to the plugin should be added here.
3464
list(APPEND PLUGIN_SOURCES
3565
"serious_python_windows_plugin.cpp"
@@ -62,23 +92,16 @@ target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
6292
target_include_directories(${PLUGIN_NAME} INTERFACE
6393
"${CMAKE_CURRENT_SOURCE_DIR}/include")
6494

65-
include_directories(
66-
"${PYTHON_PACKAGE}/include"
67-
)
68-
6995
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
7096

71-
target_link_libraries(${PLUGIN_NAME} PRIVATE
72-
"${PYTHON_PACKAGE}/libs/python${PYTHON_VERSION_NODOT}$<$<CONFIG:Debug>:_d>.lib"
73-
)
74-
7597
# List of absolute paths to libraries that should be bundled with the plugin.
7698
# This list could contain prebuilt libraries, or libraries created by an
7799
# external build triggered from this build file.
78100
string(REPLACE "\\" "/" SERIOUS_PYTHON_WINDIR "$ENV{WINDIR}")
79101
set(serious_python_windows_bundled_libraries
80102
"${PYTHON_PACKAGE}/python${PYTHON_VERSION_NODOT}$<$<CONFIG:Debug>:_d>.dll"
81103
"${PYTHON_PACKAGE}/python3$<$<CONFIG:Debug>:_d>.dll"
104+
"$<IF:$<CONFIG:Debug>,${DART_BRIDGE_DEBUG_DLL},${DART_BRIDGE_RELEASE_DLL}>"
82105
"${SERIOUS_PYTHON_WINDIR}/System32/msvcp140.dll"
83106
"${SERIOUS_PYTHON_WINDIR}/System32/vcruntime140.dll"
84107
"${SERIOUS_PYTHON_WINDIR}/System32/vcruntime140_1.dll"
@@ -90,10 +113,10 @@ add_custom_target(CopyPythonDLLs ALL DEPENDS PYTHON_PACKAGE_DOWNLOAD)
90113
add_custom_command(TARGET CopyPythonDLLs POST_BUILD
91114
COMMAND ${CMAKE_COMMAND} -E make_directory
92115
"${CMAKE_BINARY_DIR}/runner/$<$<CONFIG:Release>:Release>$<$<CONFIG:Debug>:Debug>"
93-
COMMAND ${CMAKE_COMMAND} -E copy_directory
116+
COMMAND ${CMAKE_COMMAND} -E copy_directory
94117
"${PYTHON_PACKAGE}/Lib"
95118
"${CMAKE_BINARY_DIR}/runner/$<$<CONFIG:Release>:Release>$<$<CONFIG:Debug>:Debug>/Lib"
96-
COMMAND ${CMAKE_COMMAND} -E copy_directory
119+
COMMAND ${CMAKE_COMMAND} -E copy_directory
97120
"${PYTHON_PACKAGE}/DLLs"
98121
"${CMAKE_BINARY_DIR}/runner/$<$<CONFIG:Release>:Release>$<$<CONFIG:Debug>:Debug>/DLLs"
99122
COMMAND IF \"$<$<CONFIG:Release>:release>\" == \"release\" DEL \"${CMAKE_BINARY_DIR}/runner/Release/DLLs\\*_d.*\" /S /Q

0 commit comments

Comments
 (0)