Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
58 changes: 48 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ jobs:
fi

bridge_example_macos:
name: Test Bridge example on macOS (Python ${{ matrix.python_version }})
name: Test Bridge example on macOS (${{ matrix.build_system }}, Python ${{ matrix.python_version }})
runs-on: macos-26
strategy:
fail-fast: false
matrix:
python_version: ['3.12', '3.13', '3.14']
build_system: ['cocoapods', 'spm']
env:
SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }}
steps:
Expand All @@ -87,15 +88,34 @@ jobs:
uses: actions/cache@v5
with:
path: ~/.flet/cache
key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }}
key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle.kts', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }}
restore-keys: |
flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-
flet-cache-${{ runner.os }}-${{ runner.arch }}-

- name: Configure ${{ matrix.build_system }}
run: |
if [ "${{ matrix.build_system }}" = "spm" ]; then
flutter config --enable-swift-package-manager
else
flutter config --no-enable-swift-package-manager
fi

- name: Package + run integration test
working-directory: "src/serious_python/example/bridge_example"
run: |
# SPM is the package command's default: it does host-side staging and
# writes the SP_NATIVE_SET cache-bust key (we export it into the build).
# CocoaPods opts out via SERIOUS_PYTHON_DARWIN_SPM=false; the podspec
# prepare_command stages then. (The SPM job leaves the var unset so it
# exercises the default path docs/readme commands use.)
if [ "${{ matrix.build_system }}" = "cocoapods" ]; then
export SERIOUS_PYTHON_DARWIN_SPM=false
fi
dart run serious_python:main package app/src --platform Darwin --python-version ${{ matrix.python_version }}
if [ "${{ matrix.build_system }}" = "spm" ]; then
export SP_NATIVE_SET="$(cat build/.serious_python_spm_key)"
fi
# Each test file is invoked separately. `flutter test integration_test`
# over the directory reuses one VM session, but the second file's
# `runApp()` then trips "Unable to start the app on the device" —
Expand All @@ -107,13 +127,14 @@ jobs:
flutter test integration_test/memory_test.dart -d macos --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}

bridge_example_ios:
name: Test Bridge example on iOS (Python ${{ matrix.python_version }})
name: Test Bridge example on iOS (${{ matrix.build_system }}, Python ${{ matrix.python_version }})
runs-on: macos-26
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
python_version: ['3.12', '3.13', '3.14']
build_system: ['cocoapods', 'spm']
env:
SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }}
steps:
Expand All @@ -130,7 +151,7 @@ jobs:
uses: actions/cache@v5
with:
path: ~/.flet/cache
key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }}
key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle.kts', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }}
restore-keys: |
flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-
flet-cache-${{ runner.os }}-${{ runner.arch }}-
Expand All @@ -145,17 +166,34 @@ jobs:
shutdown_after_job: true
wait_for_boot: true

- name: Configure ${{ matrix.build_system }}
run: |
if [ "${{ matrix.build_system }}" = "spm" ]; then
flutter config --enable-swift-package-manager
else
flutter config --no-enable-swift-package-manager
fi

