diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2648d55..f1f7b112 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,8 +17,14 @@ env: jobs: macos: - name: Test on macOS + name: Test on macOS (Python ${{ matrix.python_version }}) runs-on: macos-latest + strategy: + fail-fast: false + matrix: + python_version: ['3.12', '3.13', '3.14'] + env: + SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -32,12 +38,18 @@ jobs: - name: Run tests working-directory: "src/serious_python/example/flet_example" run: | - dart run serious_python:main package app/src --platform Darwin --requirements flet==0.28.3 - flutter test integration_test --device-id macos + dart run serious_python:main package app/src --platform Darwin --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 + flutter test integration_test --device-id macos --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} ios: - name: Test on iOS + name: Test on iOS (Python ${{ matrix.python_version }}) runs-on: macos-latest + strategy: + fail-fast: false + matrix: + python_version: ['3.12', '3.13', '3.14'] + env: + SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -62,12 +74,18 @@ jobs: - name: Run tests working-directory: "src/serious_python/example/flet_example" run: | - dart run serious_python:main package app/src --platform iOS --requirements flet==0.28.3 - flutter test integration_test --device-id ${{ steps.simulator.outputs.udid }} + dart run serious_python:main package app/src --platform iOS --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 + flutter test integration_test --device-id ${{ steps.simulator.outputs.udid }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} android: - name: Test on Android + name: Test on Android (Python ${{ matrix.python_version }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python_version: ['3.12', '3.13', '3.14'] + env: + SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -115,12 +133,18 @@ jobs: pre-emulator-launch-script: | sdkmanager --list_installed script: | - cd src/serious_python/example/flet_example && dart run serious_python:main package app/src --platform Android --requirements flet==0.28.3 - cd src/serious_python/example/flet_example && flutter test integration_test --device-id emulator-${{ env.EMULATOR_PORT }} + cd src/serious_python/example/flet_example && dart run serious_python:main package app/src --platform Android --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 + cd src/serious_python/example/flet_example && flutter test integration_test --device-id emulator-${{ env.EMULATOR_PORT }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} windows: - name: Test on Windows + name: Test on Windows (Python ${{ matrix.python_version }}) runs-on: windows-latest + strategy: + fail-fast: false + matrix: + python_version: ['3.12', '3.13', '3.14'] + env: + SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -134,15 +158,17 @@ jobs: - name: Run tests working-directory: "src/serious_python/example/flet_example" run: | - dart run serious_python:main package app/src --platform Windows --requirements flet==0.28.3 - flutter test integration_test -d windows + dart run serious_python:main package app/src --platform Windows --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 + flutter test integration_test -d windows --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} linux: - name: Test on Linux ${{ matrix.title }} + name: Test on Linux ${{ matrix.title }} (Python ${{ matrix.python_version }}) runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: + python_version: ['3.12', '3.13', '3.14'] + arch: [arm64, amd64] include: - arch: arm64 runner: ubuntu-24.04-arm @@ -150,6 +176,8 @@ jobs: - arch: amd64 runner: ubuntu-24.04 title: AMD64 + env: + SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -205,8 +233,8 @@ jobs: working-directory: src/serious_python/example/flet_example run: | flutter pub get - dart run serious_python:main package app/src --platform Linux --requirements flet==0.28.3 - xvfb-run flutter test integration_test -d linux + dart run serious_python:main package app/src --platform Linux --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 + xvfb-run flutter test integration_test -d linux --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} publish: name: Publish to pub.dev diff --git a/src/serious_python/CHANGELOG.md b/src/serious_python/CHANGELOG.md index e6bd9308..68ee1513 100644 --- a/src/serious_python/CHANGELOG.md +++ b/src/serious_python/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.0.0 + +* **Breaking change:** the `package` command's default Python is now the latest supported stable (3.14), up from the previously implicit 3.12. Scripts that ran `dart run serious_python:main package …` without `--python-version` will now download CPython 3.14, install 3.14 wheels, and use the matching Pyodide / Android platform tags. Pin explicitly with `--python-version 3.12` (or `SERIOUS_PYTHON_VERSION=3.12`) to preserve the old behavior. +* **Breaking change:** Android `sysconfig.get_platform()` tag format changed from `android-24-arm64-v8a` to `android-24-arm64_v8a` (and similarly for `armeabi-v7a`). The emitted wheel tag (`android_24_arm64_v8a`) is unchanged, but anything reading the raw `sysconfig.get_platform()` string from `sitecustomize.py` should switch separators. +* **Breaking change:** Windows host arch identifier dropped the `-shared` suffix (`x86_64-pc-windows-msvc-shared` → `x86_64-pc-windows-msvc`); follows astral-sh/python-build-standalone, which only publishes the combined (already shared) `install_only_stripped` build. +* Multi-version Python support. The `package` command accepts `--python-version` (or `SERIOUS_PYTHON_VERSION` env var) to select between Python 3.12 / 3.13 / 3.14. The matching CPython-standalone build, Pyodide release, and Emscripten wheel platform tag are looked up from a new `_pythonReleases` table. Adding a future pre-release line (e.g. 3.15 beta) is a one-row append with `prerelease: true`; the Flet CLI uses that flag to keep open-ended `requires-python` specifiers (`>=3.14`) on stable, while still letting `--python-version 3.15` or `==3.15.*` opt in. +* The Emscripten pip platform tag is now derived per Python release (e.g. `pyodide-2024.0-wasm32` for 0.27.7, `pyemscripten-2026.0-wasm32` for 314.0.0a2), via a `pyodide_platform_tag` field in the version registry. The previous static `pyodide-2024.0-wasm32` entry in `platforms["Emscripten"]` has been removed. +* `sitecustomize.py` now shims `platform.android_ver` so the new pip / packaging that ships with python-build-standalone 20260602+ can compute Android wheel tags on Python 3.12 hosts (where `android_ver` didn't exist) and on Python 3.13+ hosts (where it returns `api_level=0` off-device). +* Skip 32-bit Android ABIs (`armeabi-v7a`, `x86`) when Python ≥ 3.13 — PEP 738 dropped 32-bit Android support, and `flet-dev/python-build` no longer publishes those runtimes for those versions. + ## 1.0.1 ### Improvements diff --git a/src/serious_python/README.md b/src/serious_python/README.md index 37767061..6c921c59 100644 --- a/src/serious_python/README.md +++ b/src/serious_python/README.md @@ -4,7 +4,7 @@ A cross-platform plugin for adding embedded Python runtime to your Flutter apps. Serious Python embeds Python runtime into a mobile or desktop Flutter app to run a Python program on a background, without blocking UI. Processing files, working with SQLite databases, calling REST APIs, image processing, ML, AI and other heavy lifting tasks can be conveniently done in Python and run directly on a mobile device. -Build app backend service in Python and host it inside a Flutter app. Flutter app is not directly calling Python functions or modules, but instead communicating with Python environmnent via some API provided by a Python program, such as: REST API, sockets, SQLite database or files. +Build app backend service in Python and host it inside a Flutter app. Flutter app is not directly calling Python functions or modules, but instead communicating with Python environment via some API provided by a Python program, such as: REST API, sockets, SQLite database or files. Serious Python is part of [Flet](https://flet.dev) project - the fastest way to build multi-platform apps in Python. The motivation for building Serious Python was having a re-usable easy-to-use plugin, maintained and supported, to run real-world Python apps, not just "1+2" or "hello world" examples, on iOS or Android devices and hence the name "Serious Python". @@ -16,7 +16,31 @@ Serious Python is part of [Flet](https://flet.dev) project - the fastest way to ### Python versions -* Python 3.12.6 on all platforms. +The plugin can bundle one of several Python releases per build, selected via +the `--python-version X.Y` flag of `serious_python:main package` (or the +`SERIOUS_PYTHON_VERSION` env var picked up by the Android/Darwin/Linux/Windows +plugin build scripts). Defaults to the latest supported version when nothing +is specified. + +| Short | CPython runtime | Pyodide (web) | Pyodide wheel platform tag | +| ----- | --------------- | ------------- | -------------------------- | +| 3.12 | 3.12.13 | 0.27.7 | `pyodide-2024.0-wasm32` | +| 3.13 | 3.13.13 | 0.29.4 | `pyodide-2025.0-wasm32` | +| 3.14 | 3.14.5 | 314.0.0a2 | `pyemscripten-2026.0-wasm32`| + +The default is the latest stable row (currently **3.14**) when neither +`--python-version` nor `SERIOUS_PYTHON_VERSION` is set. When running through +[`flet build`](https://flet.dev/docs/publish/), the same resolution is +applied to `[project].requires-python` in your `pyproject.toml`, so most +users never need to touch this flag directly. + +Source of truth: the `_pythonReleases` map in +[`bin/package_command.dart`](bin/package_command.dart) (Dart side) and +`flet_cli/utils/python_versions.py` (Python side). Adding a new short version +means appending a row to both. Pre-release CPython lines (e.g. 3.15) can be +listed with `prerelease: true` so they're opt-in via explicit +`--python-version 3.15` (or `requires-python = "==3.15.*"`) without becoming +the auto-resolved default. ## Usage @@ -32,11 +56,11 @@ Import Serious Python package into your app: `import 'package:serious_python/serious_python.dart';` -The plugin is built against iOS 12.0, so you might need to update iOS version in `ios/Podfile`: +The plugin is built against iOS 13.0, so you might need to update iOS version in `ios/Podfile`: ```bash # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '13.0' ``` Create an instance of `SeriousPython` class and call its `run()` method: @@ -69,8 +93,16 @@ SeriousPython.run("app/app.zip", modulePaths: ["/absolute/path/to/my/site-packages"]); ``` +By default the Python program runs in a background thread so the Flutter UI stays responsive. Pass `sync: true` to run it on the calling thread instead — useful for short utility programs or when calling from a Dart isolate; long-running synchronous programs will freeze the UI on the main isolate: + +```dart +SeriousPython.run("app/app.zip", sync: true); +``` + ### Packaging Python app +> **Tip:** `serious_python` is also driven automatically by [`flet build`](https://flet.dev/docs/publish/), which threads `--python-version` and friends through for you. The instructions below cover direct standalone usage for non-Flet Flutter apps. + To simplify the packaging of your Python app Serious Python provides a CLI which can be run with the following command: ``` @@ -85,7 +117,7 @@ To package Python files for the specific platform: dart run serious_python:main package app/src -p {platform} ``` -where `{platform}` can be one of the following: `Android`, `iOS`, `macOS`, `Windows`, `Linux` or `Emscripten`. +where `{platform}` can be one of the following: `Android`, `iOS`, `Darwin`, `Windows`, `Linux` or `Emscripten`. (`Darwin` covers both macOS apps and is the value used internally by `platform.system()` in the bundled Python; it is **not** spelled `macOS`.) By default, the command creates `app/app.zip` asset, but you can change its path/name with `--asset` argument: @@ -93,7 +125,40 @@ By default, the command creates `app/app.zip` asset, but you can change its path dart run serious_python:main package --asset assets/myapp.zip app/src -p {platform} ``` -Python app dependencies can be installed with `--requirements` option. The value of `--requirements` option is passed "as is" to `pip` command. For example, `--requirements flet,numpy==2.1.1` will install two requirements directly, or `--requirements -r,requirements.txt` installs deps from `requirements.txt` file. +#### Selecting a Python version + +Pick which CPython line to bundle with `--python-version` (or set +`SERIOUS_PYTHON_VERSION` in the build environment): + +``` +dart run serious_python:main package app/src -p Android --python-version 3.13 -r flet +``` + +Supported short versions today are **3.12**, **3.13**, **3.14**; the default +is the latest stable. The same env var (`SERIOUS_PYTHON_VERSION`) is also +read by each platform plugin's build script (`build.gradle`, the +`serious_python_darwin` podspec, the Linux/Windows `CMakeLists.txt`) when +`flutter build` runs later, so a single `export` covers both the packaging +phase and the Flutter build phase. See the [Python versions](#python-versions) +table above for the matching CPython and Pyodide releases. + +#### Installing requirements + +Python app dependencies are installed with the `--requirements` option (alias `-r`). The value is passed verbatim to `pip`, so any flag pip accepts works. Pass each dependency as its own option to support specifiers that contain commas: + +``` +dart run serious_python:main package app/src -p Darwin \ + -r flet -r 'pandas>=2.2,<3' -r numpy==2.1.1 +``` + +To install from a `requirements.txt` file, pass `-r` three times (twice for pip's own `-r` flag, once more for the file path) so the Dart CLI hands the literal `-r requirements.txt` invocation to pip: + +``` +dart run serious_python:main package app/src -p iOS \ + -r -r -r app/src/requirements.txt +``` + +> The comma-separated form (`--requirements flet,numpy==2.1.1`) was removed in **0.9.2** as a breaking change so dependency specifiers containing `,` (like `pandas>=2.2,<3`) can be expressed; use the per-option form shown above instead. To package for `iOS` and `Android` platforms developer should set `SERIOUS_PYTHON_SITE_PACKAGES` environment variable with a path to a temp directory for installed app packages. The contents of that directory is embedded into app bundle during app compilation. @@ -101,7 +166,7 @@ For example: ``` export SERIOUS_PYTHON_SITE_PACKAGES=$(pwd)/build/site-packages -dart run serious_python:main package app/src -p iOS --requirements -r,app/src/requirements.txt +dart run serious_python:main package app/src -p iOS -r -r -r app/src/requirements.txt ``` For macOS, Linux and Windows app packages are installed into `__pypackages__` inside app package asset zip. @@ -183,8 +248,8 @@ dart run serious_python:main package app/src -p Darwin --verbose ## Examples -[Python REPL with Flask backend](src/serious_python/example/flask_example). +[Python REPL with Flask backend](example/flask_example). -[Flet app](src/serious_python/example/flet_example). +[Flet app](example/flet_example). -[Run Python app](src/serious_python/example/run_example). +[Run Python app](example/run_example). diff --git a/src/serious_python/bin/package_command.dart b/src/serious_python/bin/package_command.dart index b407a1d6..b21541b8 100644 --- a/src/serious_python/bin/package_command.dart +++ b/src/serious_python/bin/package_command.dart @@ -15,11 +15,8 @@ import 'macos_utils.dart' as macos_utils; import 'sitecustomize.dart'; const mobilePyPiUrl = "https://pypi.flet.dev"; -const pyodideRootUrl = "https://cdn.jsdelivr.net/pyodide/v0.27.7/full"; const pyodideLockFile = "pyodide-lock.json"; -const buildPythonVersion = "3.12.9"; -const buildPythonReleaseDate = "20250205"; const defaultSitePackagesDir = "__pypackages__"; const sitePackagesEnvironmentVariable = "SERIOUS_PYTHON_SITE_PACKAGES"; const flutterPackagesFlutterEnvironmentVariable = @@ -27,6 +24,70 @@ const flutterPackagesFlutterEnvironmentVariable = const allowSourceDistrosEnvironmentVariable = "SERIOUS_PYTHON_ALLOW_SOURCE_DISTRIBUTIONS"; +const pythonVersionEnvironmentVariable = "SERIOUS_PYTHON_VERSION"; +const pythonDistReleaseEnvironmentVariable = "SERIOUS_PYTHON_DIST_RELEASE"; +const pyodideVersionEnvironmentVariable = "SERIOUS_PYTHON_PYODIDE_VERSION"; + +const defaultPythonVersion = "3.14"; + +class _PythonRelease { + const _PythonRelease({ + required this.standaloneVersion, + required this.standaloneReleaseDate, + required this.pyodideVersion, + required this.pyodidePlatformTag, + required this.prerelease, + }); + + final String standaloneVersion; + final String standaloneReleaseDate; + final String pyodideVersion; + final String pyodidePlatformTag; + + // When true, this release is supported by `--python-version` but is not + // picked automatically by the default or by `[project].requires-python` + // resolution on the Flet CLI side. Use for beta CPython lines. + final bool prerelease; +} + +// Source of truth for the Python <-> CPython standalone <-> Pyodide mapping. +// Mirror any change here in flet-cli's python_versions.py. +const _pythonReleases = { + "3.12": _PythonRelease( + standaloneVersion: "3.12.13", + standaloneReleaseDate: "20260602", + pyodideVersion: "0.27.7", + pyodidePlatformTag: "pyodide-2024.0-wasm32", + prerelease: false, + ), + "3.13": _PythonRelease( + standaloneVersion: "3.13.13", + standaloneReleaseDate: "20260602", + pyodideVersion: "0.29.4", + pyodidePlatformTag: "pyodide-2025.0-wasm32", + prerelease: false, + ), + "3.14": _PythonRelease( + standaloneVersion: "3.14.5", + standaloneReleaseDate: "20260602", + pyodideVersion: "314.0.0a2", + pyodidePlatformTag: "pyemscripten-2026.0-wasm32", + prerelease: false, + ), + // Add future pre-release CPython lines by setting `prerelease: true`. They + // become opt-in via `--python-version 3.15` (or an explicit + // `requires-python = "==3.15.*"` on the Flet CLI side) without becoming + // the default or matching open-ended `requires-python` specifiers. + // + // "3.15": _PythonRelease( + // standaloneVersion: "3.15.0", + // standaloneReleaseDate: "...", + // pyodideVersion: "...", + // pyodidePlatformTag: "...", + // prerelease: true, + // ), +}; + const platforms = { "iOS": { "iphoneos.arm64": {"tag": "ios-13.0-arm64-iphoneos", "mac_ver": ""}, @@ -40,13 +101,20 @@ const platforms = { } }, "Android": { - "arm64-v8a": {"tag": "android-24-arm64-v8a", "mac_ver": ""}, - "armeabi-v7a": {"tag": "android-24-armeabi-v7a", "mac_ver": ""}, + // The ABI segment uses '_' so that packaging.tags.android_platforms (3.13+ + // pip vendored packaging) — which derives the abi from + // `sysconfig.get_platform().split("-")[-1]` — picks up the full ABI + // (e.g. "arm64_v8a") rather than just the trailing token. + "arm64-v8a": {"tag": "android-24-arm64_v8a", "mac_ver": ""}, + "armeabi-v7a": {"tag": "android-24-armeabi_v7a", "mac_ver": ""}, "x86_64": {"tag": "android-24-x86_64", "mac_ver": ""}, "x86": {"tag": "android-24-x86", "mac_ver": ""} }, "Emscripten": { - "": {"tag": "pyodide-2024.0-wasm32", "mac_ver": ""} + // The actual wheel platform tag is resolved per Python release from + // `_pythonReleases[...].pyodidePlatformTag` (see sitecustomize wiring + // below) since it changes with each Pyodide ABI bump. + "": {"tag": "", "mac_ver": ""} }, "Darwin": { "arm64": {"tag": "", "mac_ver": "arm64"}, @@ -86,6 +154,11 @@ class PackageCommand extends Command { bool _verbose = false; Directory? _buildDir; Directory? _pythonDir; + late String _pythonShortVersion; + late _PythonRelease _release; + + String get _pyodideRootUrl => + "https://cdn.jsdelivr.net/pyodide/v${_release.pyodideVersion}/full"; @override final name = "package"; @@ -99,6 +172,11 @@ class PackageCommand extends Command { allowed: ["iOS", "Android", "Emscripten", "Windows", "Linux", "Darwin"], mandatory: true, help: "Install dependencies for specific platform, e.g. 'Android'."); + argParser.addOption('python-version', + allowed: _pythonReleases.keys.toList(), + help: "Short Python version to bundle (e.g. 3.13). Defaults to " + "\$$pythonVersionEnvironmentVariable env var or " + "'$defaultPythonVersion'."); argParser.addMultiOption('arch', help: "Install dependencies for specific architectures only. Leave empty to install all supported architectures."); @@ -172,6 +250,31 @@ class PackageCommand extends Command { List cleanupPackageFiles = argResults?['cleanup-package-files']; _verbose = argResults?["verbose"]; + _pythonShortVersion = argResults?['python-version'] ?? + Platform.environment[pythonVersionEnvironmentVariable] ?? + defaultPythonVersion; + final baseRelease = _pythonReleases[_pythonShortVersion]; + if (baseRelease == null) { + stderr.writeln( + "Unknown Python version: $_pythonShortVersion. Supported: ${_pythonReleases.keys.join(", ")}"); + exit(2); + } + _release = _PythonRelease( + standaloneVersion: baseRelease.standaloneVersion, + standaloneReleaseDate: + Platform.environment[pythonDistReleaseEnvironmentVariable] ?? + baseRelease.standaloneReleaseDate, + pyodideVersion: + Platform.environment[pyodideVersionEnvironmentVariable] ?? + baseRelease.pyodideVersion, + pyodidePlatformTag: baseRelease.pyodidePlatformTag, + prerelease: baseRelease.prerelease, + ); + final preNote = _release.prerelease ? " — pre-release" : ""; + stdout.writeln( + "Python $_pythonShortVersion$preNote (CPython ${_release.standaloneVersion}, " + "Pyodide ${_release.pyodideVersion})"); + if (path.isRelative(sourceDirPath)) { sourceDirPath = path.join(currentPath, sourceDirPath); } @@ -288,6 +391,14 @@ class PackageCommand extends Command { if (archArg.isNotEmpty && !archArg.contains(arch.key)) { continue; } + // python-build dropped 32-bit Android in 3.13 (PEP 738); the + // platform plugin only bundles arm64-v8a + x86_64 for those + // versions, so installing 32-bit wheels would be wasted work. + if (platform == "Android" && + _pythonShortVersion != "3.12" && + (arch.key == "armeabi-v7a" || arch.key == "x86")) { + continue; + } String? sitePackagesDir; Map? pipEnv; Directory? sitecustomizeDir; @@ -307,10 +418,17 @@ class PackageCommand extends Command { "Configured $platform/${arch.key} platform with sitecustomize.py"); } + // Emscripten's wheel platform tag changes between Pyodide ABI + // bumps (e.g. pyodide-2024.0 -> pyodide-2025.0 -> pyemscripten-2026.0), + // so resolve it from the chosen Python release instead of the + // static `platforms` map. + final platformTag = platform == "Emscripten" + ? _release.pyodidePlatformTag + : arch.value["tag"]!; await File(sitecustomizePath).writeAsString(sitecustomizePy .replaceAll( - "{platform}", arch.value["tag"]!.isNotEmpty ? platform : "") - .replaceAll("{tag}", arch.value["tag"]!) + "{platform}", platformTag.isNotEmpty ? platform : "") + .replaceAll("{tag}", platformTag) .replaceAll("{mac_ver}", arch.value["mac_ver"]!)); // print(File(sitecustomizePath).readAsStringSync()); @@ -552,8 +670,8 @@ class PackageCommand extends Command { Future runPython(List args, {Map? environment}) async { if (_pythonDir == null) { - _pythonDir = Directory( - path.join(_buildDir!.path, "build_python_$buildPythonVersion")); + _pythonDir = Directory(path.join( + _buildDir!.path, "build_python_${_release.standaloneVersion}")); if (!await _pythonDir!.exists()) { await _pythonDir!.create(); @@ -570,11 +688,13 @@ class PackageCommand extends Command { } else if (Platform.isLinux && isArm64) { arch = 'aarch64-unknown-linux-gnu'; } else if (Platform.isWindows) { - arch = 'x86_64-pc-windows-msvc-shared'; + // python-build-standalone dropped the explicit `-shared` MSVC + // variant; the remaining install_only_stripped build is shared. + arch = 'x86_64-pc-windows-msvc'; } var pythonArchiveFilename = - "cpython-$buildPythonVersion+$buildPythonReleaseDate-$arch-install_only_stripped.tar.gz"; + "cpython-${_release.standaloneVersion}+${_release.standaloneReleaseDate}-$arch-install_only_stripped.tar.gz"; var pythonArchivePath = path.join(_buildDir!.path, pythonArchiveFilename); @@ -582,7 +702,7 @@ class PackageCommand extends Command { if (!await File(pythonArchivePath).exists()) { // download Python distr from GitHub final url = - "https://github.com/astral-sh/python-build-standalone/releases/download/$buildPythonReleaseDate/$pythonArchiveFilename"; + "https://github.com/astral-sh/python-build-standalone/releases/download/${_release.standaloneReleaseDate}/$pythonArchiveFilename"; if (_verbose) { verbose( @@ -649,7 +769,7 @@ class PackageCommand extends Command { const htmlFooter = "\n"; var pyodidePackages = - await fetchJsonFromUrl("$pyodideRootUrl/$pyodideLockFile"); + await fetchJsonFromUrl("$_pyodideRootUrl/$pyodideLockFile"); var wheels = Map.from(pyodidePackages["packages"]) ..removeWhere((k, p) => !p["file_name"].endsWith(".whl")); @@ -671,7 +791,7 @@ class PackageCommand extends Command { wheels.forEach((k, p) { if (k == parts[1].toLowerCase()) { links.add( - "${p['file_name']}
"); + "${p['file_name']}
"); } }); return Response.ok(htmlHeader + links.join("\n") + htmlFooter, diff --git a/src/serious_python/bin/sitecustomize.dart b/src/serious_python/bin/sitecustomize.dart index 0dfc0146..8ed6fb1e 100644 --- a/src/serious_python/bin/sitecustomize.dart +++ b/src/serious_python/bin/sitecustomize.dart @@ -41,6 +41,27 @@ if custom_system == "iOS": sys.implementation._multiarch = f"{tag_parts[2]}_{tag_parts[3]}" +if custom_system == "Android": + # Newer pip (vendored packaging >= ~25) computes Android wheel tags from + # `platform.android_ver().api_level`. That API only exists in CPython 3.13+, + # and even there it returns api_level=0 on non-Android hosts. Shim it so + # cross-builds work on any host Python. + AndroidVer = collections.namedtuple( + "AndroidVer", + ["release", "api_level", "manufacturer", "model", "device", "is_emulator"] + ) + + tag_parts = custom_platform.split("-") + try: + _api_level = int(tag_parts[1]) + except (IndexError, ValueError): + _api_level = 24 + + def custom_android_ver(): + return AndroidVer("", _api_level, "", "", "", False) + + platform.android_ver = custom_android_ver + orig_platform_version = platform.version platform.version = lambda: orig_platform_version() + ";embedded" """; diff --git a/src/serious_python/example/flet_example/app/app.zip.hash b/src/serious_python/example/flet_example/app/app.zip.hash index 4a9ba080..4ef49a27 100644 --- a/src/serious_python/example/flet_example/app/app.zip.hash +++ b/src/serious_python/example/flet_example/app/app.zip.hash @@ -1 +1 @@ -2ce2f5ff38bcf342e42fb77b2bc8e4262d90d73d5bc3284055242c6313cbe5c0 \ No newline at end of file +c70dd39b2f894beb08c977300587fe6938c3a02649598f13b80d68d998d9cc07 \ No newline at end of file diff --git a/src/serious_python/example/flet_example/app/src/main.py b/src/serious_python/example/flet_example/app/src/main.py index 4abac528..2d4eec94 100644 --- a/src/serious_python/example/flet_example/app/src/main.py +++ b/src/serious_python/example/flet_example/app/src/main.py @@ -20,6 +20,12 @@ def main(page: ft.Page): page.title = "Flet counter example" page.vertical_alignment = ft.MainAxisAlignment.CENTER + py_ver = sys.version_info + python_version_text = ( + f"Python version: {py_ver.major}.{py_ver.minor}.{py_ver.micro}" + ) + print(python_version_text) + txt_number = ft.TextField(value="0", text_align=ft.TextAlign.RIGHT, width=100) def minus_click(e): @@ -63,6 +69,7 @@ def check_ssl(e): ), ft.Text(f"App data dir: {app_data}"), ft.Text(f"App temp dir: {app_temp}"), + ft.Text(python_version_text), ) print("This is inside main() method!") diff --git a/src/serious_python/example/flet_example/integration_test/app_test.dart b/src/serious_python/example/flet_example/integration_test/app_test.dart index 880d75ff..254a6c73 100644 --- a/src/serious_python/example/flet_example/integration_test/app_test.dart +++ b/src/serious_python/example/flet_example/integration_test/app_test.dart @@ -37,6 +37,27 @@ void main() { await tester.tap(decrementButton); await tester.pumpAndSettle(); expect(find.text('-1'), findsOneWidget); + + // Verify the bundled Python runtime matches what CI requested. Skipped + // outside CI (no --dart-define). + const expectedPyVersion = + String.fromEnvironment('EXPECTED_PYTHON_VERSION'); + if (expectedPyVersion.isNotEmpty) { + bool versionFound = false; + for (int i = 0; i < 20; i++) { + await tester.pump(const Duration(seconds: 1)); + if (find + .textContaining('Python version: $expectedPyVersion.') + .evaluate() + .isNotEmpty) { + versionFound = true; + break; + } + } + expect(versionFound, isTrue, + reason: + 'Expected `Python version: $expectedPyVersion.x` in the app UI'); + } }); }); } diff --git a/src/serious_python/example/run_example/app/src/requirements.txt b/src/serious_python/example/run_example/app/src/requirements.txt index 7576bb83..50630e5d 100644 --- a/src/serious_python/example/run_example/app/src/requirements.txt +++ b/src/serious_python/example/run_example/app/src/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.26.4 +numpy #lru-dict #pyjnius #flet-libsodium diff --git a/src/serious_python/pubspec.yaml b/src/serious_python/pubspec.yaml index c94d565f..84671715 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: 1.0.1 +version: 2.0.0 platforms: ios: diff --git a/src/serious_python_android/CHANGELOG.md b/src/serious_python_android/CHANGELOG.md index e1ce0c7e..3c8afb96 100644 --- a/src/serious_python_android/CHANGELOG.md +++ b/src/serious_python_android/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.0.0 + +* **Breaking change:** default bundled Python version is now 3.14 (was 3.12). Apps built without an explicit `SERIOUS_PYTHON_VERSION` env var pull the 3.14 python-build distribution and ship `libpython3.14.so`. Set `SERIOUS_PYTHON_VERSION=3.12` (typically threaded through `flet build`) to preserve the previous default. +* Multi-version Python support. `python_version` in `android/build.gradle` reads from `SERIOUS_PYTHON_VERSION` and drives the `flet-dev/python-build` download URL. +* The Dart runtime no longer hardcodes `libpython3.12.so` — it scans `nativeLibraryDir` for `libpython3.*.so` so whichever libpython the plugin bundled is loaded automatically. +* `abiFilters` now branches on `python_version`: keep `armeabi-v7a` for 3.12, restrict to `arm64-v8a` + `x86_64` for 3.13+ (python-build dropped 32-bit Android per PEP 738). + ## 1.0.1 ### Improvements diff --git a/src/serious_python_android/android/build.gradle b/src/serious_python_android/android/build.gradle index f69d1556..edcaef5c 100644 --- a/src/serious_python_android/android/build.gradle +++ b/src/serious_python_android/android/build.gradle @@ -1,7 +1,7 @@ group 'com.flet.serious_python_android' -version '1.0.1' +version '2.0.0' -def python_version = '3.12' +def python_version = System.getenv('SERIOUS_PYTHON_VERSION') ?: '3.14' buildscript { repositories { @@ -57,7 +57,13 @@ android { minSdkVersion 21 ndk { - abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' + // python-build dropped 32-bit Android in 3.13 (PEP 738), so the + // python-android-dart--armeabi-v7a tarball only exists for 3.12. + if (python_version == '3.12') { + abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' + } else { + abiFilters 'arm64-v8a', 'x86_64' + } } } diff --git a/src/serious_python_android/lib/serious_python_android.dart b/src/serious_python_android/lib/serious_python_android.dart index 5ce78a06..61b7eaf2 100644 --- a/src/serious_python_android/lib/serious_python_android.dart +++ b/src/serious_python_android/lib/serious_python_android.dart @@ -48,7 +48,25 @@ class SeriousPythonAndroid extends SeriousPythonPlatform { spDebug("Unable to load libpyjni.so library: $e"); } - const pythonSharedLib = "libpython3.12.so"; + // unpack python bundle + final nativeLibraryDir = + await methodChannel.invokeMethod('getNativeLibraryDir'); + spDebug("getNativeLibraryDir: $nativeLibraryDir"); + + // The bundled libpython filename moves with the Python version + // (e.g. libpython3.12.so vs libpython3.13.so), so resolve it by + // scanning nativeLibraryDir rather than hardcoding a constant — the + // plugin only ever bundles one libpython per build. + final libpythonRe = RegExp(r'^libpython3\.\d+\.so$'); + final pythonSharedLib = Directory(nativeLibraryDir!) + .listSync() + .map((e) => p.basename(e.path)) + .firstWhere( + libpythonRe.hasMatch, + orElse: () => throw Exception( + "No libpython3.*.so found in $nativeLibraryDir"), + ); + spDebug("Resolved Python shared library: $pythonSharedLib"); String? getPythonFullVersion() { try { @@ -70,11 +88,6 @@ class SeriousPythonAndroid extends SeriousPythonPlatform { } } - // unpack python bundle - final nativeLibraryDir = - await methodChannel.invokeMethod('getNativeLibraryDir'); - spDebug("getNativeLibraryDir: $nativeLibraryDir"); - var bundlePath = "$nativeLibraryDir/libpythonbundle.so"; var sitePackagesZipPath = "$nativeLibraryDir/libpythonsitepackages.so"; diff --git a/src/serious_python_android/pubspec.yaml b/src/serious_python_android/pubspec.yaml index a87b9ba4..81be5f4d 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: 1.0.1 +version: 2.0.0 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 f3013903..df4e13d5 100644 --- a/src/serious_python_darwin/CHANGELOG.md +++ b/src/serious_python_darwin/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0 + +* **Breaking change:** default bundled Python version is now 3.14 (was 3.12). Apps built without an explicit `SERIOUS_PYTHON_VERSION` env var pull `python-ios-dart-3.14.tar.gz` / `python-macos-dart-3.14.tar.gz` from `flet-dev/python-build`. Set `SERIOUS_PYTHON_VERSION=3.12` to preserve the previous default. +* Multi-version Python support. `python_version` in `serious_python_darwin.podspec` reads from `SERIOUS_PYTHON_VERSION`; `prepare_ios.sh` / `prepare_macos.sh` already took the version as `$1` and download the matching tarballs. + ## 1.0.1 ### Improvements diff --git a/src/serious_python_darwin/darwin/serious_python_darwin.podspec b/src/serious_python_darwin/darwin/serious_python_darwin.podspec index 237b37e5..3be1282b 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 = '1.0.1' + s.version = '2.0.0' 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. @@ -27,7 +27,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.0' - python_version = "3.12" + python_version = ENV['SERIOUS_PYTHON_VERSION'] || "3.14" dist_ios = "dist_ios" dist_macos = "dist_macos" diff --git a/src/serious_python_darwin/pubspec.yaml b/src/serious_python_darwin/pubspec.yaml index 652b826f..e2b781e2 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: 1.0.1 +version: 2.0.0 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 3b66113e..04fc548a 100644 --- a/src/serious_python_linux/CHANGELOG.md +++ b/src/serious_python_linux/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0 + +* **Breaking change:** default bundled Python version is now 3.14 (was 3.12). The plugin downloads `python-linux-dart-3.14-.tar.gz` from `flet-dev/python-build` and bundles `libpython3.14.so.1.0` unless `SERIOUS_PYTHON_VERSION=3.12` is set in the build environment. +* Multi-version Python support. `PYTHON_VERSION` in `linux/CMakeLists.txt` reads from `SERIOUS_PYTHON_VERSION`, and all `python3.12` / `libpython3.12.so.1.0` / `lib/python3.12` paths are derived from it. The plugin source receives the version via a `SERIOUS_PYTHON_VERSION` compile-time macro so the runtime module path matches the bundled distro. + ## 1.0.1 ### Improvements diff --git a/src/serious_python_linux/linux/CMakeLists.txt b/src/serious_python_linux/linux/CMakeLists.txt index d371a5d0..89f6eb65 100644 --- a/src/serious_python_linux/linux/CMakeLists.txt +++ b/src/serious_python_linux/linux/CMakeLists.txt @@ -8,7 +8,11 @@ cmake_minimum_required(VERSION 3.10) # Project-level configuration. set(PROJECT_NAME "serious_python_linux") -set(PYTHON_VERSION "3.12") +if(DEFINED ENV{SERIOUS_PYTHON_VERSION}) + set(PYTHON_VERSION "$ENV{SERIOUS_PYTHON_VERSION}") +else() + set(PYTHON_VERSION "3.14") +endif() set(PYTHON_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR}) project(${PROJECT_NAME} LANGUAGES CXX) @@ -46,6 +50,8 @@ apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_compile_definitions(${PLUGIN_NAME} PRIVATE + SERIOUS_PYTHON_VERSION="${PYTHON_VERSION}") # Source include directories and library dependencies. Add any plugin-specific # dependencies here. @@ -53,7 +59,7 @@ target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include" ) include_directories( - "${PYTHON_PACKAGE}/include/python3.12" + "${PYTHON_PACKAGE}/include/python${PYTHON_VERSION}" ) target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) @@ -67,11 +73,11 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE # external build triggered from this build file. set(serious_python_linux_bundled_libraries "${PYTHON_PACKAGE}/lib/libpython3.so" - "${PYTHON_PACKAGE}/lib/libpython3.12.so.1.0" + "${PYTHON_PACKAGE}/lib/libpython${PYTHON_VERSION}.so.1.0" PARENT_SCOPE ) -install(DIRECTORY "${PYTHON_PACKAGE}/lib/python3.12" +install(DIRECTORY "${PYTHON_PACKAGE}/lib/python${PYTHON_VERSION}" DESTINATION "${CMAKE_BINARY_DIR}/bundle") install(CODE " diff --git a/src/serious_python_linux/linux/serious_python_linux_plugin.cc b/src/serious_python_linux/linux/serious_python_linux_plugin.cc index af45cb5d..33d4970d 100644 --- a/src/serious_python_linux/linux/serious_python_linux_plugin.cc +++ b/src/serious_python_linux/linux/serious_python_linux_plugin.cc @@ -11,6 +11,14 @@ #include +// Defined by linux/CMakeLists.txt to match the bundled libpython +// (e.g. "3.13"). The fallback only matters if this file is ever compiled +// outside the plugin's own CMake — defaults to the same version CMakeLists +// falls back to when SERIOUS_PYTHON_VERSION env var is unset. +#ifndef SERIOUS_PYTHON_VERSION +#define SERIOUS_PYTHON_VERSION "3.14" +#endif + #define SERIOUS_PYTHON_LINUX_PLUGIN(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj), serious_python_linux_plugin_get_type(), \ SeriousPythonLinuxPlugin)) @@ -102,7 +110,8 @@ static void serious_python_linux_plugin_handle_method_call( module_paths_str_array[i++] = g_strdup_printf("%s", app_dir); module_paths_str_array[i++] = g_strdup_printf("%s/__pypackages__", app_dir); module_paths_str_array[i++] = g_strdup_printf("%s/site-packages", exe_dir); - module_paths_str_array[i++] = g_strdup_printf("%s/python3.12", exe_dir); + module_paths_str_array[i++] = + g_strdup_printf("%s/python" SERIOUS_PYTHON_VERSION, exe_dir); module_paths_str_array[i++] = NULL; gchar *module_paths_str = g_strjoinv(":", module_paths_str_array); // join with comma and space as separators diff --git a/src/serious_python_linux/pubspec.yaml b/src/serious_python_linux/pubspec.yaml index 58d747f6..9a834cb7 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: 1.0.1 +version: 2.0.0 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 8a455485..c9131d19 100644 --- a/src/serious_python_platform_interface/CHANGELOG.md +++ b/src/serious_python_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.0 + +* Version bump aligning with the major release of the `serious_python_*` platform plugins (multi-version Python support, default Python now 3.14). No interface changes. + ## 1.0.1 ### Improvements diff --git a/src/serious_python_platform_interface/pubspec.yaml b/src/serious_python_platform_interface/pubspec.yaml index a1110033..22dbe9ee 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: 1.0.1 +version: 2.0.0 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 f9b02434..ca8b7b90 100644 --- a/src/serious_python_windows/CHANGELOG.md +++ b/src/serious_python_windows/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0 + +* **Breaking change:** default bundled Python version is now 3.14 (was 3.12). The plugin downloads `python-windows-for-dart-3.14.zip` and bundles `python314.dll` / `python314.lib` unless `SERIOUS_PYTHON_VERSION=3.12` is set in the build environment. +* Multi-version Python support. `PYTHON_VERSION` in `windows/CMakeLists.txt` reads from `SERIOUS_PYTHON_VERSION`; the `python-build` download URL and the `python312.lib` / `python312.dll` filenames are derived from it (e.g. `python313.lib`, `python314.dll`). + ## 1.0.1 ### Improvements diff --git a/src/serious_python_windows/pubspec.yaml b/src/serious_python_windows/pubspec.yaml index 11899519..f41422dd 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: 1.0.1 +version: 2.0.0 environment: sdk: '>=3.1.3 <4.0.0' diff --git a/src/serious_python_windows/windows/CMakeLists.txt b/src/serious_python_windows/windows/CMakeLists.txt index 17046ec6..53910244 100644 --- a/src/serious_python_windows/windows/CMakeLists.txt +++ b/src/serious_python_windows/windows/CMakeLists.txt @@ -6,6 +6,12 @@ cmake_minimum_required(VERSION 3.14) # Project-level configuration. set(PROJECT_NAME "serious_python_windows") +if(DEFINED ENV{SERIOUS_PYTHON_VERSION}) + set(PYTHON_VERSION "$ENV{SERIOUS_PYTHON_VERSION}") +else() + set(PYTHON_VERSION "3.14") +endif() +string(REPLACE "." "" PYTHON_VERSION_NODOT "${PYTHON_VERSION}") project(${PROJECT_NAME} LANGUAGES CXX) # Explicitly opt in to modern CMake behaviors to avoid warnings with recent @@ -17,7 +23,7 @@ cmake_policy(VERSION 3.14...3.25) set(PLUGIN_NAME "serious_python_windows_plugin") set(PYTHON_PACKAGE ${CMAKE_BINARY_DIR}/python) -set(PYTHON_URL "https://github.com/flet-dev/python-build/releases/download/v3.12/python-windows-for-dart-3.12.zip") +set(PYTHON_URL "https://github.com/flet-dev/python-build/releases/download/v${PYTHON_VERSION}/python-windows-for-dart-${PYTHON_VERSION}.zip") set(PYTHON_FILE ${CMAKE_BINARY_DIR}/python-windows-for-dart.zip) if (NOT EXISTS ${PYTHON_FILE}) file(DOWNLOAD ${PYTHON_URL} ${PYTHON_FILE}) @@ -63,7 +69,7 @@ include_directories( target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) target_link_libraries(${PLUGIN_NAME} PRIVATE - "${PYTHON_PACKAGE}/libs/python312$<$:_d>.lib" + "${PYTHON_PACKAGE}/libs/python${PYTHON_VERSION_NODOT}$<$:_d>.lib" ) # List of absolute paths to libraries that should be bundled with the plugin. @@ -71,7 +77,7 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE # external build triggered from this build file. string(REPLACE "\\" "/" SERIOUS_PYTHON_WINDIR "$ENV{WINDIR}") set(serious_python_windows_bundled_libraries - "${PYTHON_PACKAGE}/python312$<$:_d>.dll" + "${PYTHON_PACKAGE}/python${PYTHON_VERSION_NODOT}$<$:_d>.dll" "${PYTHON_PACKAGE}/python3$<$:_d>.dll" "${SERIOUS_PYTHON_WINDIR}/System32/msvcp140.dll" "${SERIOUS_PYTHON_WINDIR}/System32/vcruntime140.dll"