From 6f4ca31a22de6e0a993341762078158218393520 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Mon, 29 Jun 2026 23:43:05 +0200 Subject: [PATCH 1/3] initial commit --- setup.sh | 79 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/setup.sh b/setup.sh index b911bd0..3e773e5 100755 --- a/setup.sh +++ b/setup.sh @@ -34,7 +34,7 @@ if ! command -v uv &> /dev/null; then fi # Pinned flet-dev/python-build release to consume (date-keyed YYYYMMDD, PBS-style). -PYTHON_BUILD_RELEASE="${PYTHON_BUILD_RELEASE:-20260614}" +PYTHON_BUILD_RELEASE="${PYTHON_BUILD_RELEASE:-20260629}" # Resolve a full X.Y.Z from a bare X.Y minor using the pinned release's # manifest.json (downloaded + cached under downloads/). Echoes the full version; @@ -74,6 +74,45 @@ resolve_full_version() { echo "$full" } +# Echo the space-separated Android ABI set for a bare X.Y minor, read from the same +# pinned-release manifest (`pythons..android_abis`) — the single source of truth +# shared with python-build's own build-all.sh / packaging. Same 3-tier parser as +# resolve_full_version (jq -> python3 -> scoped sed) so it works on a bare machine. +# Returns non-zero (empty) if the fetch or lookup fails. +resolve_android_abis() { + local minor="$1" + local mf="downloads/python-build-manifest-${PYTHON_BUILD_RELEASE}.json" + local murl="https://github.com/flet-dev/python-build/releases/download/${PYTHON_BUILD_RELEASE}/manifest.json" + + mkdir -p downloads + if [ ! -f "$mf" ]; then + if ! curl -fsSL -o "$mf" "$murl"; then + echo "Failed to fetch python-build manifest ($murl)." >&2 + rm -f "$mf" + return 1 + fi + fi + + local abis="" + if command -v jq >/dev/null 2>&1; then + abis="$(jq -r --arg m "$minor" '.pythons[$m].android_abis // [] | join(" ")' "$mf" 2>/dev/null)" + elif command -v python3 >/dev/null 2>&1; then + abis="$(python3 -c 'import json,sys; print(" ".join(json.load(open(sys.argv[1])).get("pythons",{}).get(sys.argv[2],{}).get("android_abis",[])))' "$mf" "$minor" 2>/dev/null)" + else + # Scope to the minor's block, then to the android_abis array, and collect the + # quoted ABI tokens (dropping the "android_abis" key token itself). + local minor_re="${minor//./[.]}" + abis="$(sed -n "/\"$minor_re\"[[:space:]]*:/,/}/p" "$mf" \ + | sed -n '/"android_abis"/,/]/p' \ + | grep -oE '"[a-z0-9_-]+"' | tr -d '"' \ + | grep -vx 'android_abis' | tr '\n' ' ')" + fi + + [ -n "$abis" ] || return 1 + # Trim trailing whitespace. + echo "$abis" | xargs +} + # Accept either a full X.Y.Z or a bare X.Y minor as $1. Derive the X.Y minor either # way (cut handles both forms); when only the minor is given, resolve the patch from # the pinned release's manifest. A full version is taken as-is (works offline and for @@ -93,6 +132,17 @@ echo "Python version: $PYTHON_VERSION" echo "Python short version: $PYTHON_VER" echo "python-build release: $PYTHON_BUILD_RELEASE" +# Android ABI set for this minor, from the python-build manifest. Overridable via env (e.g. CI validating +# a python-build branch via PYTHON_BUILD_RUN_ID whose ABI set differs from the pinned release). cross.py and +# make_dep_wheels.py read $ANDROID_ABIS and fall back to the same default if it's unset. +ANDROID_ABIS="${ANDROID_ABIS:-$(resolve_android_abis "$PYTHON_VER")}" +if [ -z "$ANDROID_ABIS" ]; then + ANDROID_ABIS="arm64-v8a x86_64 armeabi-v7a" + echo "Warning: manifest has no android_abis for $PYTHON_VER; defaulting to: $ANDROID_ABIS" >&2 +fi +export ANDROID_ABIS +echo "Android ABIs: $ANDROID_ABIS" + # Download (and cache) a mobile-forge Python support package, extracting it # into $2. Source is either the pinned date-keyed ($PYTHON_BUILD_RELEASE) release # on flet-dev/python-build, or a specific Actions run's artifacts when @@ -327,29 +377,15 @@ if [ ! -z "$MOBILE_FORGE_IOS_SUPPORT_PATH" ]; then echo "MOBILE_FORGE_IOS_SUPPORT_PATH: $MOBILE_FORGE_IOS_SUPPORT_PATH" fi -# configure Android paths +# configure Android paths — every ABI the manifest declares for this +# minor must have a device binary in the support tree if [ ! -z "$MOBILE_FORGE_ANDROID_SUPPORT_PATH" ]; then - if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/arm64-v8a/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android arm64-v8a device binary." - return - fi - - if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/x86_64/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android x86_64 device binary." - return - fi - - if [ "$python_version_minor" -lt 13 ]; then - if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/armeabi-v7a/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android armeabi-v7a device binary." - return - fi - - if [ ! -e $MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/x86/python-$PYTHON_VERSION/bin/python$PYTHON_VER ]; then - echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android x86 device binary." + for abi in $ANDROID_ABIS; do + if [ ! -e "$MOBILE_FORGE_ANDROID_SUPPORT_PATH/install/android/$abi/python-$PYTHON_VERSION/bin/python$PYTHON_VER" ]; then + echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH does not appear to contain a Python $PYTHON_VERSION Android $abi device binary." return fi - fi + done echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH: $MOBILE_FORGE_ANDROID_SUPPORT_PATH" fi @@ -362,6 +398,7 @@ if [ -n "${GITHUB_ENV:-}" ]; then { echo "MOBILE_FORGE_IOS_SUPPORT_PATH=$MOBILE_FORGE_IOS_SUPPORT_PATH" echo "MOBILE_FORGE_ANDROID_SUPPORT_PATH=$MOBILE_FORGE_ANDROID_SUPPORT_PATH" + echo "ANDROID_ABIS=$ANDROID_ABIS" } >> "$GITHUB_ENV" fi From ff7ae31f28d82e7d13cdd0ca7627b4cfb5801b6a Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Mon, 29 Jun 2026 23:43:14 +0200 Subject: [PATCH 2/3] updates --- .github/workflows/build-wheels-version.yml | 2 +- make_dep_wheels.py | 13 +++++++++---- src/forge/cross.py | 9 ++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-wheels-version.yml b/.github/workflows/build-wheels-version.yml index c156040..19bc7f0 100644 --- a/.github/workflows/build-wheels-version.yml +++ b/.github/workflows/build-wheels-version.yml @@ -160,7 +160,7 @@ jobs: if [[ "$arch" == "android" ]]; then runner="ubuntu-latest" platform="android" - rust_targets="aarch64-linux-android,arm-linux-androideabi,x86_64-linux-android,i686-linux-android" + rust_targets="aarch64-linux-android,arm-linux-androideabi,x86_64-linux-android" else runner="macos-26" platform="ios" diff --git a/make_dep_wheels.py b/make_dep_wheels.py index 411ae65..a6d7f85 100644 --- a/make_dep_wheels.py +++ b/make_dep_wheels.py @@ -56,9 +56,12 @@ def get_dependencies(os_name): def get_targets(os_name): if os_name == "android": - if sys.version_info[:2] >= (3, 13): - return ["arm64-v8a", "x86_64"] - return ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"] + # ABIs come from python-build's manifest via $ANDROID_ABIS (exported by setup.sh). + # Fall back to the all abis when invoked without setup.sh's environment. + abis = os.environ.get("ANDROID_ABIS") + if abis: + return abis.split() + return ["arm64-v8a", "armeabi-v7a", "x86_64"] return [ "iphoneos.arm64", "iphonesimulator.arm64", @@ -84,7 +87,9 @@ def make_wheel(package, os_name, target): package_version_build = re.search( rf"^{package}: (.*)", versions, re.MULTILINE | re.IGNORECASE )[1] - min_version = re.search(rf"^Min {os_name} version: (.*)", versions, re.MULTILINE | re.IGNORECASE)[1] + min_version = re.search( + rf"^Min {os_name} version: (.*)", versions, re.MULTILINE | re.IGNORECASE + )[1] package_version, package_build = package_version_build.split("-") diff --git a/src/forge/cross.py b/src/forge/cross.py index 697cb64..de35b90 100644 --- a/src/forge/cross.py +++ b/src/forge/cross.py @@ -20,10 +20,13 @@ class CrossVEnv: "watchOS": "4.0", } + # The Android ABI set is python-build's manifest. setup.sh reads it from the pinned + # release's manifest and exports it as $ANDROID_ABIS; this falls back to all 3 abis + # when forge is invoked without sourcing setup.sh. ANDROID_HOST_ARCHS = ( - ("arm64-v8a", "x86_64") - if sys.version_info[:2] >= (3, 13) - else ("arm64-v8a", "armeabi-v7a", "x86_64", "x86") + tuple(os.environ["ANDROID_ABIS"].split()) + if os.environ.get("ANDROID_ABIS") + else ("arm64-v8a", "armeabi-v7a", "x86_64") ) HOST_SDKS = { From 94694ec8970eaf9ec73b1efccdb0149e1ad28137 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Tue, 30 Jun 2026 00:01:39 +0200 Subject: [PATCH 3/3] updates --- make_dep_wheels.py | 4 ++-- setup.sh | 10 +++------- src/forge/cross.py | 5 ++--- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/make_dep_wheels.py b/make_dep_wheels.py index a6d7f85..e7767d0 100644 --- a/make_dep_wheels.py +++ b/make_dep_wheels.py @@ -56,8 +56,8 @@ def get_dependencies(os_name): def get_targets(os_name): if os_name == "android": - # ABIs come from python-build's manifest via $ANDROID_ABIS (exported by setup.sh). - # Fall back to the all abis when invoked without setup.sh's environment. + # setup.sh reads the ABI set from the pinned python-build release's manifest and exports it + # as env var; while falling back to all 3 abis when forge is invoked without sourcing setup.sh. abis = os.environ.get("ANDROID_ABIS") if abis: return abis.split() diff --git a/setup.sh b/setup.sh index 3e773e5..27c9e72 100755 --- a/setup.sh +++ b/setup.sh @@ -75,10 +75,7 @@ resolve_full_version() { } # Echo the space-separated Android ABI set for a bare X.Y minor, read from the same -# pinned-release manifest (`pythons..android_abis`) — the single source of truth -# shared with python-build's own build-all.sh / packaging. Same 3-tier parser as -# resolve_full_version (jq -> python3 -> scoped sed) so it works on a bare machine. -# Returns non-zero (empty) if the fetch or lookup fails. +# pinned python-build release manifest. Returns non-zero (empty) if the fetch or lookup fails. resolve_android_abis() { local minor="$1" local mf="downloads/python-build-manifest-${PYTHON_BUILD_RELEASE}.json" @@ -132,9 +129,8 @@ echo "Python version: $PYTHON_VERSION" echo "Python short version: $PYTHON_VER" echo "python-build release: $PYTHON_BUILD_RELEASE" -# Android ABI set for this minor, from the python-build manifest. Overridable via env (e.g. CI validating -# a python-build branch via PYTHON_BUILD_RUN_ID whose ABI set differs from the pinned release). cross.py and -# make_dep_wheels.py read $ANDROID_ABIS and fall back to the same default if it's unset. +# Android ABI set for this minor, from the python-build manifest. +# Overridable by setting ANDROID_ABIS env var. ANDROID_ABIS="${ANDROID_ABIS:-$(resolve_android_abis "$PYTHON_VER")}" if [ -z "$ANDROID_ABIS" ]; then ANDROID_ABIS="arm64-v8a x86_64 armeabi-v7a" diff --git a/src/forge/cross.py b/src/forge/cross.py index de35b90..b6d7e45 100644 --- a/src/forge/cross.py +++ b/src/forge/cross.py @@ -20,9 +20,8 @@ class CrossVEnv: "watchOS": "4.0", } - # The Android ABI set is python-build's manifest. setup.sh reads it from the pinned - # release's manifest and exports it as $ANDROID_ABIS; this falls back to all 3 abis - # when forge is invoked without sourcing setup.sh. + # setup.sh reads the ABI set from the pinned python-build release's manifest and exports it + # as env var; while falling back to all 3 abis when forge is invoked without sourcing setup.sh. ANDROID_HOST_ARCHS = ( tuple(os.environ["ANDROID_ABIS"].split()) if os.environ.get("ANDROID_ABIS")