- name: Package + run integration test
working-directory: "src/serious_python/example/bridge_example"
run: |
ts() { date '+%H:%M:%S'; }
# SPM is the default; CocoaPods opts out (the SPM job leaves the var
# unset to exercise the default path docs/readme commands use).
if [ "${{ matrix.build_system }}" = "cocoapods" ]; then
export SERIOUS_PYTHON_DARWIN_SPM=false
fi
echo "[$(ts)] >>> dart run serious_python:main package"
# certifi is a placeholder requirement: serious_python_darwin's
# sync_site_packages.sh only populates dist_ios/site-xcframeworks
# (which bundle-python-frameworks-ios.sh then requires at build
# time) when iOS-specific site-packages subdirs exist. Empty
# --requirements skips that branch and the build fails.
# (the iOS native C-extensions — embedded by the podspec script_phase
# under CocoaPods, or enumerated into extra-xcframeworks under SPM)
# when iOS-specific site-packages subdirs exist. Empty --requirements
# skips that branch and the build fails.
dart run serious_python:main package app/src --platform iOS --python-version ${{ matrix.python_version }} --requirements certifi
if [ "${{ matrix.build_system }}" = "spm" ]; then
export SP_NATIVE_SET="$(cat build/.serious_python_spm_key)"
fi
echo "[$(ts)] >>> flutter test integration_test (per-file)"
# See macOS job for why each file runs as a separate invocation.
flutter test integration_test/interactivity_test.dart --device-id ${{ steps.simulator.outputs.udid }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}
Expand Down Expand Up @@ -188,7 +226,7 @@ jobs:
uses: actions/cache@v5
with:
path: ~/.flet/cache
key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }}
key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle.kts', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }}
restore-keys: |
flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-
flet-cache-${{ runner.os }}-${{ runner.arch }}-
Expand Down Expand Up @@ -286,7 +324,7 @@ jobs:
uses: actions/cache@v5
with:
path: ~/.flet/cache
key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }}
key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle.kts', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }}
restore-keys: |
flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-
flet-cache-${{ runner.os }}-${{ runner.arch }}-
Expand Down Expand Up @@ -364,7 +402,7 @@ jobs:
uses: actions/cache@v5
with:
path: ~/.flet/cache
key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }}
key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle.kts', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }}
restore-keys: |
flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-
flet-cache-${{ runner.os }}-${{ runner.arch }}-
Expand Down
1 change: 1 addition & 0 deletions src/serious_python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* **New `SeriousPython.prepareApp()`** — materializes the app (Android first-launch unpack) and returns the directory containing its entry point. **`SeriousPython.run()` now takes no `assetPath` argument** (it resolves the app via `prepareApp()`), sets the current directory to a writable per-app data dir (`<application-support>/data`) — not the read-only bundle — so relative file writes / SQLite work, and runs `main.pyc`/`main.py` (or `appFileName`).
* **Breaking change:** the `app.zip` asset convention and the runtime zip-extraction API are removed — `SeriousPython.run("app/app.zip")`, `extractAssetZip`, and `extractFileZip` no longer exist. Repackage with `dart run serious_python:main package <app> -p <platform>` and call `SeriousPython.run()` with no arguments.
* **Android:** the runtime payload moved to `<application-support>/flet/{app, stdlib.zip, sitepackages.zip, extract/}` (resolved via `getApplicationSupportDirectory()`; the custom `getFilesDir` method channel is dropped). User data in the sibling `<application-support>/data` survives app updates.
* **Swift Package Manager (darwin) staging in the `package` command — on by default.** For iOS/macOS the `package` command runs the host-side equivalent of the podspec `prepare_command` (which SPM has no hook for) by resolving `serious_python_darwin`'s `darwin/` dir (`SERIOUS_PYTHON_DARWIN_DIR` override, else the project's `package_config.json`), invoking `prepare_spm.sh`, and writing the `SP_NATIVE_SET` cache-bust key to `build/.serious_python_spm_key` (overridable via `SERIOUS_PYTHON_SPM_KEY_FILE`) for the caller to export into the `flutter build` environment. SPM is Flutter's default darwin integration since 3.44, so this happens by default — set **`SERIOUS_PYTHON_DARWIN_SPM`** to a falsy value (`0`/`false`/`no`/`off`) to opt out and build with CocoaPods (the podspec stages then). See `serious_python_darwin` 4.0.0.

## 3.0.0

Expand Down
83 changes: 83 additions & 0 deletions src/serious_python/bin/package_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ const flutterPackagesFlutterEnvironmentVariable =
"SERIOUS_PYTHON_FLUTTER_PACKAGES";
const allowSourceDistrosEnvironmentVariable =
"SERIOUS_PYTHON_ALLOW_SOURCE_DISTRIBUTIONS";
// Swift Package Manager (darwin) host-side staging. For iOS/macOS the package
// command runs the SPM equivalent of the podspec `prepare_command` — assembling
// the dist and mapping it into the plugin's Package.swift layout — since SPM has
// no pod-install hook. SPM is Flutter's default darwin integration since 3.44, so
// this happens **by default**; set `SERIOUS_PYTHON_DARWIN_SPM` to a falsy value
// (0/false/no/off) to opt out and build with CocoaPods (e.g. `flet build` sets it
// false when the app uses a non-SPM plugin). `SERIOUS_PYTHON_DARWIN_DIR` optionally
// overrides the resolved plugin `darwin/` dir; `SERIOUS_PYTHON_SPM_KEY_FILE` overrides
// where the SP_NATIVE_SET cache-bust key is written for the caller to export into the
// `flutter build` environment.
const darwinSpmEnvironmentVariable = "SERIOUS_PYTHON_DARWIN_SPM";
const darwinDirEnvironmentVariable = "SERIOUS_PYTHON_DARWIN_DIR";
const spmKeyFileEnvironmentVariable = "SERIOUS_PYTHON_SPM_KEY_FILE";

