Skip to content

ci: cut GitHub Release on tag runs (#225) #346

ci: cut GitHub Release on tag runs (#225)

ci: cut GitHub Release on tag runs (#225) #346

Workflow file for this run

name: CI
on:
push:
branches: ['**']
tags: ['*']
pull_request:
workflow_dispatch:
# Auto-cancel in-flight runs when a new push lands on the same ref.
# Tag builds (refs/tags/*) get a unique-per-run group so release runs
# never cancel each other.
concurrency:
group: ${{ github.workflow }}-${{ startsWith(github.ref, 'refs/tags/') && github.run_id || github.ref }}
cancel-in-progress: true
env:
ROOT: "${{ github.workspace }}"
SCRIPTS: "${{ github.workspace }}/.github/scripts"
SERIOUS_PYTHON_SITE_PACKAGES: "${{ github.workspace }}/site-packages"
SERIOUS_PYTHON_APP: "${{ github.workspace }}/python-app"
UV_PYTHON: "3.12"
# futureware-tech/simulator-action upstream still declares node20 in
# action.yml (no Node 24 release as of v5). Force the runner to execute
# node20-declared JS actions on Node 24 so we don't trip the deprecation
# warning that becomes a hard error on 2026-09-16.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
# Pin flet so the Python side matches the Dart-side flet version. With
# `--only-binary :all:`, pip would otherwise silently downgrade to an
# older flet whose transitive deps (e.g. msgpack) have wheels for the
# target python_version / platform, leaving the two halves mismatched.
FLET_VERSION: "0.85.3"
permissions:
contents: read
jobs:
version_tables_in_sync:
name: Version tables in sync with manifest
# Tags only publish; tests run on branches/PRs before the tag is cut.
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Setup Flutter
uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3
with:
path: '.fvmrc'
cache: true
- name: Regenerate version tables and check for drift
working-directory: src/serious_python
run: |
set -euo pipefail
flutter pub get >/dev/null
dart run serious_python:gen_version_tables
if ! git diff --quiet; then
echo "::error::Generated Python version tables are out of date."
echo "The committed lib/src/python_versions.dart and the four"
echo "python_versions.properties must match python-build's"
echo "manifest.json for the pinned pythonReleaseDate. To fix: edit"
echo "python-build's manifest.json + cut a release, bump"
echo "pythonReleaseDate, then run"
echo " (cd src/serious_python && dart run serious_python:gen_version_tables)"
echo "and commit the regenerated files."
git --no-pager diff
exit 1
fi
bridge_example_macos:
name: Test Bridge example on macOS (${{ matrix.build_system }}, Python ${{ matrix.python_version }})
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
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:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Setup Flutter
uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3
with:
path: '.fvmrc'
cache: true
- name: Cache flet downloads
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
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.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 }}-
# Avoid release-time cache poisoning.
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
- 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" —
# the spawn-then-teardown of bridge_example's embedded Python +
# PythonBridge ports isn't clean enough for VM reuse. One process
# per file sidesteps the issue.
flutter test integration_test/interactivity_test.dart -d macos --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}
flutter test integration_test/throughput_test.dart -d macos --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}
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 (${{ matrix.build_system }}, Python ${{ matrix.python_version }})
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
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:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Setup Flutter
uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3
with:
path: '.fvmrc'
cache: true
- name: Cache flet downloads
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
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.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 }}-
# Avoid release-time cache poisoning.
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
- name: Setup iOS Simulator
id: simulator
uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5 # v5
with:
model: 'iPhone 17 Pro Max'
os: "iOS"
os_version: "26.5"
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"
env:
# Pass the simulator UDID via env rather than interpolating the
# step output straight into the run script (template injection).
SIMULATOR_UDID: ${{ steps.simulator.outputs.udid }}
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
# (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 "$SIMULATOR_UDID" --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}
flutter test integration_test/throughput_test.dart --device-id "$SIMULATOR_UDID" --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}
flutter test integration_test/memory_test.dart --device-id "$SIMULATOR_UDID" --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}
echo "[$(ts)] >>> done"
bridge_example_android:
name: Test Bridge example on Android (Python ${{ matrix.python_version }})
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# x86_64 matches the emulator architecture; only build/install for
# that ABI to keep CI fast.
python_version: ['3.12', '3.13', '3.14']
env:
SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Setup Flutter
uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3
with:
path: '.fvmrc'
cache: true
- name: Cache flet downloads
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
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.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 }}-
# Avoid release-time cache poisoning.
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Gradle cache
# Stay on v5 — v6 moved the caching component to a separate
# `gradle-actions-caching` library under proprietary commercial
# Terms of Use (https://blog.gradle.org/github-actions-for-gradle-v6).
# v5 was the last fully MIT-licensed major and is on Node 24.
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
with:
# Avoid release-time cache poisoning.
cache-disabled: ${{ startsWith(github.ref, 'refs/tags/') }}
- name: AVD cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-bridge
# Avoid release-time cache poisoning.
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
- name: Setup Android Emulator + Run tests
uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0
env:
EMULATOR_PORT: 5554
with:
avd-name: android_emulator
api-level: 33
target: google_atd
arch: x86_64
profile: pixel_5
sdcard-path-or-size: 128M
ram-size: 2048M
disk-size: 4096M
emulator-port: ${{ env.EMULATOR_PORT }}
disable-animations: true
emulator-options: -no-window -noaudio -no-boot-anim -wipe-data -cache-size 1000 -partition-size 8192
pre-emulator-launch-script: |
sdkmanager --list_installed
script: |
cd src/serious_python/example/bridge_example && dart run serious_python:main package app/src --platform Android --python-version ${{ matrix.python_version }}
# Per-file: `flutter test integration_test` (whole dir) reuses one
# VM session and the second file fails to start the app. See
# bridge_example_macos for the discovery.
cd src/serious_python/example/bridge_example && flutter test integration_test/interactivity_test.dart --device-id emulator-${{ env.EMULATOR_PORT }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}
cd src/serious_python/example/bridge_example && flutter test integration_test/throughput_test.dart --device-id emulator-${{ env.EMULATOR_PORT }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}
cd src/serious_python/example/bridge_example && flutter test integration_test/memory_test.dart --device-id emulator-${{ env.EMULATOR_PORT }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}
- name: Diagnostics on failure
if: failure()
shell: bash
run: |
set +e
REPO="$GITHUB_WORKSPACE"
ABI=x86_64
echo "=== serious_python_android/android/src/main/jniLibs/$ABI/ ==="
ls -la "$REPO/src/serious_python_android/android/src/main/jniLibs/$ABI/" 2>/dev/null || echo "(not found)"
echo
echo "=== example/build/app/outputs/apk/debug/*.apk ==="
APK=$(find "$REPO/src/serious_python/example/bridge_example/build" -name "*-debug.apk" 2>/dev/null | head -n1)
echo "APK: $APK"
if [ -n "$APK" ]; then
echo "Native libs inside APK:"
unzip -l "$APK" | grep -E "lib/$ABI/" || echo "(no lib/$ABI/ entries)"
fi
bridge_example_windows:
name: Test Bridge example on Windows (Python ${{ matrix.python_version }})
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
# Explicit pin instead of `windows-latest` to avoid silent image moves.
# `windows-2025-vs2026` is what GitHub redirects `windows-latest` to
# starting 2026-06-15 (Server 2025 + VS 2026).
runs-on: windows-2025-vs2026
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Setup Flutter
uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3
with:
path: '.fvmrc'
cache: true
- name: Cache flet downloads
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
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.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 }}-
# Avoid release-time cache poisoning.
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
- name: Package + run integration test
working-directory: "src/serious_python/example/bridge_example"
shell: bash
run: |
dart run serious_python:main package app/src --platform Windows --python-version ${{ matrix.python_version }}
# bash + pipefail so the test step actually fails when flutter fails
# (default PowerShell shell on windows runners eats the failure when
# piping into tail).
set -o pipefail
# Per-file: `flutter test integration_test` (whole dir) reuses one
# VM session and the second file fails to start the app. See
# bridge_example_macos for the discovery.
flutter test integration_test/interactivity_test.dart -d windows --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} -v 2>&1 | tail -300
flutter test integration_test/throughput_test.dart -d windows --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} -v 2>&1 | tail -300
flutter test integration_test/memory_test.dart -d windows --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} -v 2>&1 | tail -300
- name: Diagnostics on failure
if: failure()
shell: bash
working-directory: "src/serious_python/example/bridge_example"
run: |
set +e
DBG_DIR=build/windows/x64/runner/Debug
echo "=== runner/Debug dir ==="
ls -la $DBG_DIR/ || true
echo
echo "=== dart_bridge*.dll bundled? ==="
ls -la $DBG_DIR/dart_bridge*.dll || echo "(no dart_bridge dll bundled)"
echo
echo "=== runner/Debug/Lib (Python stdlib) ==="
ls $DBG_DIR/Lib | head -20 || true
echo
echo "=== runner/Debug/site-packages ==="
ls -la $DBG_DIR/site-packages/ || true
bridge_example_linux:
name: Test Bridge example on Linux ${{ matrix.title }} (Python ${{ matrix.python_version }})
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
python_version: ['3.12', '3.13', '3.14']
arch: [amd64, arm64]
include:
- arch: amd64
runner: ubuntu-24.04
title: AMD64
- arch: arm64
runner: ubuntu-24.04-arm
title: ARM64
env:
SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Get Flutter version from .fvmrc
uses: kuhnroyal/flutter-fvm-config-action/config@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3
id: fvm-config-action
with:
path: '.fvmrc'
- name: Setup Flutter
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
with:
flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }}
channel: ${{ matrix.arch == 'arm64' && 'master' || 'stable' }}
cache: true
- name: Cache flet downloads
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
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.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 }}-
# Read-only on tag/release builds to avoid cache poisoning.
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}
- name: Install Linux desktop build deps
run: |
sudo apt-get update --allow-releaseinfo-change
sudo apt-get install -y xvfb libgtk-3-dev
if [ "${{ matrix.arch }}" = "amd64" ]; then
sudo apt-get install -y \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly gstreamer1.0-libav
else
sudo apt-get install -y \
clang ninja-build gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly gstreamer1.0-libav
fi
- name: Package + run integration test
working-directory: src/serious_python/example/bridge_example
run: |
flutter pub get
dart run serious_python:main package app/src \
--platform Linux \
--python-version ${{ matrix.python_version }}
set -o pipefail
# Three back-to-back `xvfb-run` invocations race for display
# numbers — the second/third sometimes trips "Xvfb failed to
# start" because the previous Xvfb's display isn't released yet
# (3-of-6 Linux jobs in run 27441055242 hit this). Start one
# Xvfb up front, share DISPLAY across all three test runs.
Xvfb :99 -screen 0 1280x1024x24 >/tmp/xvfb.log 2>&1 &
XVFB_PID=$!
export DISPLAY=:99
trap 'kill $XVFB_PID 2>/dev/null || true' EXIT
# Brief settle so Xvfb is accepting connections before flutter
# test tries to open the window. xdpyinfo isn't in the ubuntu
# runner's default toolchain; a fixed sleep is sufficient.
sleep 1
# Per-file: `flutter test integration_test` (whole dir) reuses one
# VM session and the second file fails to start the app. See
# bridge_example_macos for the discovery.
flutter test integration_test/interactivity_test.dart -d linux --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} -v 2>&1 | tail -300
flutter test integration_test/throughput_test.dart -d linux --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} -v 2>&1 | tail -300
flutter test integration_test/memory_test.dart -d linux --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} -v 2>&1 | tail -300
- name: Diagnostics on failure
if: failure()
working-directory: src/serious_python/example/bridge_example
run: |
set +e
DBG_DIR=build/linux/${{ matrix.arch == 'arm64' && 'arm64' || 'x64' }}/debug/bundle
echo "=== bundle dir ==="
ls -la $DBG_DIR/ 2>/dev/null
echo
echo "=== bundle/lib (bundled shared libs) ==="
ls -la $DBG_DIR/lib/ 2>/dev/null
echo
echo "=== readelf -d on libdart_bridge.so ==="
readelf -d $DBG_DIR/lib/libdart_bridge.so 2>/dev/null | grep -E "NEEDED|libpython" || echo "(libdart_bridge.so not found)"
echo
echo "=== bundle data/flutter_assets/native_assets/linux/ ==="
ls -la $DBG_DIR/data/flutter_assets/native_assets/linux/ 2>/dev/null
echo
echo "=== Python stdlib dir ==="
ls -d $DBG_DIR/python3.* 2>/dev/null | head -5
publish:
name: Publish to pub.dev
needs:
- bridge_example_macos
- bridge_example_ios
- bridge_example_android
- bridge_example_linux
- bridge_example_windows
runs-on: ubuntu-22.04
# The bridge_example_* deps are skipped on tag builds (tags only publish);
# `!cancelled()` lets publish run despite those skipped needs.
if: ${{ !cancelled() && startsWith(github.ref, 'refs/tags/v') }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 0
- name: Setup uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
# Avoid release-time cache poisoning.
enable-cache: ${{ !startsWith(github.ref, 'refs/tags/') }}
- name: Setup Flutter
uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3
with:
path: '.fvmrc'
cache: true
- name: Compute PKG_VER
run: |
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
# Extract the tag name
tag="${GITHUB_REF#refs/tags/}"
# Remove leading "v" if present
PKG_VER="${tag#v}"
else
# Get the latest tag, or fall back to "v0.0.0" if none exist
cv=$(git describe --abbrev=0 2>/dev/null || echo "v0.0.0")
# Remove leading "v" if present
cv=${cv#v}
# Split into major/minor components
major=$(echo "$cv" | cut -d. -f1)
minor=$(echo "$cv" | cut -d. -f2)
# Construct the package version
PKG_VER="${major}.${minor}.${GITHUB_RUN_NUMBER}"
fi
export PKG_VER
echo "PKG_VER=$PKG_VER" | tee -a "$GITHUB_ENV"
- name: Configure pub.dev credentials
run: |
mkdir -p $HOME/.config/dart
echo "${{ secrets.PUB_DEV_TOKEN }}" | base64 --decode > $HOME/.config/dart/pub-credentials.json
- name: Patch pubspec versions
working-directory: "src"
run: |
for pkg in \
"serious_python" \
"serious_python_platform_interface" \
"serious_python_android" \
"serious_python_darwin" \
"serious_python_windows" \
"serious_python_linux"; do
uv run "$SCRIPTS/patch_pubspec.py" "$pkg/pubspec.yaml" "$PKG_VER"
done
- name: Publish packages
run: |
publish_pkg () {
pushd "$1" >/dev/null
dart pub publish --force
popd >/dev/null
}
publish_pkg src/serious_python_platform_interface
sleep 600
publish_pkg src/serious_python_android
publish_pkg src/serious_python_darwin
publish_pkg src/serious_python_windows
publish_pkg src/serious_python_linux
sleep 600
publish_pkg src/serious_python
release:
name: Publish GitHub Release
needs:
- publish
runs-on: ubuntu-latest
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
permissions:
contents: write
steps:
- name: Detect pre-release
id: prerelease
# Dart/pub pre-releases carry a semver hyphen suffix (e.g. v1.2.0-beta.1);
# mark those as GitHub pre-releases.
env:
REF_NAME: ${{ github.ref_name }}
run: |
if [[ "$REF_NAME" == *-* ]]; then
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
else
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
name: ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
prerelease: ${{ steps.prerelease.outputs.is_prerelease == 'true' }}
generate_release_notes: true