// Python runtime version data — `defaultPythonVersion`, `pythonReleases`, the
// `*EnvironmentVariable` names, `dartBridgeVersion`, `pythonReleaseDate` — lives
Expand Down Expand Up @@ -564,6 +577,17 @@ class PackageCommand extends Command {
}
await appStagingDir.create(recursive: true);
await copyDirectory(tempDir, appStagingDir, tempDir.path, []);

// Swift Package Manager (darwin) host-side staging: the podspec
// prepare_command doesn't run under SPM, so assemble the dist and map it
// into the plugin's Package.swift layout here (app is now staged). SPM is
// Flutter's default darwin integration since 3.44, so this runs **by
// default**; set `SERIOUS_PYTHON_DARWIN_SPM` to a falsy value (0/false/
// no/off) to opt out and build with CocoaPods (the podspec stages then).
if ((platform == "iOS" || platform == "Darwin") &&
!_isFalsy(Platform.environment[darwinSpmEnvironmentVariable])) {
await _stageDarwinSpm(platform, currentPath);
}
}
} catch (e) {
stdout.writeln("Error: $e");
Expand Down Expand Up @@ -643,6 +667,65 @@ class PackageCommand extends Command {
return proc.exitCode;
}

static bool _isFalsy(String? v) =>
v != null && const ["0", "false", "no", "off"].contains(v.toLowerCase());

// Run the darwin SPM staging (prepare_spm.sh: assemble dist + map into the
// plugin's Package.swift layout) and persist the SP_NATIVE_SET cache-bust key
// for `flet build` to export into the `flutter build` environment.
Future<void> _stageDarwinSpm(String platform, String projectPath) async {
final darwinDir = await _resolveDarwinDir(projectPath);
if (darwinDir == null) {
stdout.writeln("SPM staging skipped: could not resolve serious_python_darwin "
"(set $darwinDirEnvironmentVariable or ensure "
".dart_tool/package_config.json is present).");
return;
}
final spmPlatform = platform == "iOS" ? "ios" : "macos";
final script = path.join(darwinDir, "prepare_spm.sh");
stdout.writeln("SPM: staging $spmPlatform via $script");
final result = await Process.run("/bin/sh", [script, spmPlatform],
workingDirectory: darwinDir);
if ((result.stderr as String).isNotEmpty) {
verbose(result.stderr as String);
}
if (result.exitCode != 0) {
throw Exception("prepare_spm.sh failed (exit ${result.exitCode}):\n"
"${result.stderr}");
}
// stage_spm.sh prints the key as its last stdout line.
final key = (result.stdout as String)
.trim()
.split("\n")
.where((l) => l.trim().isNotEmpty)
.last
.trim();
final keyFile = Platform.environment[spmKeyFileEnvironmentVariable] ??
path.join(projectPath, "build", ".serious_python_spm_key");
await File(keyFile).parent.create(recursive: true);
await File(keyFile).writeAsString(key);
stdout.writeln("SPM: SP_NATIVE_SET=$key -> $keyFile");
}

// Resolve serious_python_darwin's `darwin/` directory — an explicit override
// (set by flet) wins, else read the flutter project's package config.
Future<String?> _resolveDarwinDir(String projectPath) async {
final override = Platform.environment[darwinDirEnvironmentVariable];
if (override != null && override.isNotEmpty) return override;
final pc =
File(path.join(projectPath, ".dart_tool", "package_config.json"));
if (!await pc.exists()) return null;
final data = jsonDecode(await pc.readAsString()) as Map<String, dynamic>;
for (final pkg in (data["packages"] as List)) {
if (pkg["name"] == "serious_python_darwin") {
final base = Uri.directory(path.join(projectPath, ".dart_tool"));
final root = base.resolve(pkg["rootUri"] as String).toFilePath();
return path.join(root, "darwin");
}
}
return null;
}

Future<void> zipDirectoryPosix(Directory source, File dest) async {
final encoder = ZipFileEncoder();
encoder.create(dest.path);
Expand Down
7 changes: 0 additions & 7 deletions src/serious_python/example/bridge_example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
PODS:
- Flutter (1.0.0)
- serious_python_darwin (2.0.0):
- Flutter
- FlutterMacOS

DEPENDENCIES:
- Flutter (from `Flutter`)
- serious_python_darwin (from `.symlinks/plugins/serious_python_darwin/darwin`)

EXTERNAL SOURCES:
Flutter:
:path: Flutter
serious_python_darwin:
:path: ".symlinks/plugins/serious_python_darwin/darwin"

SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
serious_python_darwin: 6d58c9837595683a71e20114bdd4607568c98e84

PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e

Expand Down
Loading
Loading