From 407087c01dd9a4b03ae28e8e7b1a14ae08ee2435 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 2 Jul 2026 21:02:14 +0200 Subject: [PATCH 01/10] recipe: tornado 6.5.7 Pure-Python async networking lib with one optional C extension (tornado.speedups, abi3/websocket_mask). Requested in flet#6632 to unblock the jupyter-client dependency leg. build.script_env TORNADO_EXTENSION=1 forces the extension: setup.py marks it optional, so without this a compile failure would silently ship a pure-Python wheel with no .so. No patches; no runtime deps. Full matrix builds green locally (android arm64-v8a/x86_64/armeabi-v7a + iOS device/arm64-sim/x86_64-sim). --- recipes/tornado/meta.yaml | 14 ++++++++++++++ recipes/tornado/test_tornado.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 recipes/tornado/meta.yaml create mode 100644 recipes/tornado/test_tornado.py diff --git a/recipes/tornado/meta.yaml b/recipes/tornado/meta.yaml new file mode 100644 index 0000000..75e5c90 --- /dev/null +++ b/recipes/tornado/meta.yaml @@ -0,0 +1,14 @@ +package: + name: tornado + version: 6.5.7 + +build: + number: 1 + script_env: + # tornado's only native piece is the optional `tornado.speedups` C + # extension (websocket_mask). setup.py marks it + # `optional=(TORNADO_EXTENSION != "1")`, so on any compile failure it + # silently falls back to a pure-Python wheel with no .so — a green build + # that ships nothing native. Force it so a failure aborts the build loudly + # and the on-device test can prove the .so actually loaded. + TORNADO_EXTENSION: "1" diff --git a/recipes/tornado/test_tornado.py b/recipes/tornado/test_tornado.py new file mode 100644 index 0000000..00a3919 --- /dev/null +++ b/recipes/tornado/test_tornado.py @@ -0,0 +1,28 @@ +def test_speedups_extension_loaded(): + """The whole reason tornado needs a recipe is the compiled + `tornado.speedups` abi3 extension. With TORNADO_EXTENSION=1 the pure-Python + fallback is disabled, so a successful import proves the .so cross-compiled + and loaded rather than silently degrading.""" + from tornado import speedups + + assert hasattr(speedups, "websocket_mask") + + +def test_websocket_mask_roundtrip(): + """XOR-masking is an involution: masking twice restores the input. A + 13-byte payload deliberately exercises all three loops in speedups.c — + the 64-bit block, the 32-bit block, and the single-byte tail.""" + from tornado.speedups import websocket_mask + + mask = b"\xa1\xb2\xc3\xd4" + data = b"hello, world!" + assert websocket_mask(mask, websocket_mask(mask, data)) == data + + +def test_ioloop_asyncio_bridge(): + """jupyter-client (the reason this package is requested) drives tornado + through tornado.ioloop, which is a thin wrapper over asyncio. Importing it + pulls tornado.platform.asyncio and confirms that path loads on mobile.""" + import tornado.ioloop + + assert tornado.ioloop.IOLoop is not None From cd4ab1036adc68ac790236c84d839d75d5931a6c Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 2 Jul 2026 21:40:48 +0200 Subject: [PATCH 02/10] recipe: flet-libarrow 24.0.0 (Apache Arrow C++ for pyarrow) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foundation recipe for pyarrow (flet#3275 / #6632): cross-compiles Apache Arrow C++ 24.0.0 into a shared libarrow + libarrow_compute via CMake, driving the NDK/iOS toolchain (Arrow's own CMake has no mobile awareness). Ported from the prior 'pyarrow' branch, which had Android verified on-device. Key cross fixes (see build.sh): ARROW_CPU_FLAG to bypass Arrow's 'Unknown system processor' FATAL_ERROR; SIMD_LEVEL=NONE; SYSTEM zlib (BUNDLED builds host-arch libz that breaks the x86_64-sim slice); iOS -framework CoreFoundation (vendored date/ios.mm); Android date/locale patches (nl_langinfo API24, BUILD_TZ_LIB); iOS dylib de-versioning so flet's per-slice framework relocation works. Component floor = COMPUTE+CSV+JSON+FILESYSTEM+IPC (pyarrow's irreducible set); heavy deps (parquet/dataset/acero/flight/re2/utf8proc/cloud fs) OFF. Improvement over baseline: excluded_arches [armeabi-v7a, x86] — Arrow's CMake 32/64-bit gate makes 32-bit libarrow unconsumable by pyarrow, so don't build it. Built green locally: android arm64-v8a + x86_64, iOS device + arm64-sim + x86_64-sim. libarrow/libarrow_compute .so 16KB-aligned; iOS dylibs unversioned with @rpath install-ids. --- recipes/flet-libarrow/build.sh | 158 ++++++++++++++++++++++++++++++++ recipes/flet-libarrow/meta.yaml | 29 ++++++ 2 files changed, 187 insertions(+) create mode 100755 recipes/flet-libarrow/build.sh create mode 100644 recipes/flet-libarrow/meta.yaml diff --git a/recipes/flet-libarrow/build.sh b/recipes/flet-libarrow/build.sh new file mode 100755 index 0000000..2428e93 --- /dev/null +++ b/recipes/flet-libarrow/build.sh @@ -0,0 +1,158 @@ +#!/bin/bash +# flet-libarrow: Apache Arrow C++ as a shared libarrow, cross-compiled via CMake, +# for the pyarrow recipe. MINIMAL CORE config (IPC only; no compute/parquet/ +# compression) — the proven feasibility set. Arrow's own CMake has no iOS/Android +# awareness, so we drive the toolchain. +# +# Key cross fixes: +# * ARROW_CPU_FLAG= — Arrow's SetupCxxFlags FATAL_ERRORs on +# an unknown CMAKE_SYSTEM_PROCESSOR; under CMAKE_SYSTEM_NAME=iOS CMake leaves +# it empty, so set the flag directly. +# * iOS link: -framework CoreFoundation — Arrow's vendored `date` lib compiles +# ios.mm (Obj-C++) using CoreFoundation for the tzdata. +# * ARROW_SIMD_LEVEL=NONE — avoids -march=armv8-a (Apple clang dislikes it). +set -eu + +# Locate the Arrow C++ source (cpp/), whether or not forge stripped the tarball top dir. +if [ -d cpp ]; then + CPP="$PWD/cpp" +else + CPP="$(find "$PWD" -maxdepth 3 -type d -name cpp -path '*/cpp' | head -1)" +fi +[ -n "${CPP:-}" ] && [ -d "$CPP" ] || { echo "ERROR: cannot find Arrow cpp/ source under $PWD"; exit 1; } +echo "Arrow C++ source: $CPP" + +# Arrow's vendored date/locale code has two Android-only build breakages (the iOS +# path uses ios.mm + CoreFoundation and is fine). Patch them in place: +# (1) musl/strptime.c calls nl_langinfo() in the locale (%c/%x/%X) cases, but +# nl_langinfo is undeclared on Android API 24 (added in API 26) -> guard +# those cases out on Android (Arrow's date parsing uses ISO formats anyway). +# (2) datetime/tz.cpp's Android tzdata path uses init_tzdb() + reads the private +# time_zone::parse_from_android_tzdata(); both the declaration (tz.h:142) and +# the `friend init_tzdb` (tz.h:832) are gated on BUILD_TZ_LIB, which Arrow +# never defines -> define it for the date translation unit on Android. +if [ -n "${NDK_ROOT:-}" ]; then + echo "=== patch Arrow vendored date/locale for Android ===" + perl -0777 -pi -e 's/#ifdef HAVE_LANGINFO/#if defined(HAVE_LANGINFO) && !defined(__ANDROID__)/g' \ + "$CPP/src/arrow/vendored/musl/strptime.c" + perl -0777 -pi -e 's{#include "datetime/visibility.h"}{#define BUILD_TZ_LIB 1\n#include "datetime/visibility.h"}' \ + "$CPP/src/arrow/vendored/datetime.cpp" +fi + +# Map the target arch to Arrow's CPU flag. +case "${HOST_ARCH:-${ANDROID_ABI:-}}" in + arm64*|aarch64) CPU=aarch64 ;; + armeabi*|armv7*) CPU=aarch32 ;; + x86_64|x86) CPU=x86 ;; + *) CPU=aarch64 ;; +esac + +# CMake drives the cross toolchain itself (NDK file / iOS), so keep forge's host +# compiler flags out of the way. +unset CC CXX CFLAGS CPPFLAGS LDFLAGS AR RANLIB STRIP || true + +COMMON_ARGS=( + -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_INSTALL_PREFIX="$PREFIX" + -DCMAKE_INSTALL_LIBDIR=lib + -DARROW_CPU_FLAG="$CPU" + -DARROW_SIMD_LEVEL=NONE + -DARROW_BUILD_SHARED=ON -DARROW_BUILD_STATIC=OFF + -DARROW_IPC=ON + # pyarrow 24 builds 7 UNCONDITIONAL Cython modules (lib, _compute, _csv, + # _feather, _fs, _json, _pyarrow_cpp_tests) and FATAL_ERRORs unless Arrow C++ + # was built with COMPUTE + CSV; it also can't link _json/_fs/_feather without + # JSON + FILESYSTEM + IPC(feather). So COMPUTE+CSV+JSON+FILESYSTEM+IPC is + # pyarrow's true irreducible floor. Feather rides ARROW_IPC; JSON pulls only + # header-only RapidJSON (bundled). Heavy optional deps stay OFF: re2/utf8proc + # (compute string kernels → abseil), parquet/dataset/acero/flight/orc/gandiva, + # and all cloud filesystems (S3/GCS/Azure/HDFS). + -DARROW_COMPUTE=ON -DARROW_CSV=ON -DARROW_JSON=ON -DARROW_PARQUET=OFF + -DARROW_DATASET=OFF -DARROW_ACERO=OFF -DARROW_FLIGHT=OFF -DARROW_GANDIVA=OFF + -DARROW_FILESYSTEM=ON + -DARROW_WITH_BROTLI=OFF -DARROW_WITH_BZ2=OFF -DARROW_WITH_LZ4=OFF + -DARROW_WITH_SNAPPY=OFF -DARROW_WITH_ZLIB=OFF -DARROW_WITH_ZSTD=OFF + -DARROW_WITH_UTF8PROC=OFF -DARROW_WITH_RE2=OFF + -DARROW_MIMALLOC=OFF -DARROW_JEMALLOC=OFF -DARROW_WITH_BACKTRACE=OFF + -DARROW_DEPENDENCY_SOURCE=BUNDLED + # zlib can't actually be turned off here: Arrow force-enables it on Apple + # because the vendored `date` lib decompresses the system tzdata via zlib in + # ios.mm (GZipCodec also pulls it). The BUNDLED zlib_ep builds for the HOST + # arch — fine when host==target, but on an arm64 mac it produces an arm64 + # libz.a that fails to link the x86_64 simulator slice. Use the platform's + # SYSTEM zlib instead: the iOS SDK ships libz.tbd and the Android NDK ships + # libz.so — both correct-arch and present on-device at runtime (no bundling). + -DZLIB_SOURCE=SYSTEM + -DARROW_BUILD_TESTS=OFF -DARROW_BUILD_BENCHMARKS=OFF + -DARROW_BUILD_EXAMPLES=OFF -DARROW_BUILD_UTILITIES=OFF -DARROW_CUDA=OFF +) + +mkdir -p _build && cd _build + +if [ -n "${NDK_ROOT:-}" ]; then + echo "=== configure (Android $ANDROID_ABI) ===" + cmake "$CPP" "${COMMON_ARGS[@]}" \ + -DCMAKE_TOOLCHAIN_FILE="$NDK_ROOT/build/cmake/android.toolchain.cmake" \ + -DANDROID_ABI="$ANDROID_ABI" \ + -DANDROID_PLATFORM="android-$ANDROID_API_LEVEL" \ + -DANDROID_STL=c++_shared \ + -DCMAKE_SHARED_LINKER_FLAGS="-Wl,-z,max-page-size=16384" +else + echo "=== configure (iOS $HOST_ARCH, sysroot $SDK_ROOT) ===" + cmake "$CPP" "${COMMON_ARGS[@]}" \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DCMAKE_SYSTEM_PROCESSOR="$HOST_ARCH" \ + -DCMAKE_OSX_SYSROOT="$SDK_ROOT" \ + -DCMAKE_OSX_ARCHITECTURES="$HOST_ARCH" \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 \ + -DZLIB_INCLUDE_DIR="$SDK_ROOT/usr/include" \ + -DZLIB_LIBRARY="$SDK_ROOT/usr/lib/libz.tbd" \ + -DCMAKE_SHARED_LINKER_FLAGS="-framework CoreFoundation" +fi + +echo "=== build + install libarrow (+ libarrow_compute) ===" +# No --target: build the default ALL target so both arrow_shared and +# arrow_compute_shared (ARROW_COMPUTE=ON) get built. Components are OFF, so ALL +# is just the two Arrow libs + bundled xsimd. +cmake --build . -j "${CPU_COUNT:-4}" +cmake --install . + +# iOS: flet's per-slice framework relocation (serious_python_darwin) cannot handle a +# VERSIONED dylib + its symlinks — xcframework_utils.sh globs "libarrow.*.dylib" AND +# "libarrow.dylib", matching the real file plus BOTH symlinks, then `lipo -create` on +# three copies of one arch fails ("duplicate architecture"), so the simulator +# framework is never built and pyarrow's @rpath link can't resolve at runtime. +# Collapse each arrow dylib to a single UNVERSIONED lib.dylib (the proven +# flet-lib* shape): drop the version symlinks, keep the real file unversioned, +# retarget its install-id + the libarrow_compute->libarrow ref, and repoint Arrow's +# CMake config so pyarrow's find_package(Arrow) links @rpath/lib.dylib. +# Android ships plain .so via jniLibs (no versioning) and is unaffected. +if [ -z "${NDK_ROOT:-}" ]; then + echo "=== de-version arrow dylibs for iOS ===" + for _lib in libarrow libarrow_compute; do + _real="$(find "$PREFIX/lib" -maxdepth 1 -type f -name "$_lib.*.dylib" | head -1)" + [ -n "$_real" ] || continue + mv "$_real" "$PREFIX/lib/$_lib.dylib.tmp" + find "$PREFIX/lib" -maxdepth 1 -name "$_lib.*.dylib" -delete # dangling version symlinks + rm -f "$PREFIX/lib/$_lib.dylib" # unversioned symlink + mv "$PREFIX/lib/$_lib.dylib.tmp" "$PREFIX/lib/$_lib.dylib" + install_name_tool -id "@rpath/$_lib.dylib" "$PREFIX/lib/$_lib.dylib" + done + # libarrow_compute links libarrow by its old versioned @rpath name -> retarget it. + _ref="$(otool -L "$PREFIX/lib/libarrow_compute.dylib" | grep -oE '@rpath/libarrow\.[0-9][0-9.]*\.dylib' | head -1)" + [ -n "${_ref:-}" ] && install_name_tool -change "$_ref" "@rpath/libarrow.dylib" "$PREFIX/lib/libarrow_compute.dylib" + # Repoint Arrow's CMake config (IMPORTED_LOCATION / SONAME) to the unversioned name. + if [ -d "$PREFIX/lib/cmake" ]; then + find "$PREFIX/lib/cmake" -type f -name "*.cmake" -exec sed -i.bak -E 's#(libarrow(_compute)?)\.[0-9][0-9.]*\.dylib#\1.dylib#g' {} \; + find "$PREFIX/lib/cmake" -name "*.bak" -delete + fi + echo "=== de-versioned ==="; ls -la "$PREFIX/lib" | grep -i arrow || true +fi + +# (pyarrow ships its own arrow_python C++ sources — its CMake sets +# PYARROW_CPP_ROOT_DIR=pyarrow/src — so flet-libarrow only needs to provide the +# built libarrow + headers, installed above.) + +echo "=== installed ===" +ls -la "$PREFIX/lib" | grep -i arrow || true diff --git a/recipes/flet-libarrow/meta.yaml b/recipes/flet-libarrow/meta.yaml new file mode 100644 index 0000000..400cacd --- /dev/null +++ b/recipes/flet-libarrow/meta.yaml @@ -0,0 +1,29 @@ +{% set version = "24.0.0" %} +package: + name: flet-libarrow + version: '{{ version }}' + # Only 64-bit slices are shipped: Arrow's CMake configs carry a 32/64-bit gate + # (ArrowConfigVersion.cmake marks PACKAGE_VERSION_UNSUITABLE unless + # sizeof(void*)==8), so a 32-bit pyarrow could not consume this libarrow anyway. + # pyarrow excludes the same arches; building libarrow for them would just be + # unconsumable CI work, so drop them here too. + excluded_arches: [armeabi-v7a, x86] + +build: + number: 1 + +source: + # Apache Arrow C++ (curated source release; build dir = its cpp/). pyarrow + # links this shared libarrow. MINIMAL CORE for now — IPC only, no compute / + # parquet / compression (see build.sh + the feasibility notes). + url: https://archive.apache.org/dist/arrow/arrow-{{ version }}/apache-arrow-{{ version }}.tar.gz + +requirements: + build: + - cmake + - ninja +# {% if sdk == 'android' %} + host: + # libarrow.so is C++; on Android it links libc++_shared.so. + - flet-libcpp-shared >=27.2.12479018 +# {% endif %} From 7536821513f695fd04bf1a83264aa15641c05478 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 2 Jul 2026 21:51:40 +0200 Subject: [PATCH 03/10] recipe: pyarrow 24.0.0 (Apache Arrow Python bindings) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consumes flet-libarrow (libarrow + libarrow_compute) and builds pyarrow's Cython ext modules + arrow_python via scikit-build-core/CMake, cross-compiled for mobile. Unblocks the google-colab-cli / jupyter chain (flet#6632) and the long-standing pyarrow request (flet#3275). Ported from the prior 'pyarrow' branch (Android was on-device verified 5/5). Component set = compute-only floor (lib/_compute/_csv/_feather/_fs/_json); all PYARROW_WITH_* heavy components (parquet/dataset/acero/flight/orc/cuda/cloud-fs) hard-locked OFF via script_env. excluded_arches [armeabi-v7a, x86] (Arrow's CMake 32/64-bit gate). iOS uses the Unix Makefiles generator (ninja pip pkg crashes importing under the iOS crossenv python). Two patches: - arrow-python-optlib.patch: install libarrow_python into the wheel's opt/lib (unversioned) so it rides the same routing as libarrow/libarrow_compute — APK jniLibs on Android, per-slice *.fwork frameworks on iOS. - mobile.patch: pyarrow/__init__ preload shim — on iOS, RTLD_GLOBAL-load the arrow dylibs from their relocated *.fwork frameworks before importing pyarrow.lib so the ext modules' @rpath load-commands bind by install-name. No-op on Android/desktop. Build validated locally, full matrix: android arm64-v8a + x86_64, iOS device + arm64-sim + x86_64-sim. Android linkage confirmed (lib.so DT_NEEDED resolves libarrow/_compute/_python/libc++_shared by basename from jniLibs). iOS on-device runtime (the .fwork preload path) still to be verified — next. --- recipes/pyarrow/meta.yaml | 119 ++++++++++++++++++ .../pyarrow/patches/arrow-python-optlib.patch | 32 +++++ recipes/pyarrow/patches/mobile.patch | 38 ++++++ recipes/pyarrow/tests/test_pyarrow.py | 80 ++++++++++++ 4 files changed, 269 insertions(+) create mode 100644 recipes/pyarrow/meta.yaml create mode 100644 recipes/pyarrow/patches/arrow-python-optlib.patch create mode 100644 recipes/pyarrow/patches/mobile.patch create mode 100644 recipes/pyarrow/tests/test_pyarrow.py diff --git a/recipes/pyarrow/meta.yaml b/recipes/pyarrow/meta.yaml new file mode 100644 index 0000000..c5074be --- /dev/null +++ b/recipes/pyarrow/meta.yaml @@ -0,0 +1,119 @@ +# Map the slice arch to Arrow's CPU flag. Under CMAKE_SYSTEM_NAME=iOS (and even +# under the NDK toolchain) Arrow's SetupCxxFlags can see an empty +# CMAKE_SYSTEM_PROCESSOR and FATAL_ERROR "Unknown system processor"; setting +# ARROW_CPU_FLAG directly bypasses that. 32-bit arches are excluded, so x86_64 -> +# x86 and arm64/arm64-v8a -> aarch64 covers every shipped slice. +# {% set arrow_cpu = 'x86' if 'x86' in arch else 'aarch64' %} +package: + name: pyarrow + version: 24.0.0 + # Arrow's CMake configs carry a 32/64-bit gate (ArrowConfigVersion.cmake: + # PACKAGE_VERSION_UNSUITABLE unless sizeof(void*)==8), so a 64-bit flet-libarrow + # cannot be consumed by a 32-bit pyarrow. Drop the 32-bit Android ABIs. + excluded_arches: [armeabi-v7a, x86] + +requirements: + build: + # pyarrow builds via scikit-build-core + CMake; with forge's --no-isolation + # these must be in the build venv. scikit-build-core, cython>=3.1, libcst, + # numpy and setuptools_scm come from pyarrow's own build-system.requires. + - cmake + - ninja + host: + # Build needs the cross-target NumPy headers (arrow_python links Python3::NumPy); + # numpy is import-guarded at runtime (optional), so it is NOT promoted to + # Requires-Dist (forge only promotes flet-* host deps). + - numpy ^2.0.0 + # flet-libarrow ships libarrow + libarrow_compute in opt/lib. It's a real + # runtime dep on BOTH platforms -> requirements.host (promoted to Requires-Dist, + # ABI-pinned to the libarrow.2400 SONAME) so flet actually installs it into the + # app. Android: serious_python copies opt/lib/*.so into the APK jniLibs, resolved + # by DT_NEEDED basename. iOS: flet relocates each opt/lib dylib into its own + # per-platform *.fwork framework; pyarrow's __init__ preload shim (mobile.patch) + # CDLLs them before importing pyarrow.lib so the ext modules' @rpath link binds + # by install-name. (host_build would NOT ship the wheel -> dylibs never reach + # jniLibs/.fwork, which is why the old iOS bundle approach was needed and failed.) + - flet-libarrow ==24 +# {% if sdk == 'android' %} + # pyarrow's + libarrow's .so are C++ -> need libc++_shared.so at runtime + # (iOS uses the system libc++, no flet-lib needed). + - flet-libcpp-shared >=27.2.12479018 +# {% endif %} + +patches: + # Both platforms: preload the Arrow C++ dylibs from opt/lib (RTLD_GLOBAL) before + # importing pyarrow.lib, so dyld resolves the extension modules' @rpath link by + # install-name after flet relocates each opt/lib dylib into its own *.fwork + # framework on iOS. No-op on Android (jniLibs already resolves) / desktop. + - mobile.patch + # Install pyarrow's own libarrow_python into the wheel's opt/lib (not the package + # dir) so it rides the same routing as libarrow / libarrow_compute: APK jniLibs on + # Android, per-platform *.fwork frameworks on iOS. Avoids a $ORIGIN runpath (CMake + # can't emit one on Android). Guarded by SKBUILD_PLATLIB_DIR (both mobile + # platforms); no-op on desktop. + - arrow-python-optlib.patch + +build: + number: 1 + script_env: + # Belt-and-suspenders: hard-lock every OPTIONAL component OFF regardless of + # what the flet-libarrow Arrow C++ build enables. pyarrow's define_option macro + # honors PYARROW_WITH_ env over Arrow's own option. The MANDATORY modules + # (lib/_compute/_csv/_feather/_fs/_json/_pyarrow_cpp_tests) have no toggle — + # they track the Arrow C++ build (which we build with COMPUTE+CSV+JSON+ + # FILESYSTEM+IPC, all heavier components OFF). + PYARROW_WITH_PARQUET: "0" + PYARROW_WITH_PARQUET_ENCRYPTION: "0" + PYARROW_WITH_DATASET: "0" + PYARROW_WITH_ACERO: "0" + PYARROW_WITH_SUBSTRAIT: "0" + PYARROW_WITH_FLIGHT: "0" + PYARROW_WITH_GANDIVA: "0" + PYARROW_WITH_ORC: "0" + PYARROW_WITH_CUDA: "0" + PYARROW_WITH_S3: "0" + PYARROW_WITH_GCS: "0" + PYARROW_WITH_AZURE: "0" + PYARROW_WITH_HDFS: "0" +# {% if sdk == 'android' %} + # Runtime libs all resolve by DT_NEEDED basename from the APK jniLibs: libarrow / + # libarrow_compute come from flet-libarrow, and pyarrow's own libarrow_python.so + # is relocated into opt/lib by android-arrow-python-optlib.patch so it lands there + # too (no $ORIGIN runpath needed — CMake can't emit one on Android). On-device + # verified (5/5 steps). + CMAKE_ARGS: >- + -DPYARROW_CPP_HOME={purelib}/opt + -DCMAKE_PREFIX_PATH={purelib}/opt + -DARROW_CPU_FLAG={{ arrow_cpu }} + -DARROW_SIMD_LEVEL=NONE + -DCMAKE_TOOLCHAIN_FILE={NDK_ROOT}/build/cmake/android.toolchain.cmake + -DANDROID_ABI={ANDROID_ABI} + -DANDROID_NATIVE_API_LEVEL={ANDROID_API_LEVEL} + -DANDROID_STL=c++_shared + -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/env + -DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=16384 + -DCMAKE_MODULE_LINKER_FLAGS=-Wl,-z,max-page-size=16384 + -DPython3_EXECUTABLE={CROSS_VENV_PYTHON} + -DPython3_INCLUDE_DIR={HOST_PYTHON_HOME}/include/python{py_version_short} + -DPython3_LIBRARY={HOST_PYTHON_HOME}/lib/libpython{py_version_short}.so + -DPython3_NumPy_INCLUDE_DIR={purelib}/numpy/_core/include +# {% else %} + # The `ninja` pip package crashes importing on the iOS crossenv python + # (sysconfig.get_preferred_scheme('user') -> 'posix_user' invalid on iOS), so + # use the Makefiles generator on iOS; scikit-build-core then never imports ninja. + CMAKE_GENERATOR: Unix Makefiles + CMAKE_ARGS: >- + -DPYARROW_CPP_HOME={purelib}/opt + -DCMAKE_PREFIX_PATH={purelib}/opt + -DARROW_CPU_FLAG={{ arrow_cpu }} + -DARROW_SIMD_LEVEL=NONE + -DCMAKE_SYSTEM_NAME=iOS + -DCMAKE_OSX_SYSROOT={{ sdk }} + -DCMAKE_OSX_DEPLOYMENT_TARGET={{ sdk_version }} + -DCMAKE_OSX_ARCHITECTURES={{ arch }} + -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/env + -DPython3_EXECUTABLE={CROSS_VENV_PYTHON} + -DPython3_INCLUDE_DIR={HOST_PYTHON_HOME}/include/python{py_version_short} + -DPython3_LIBRARY={HOST_PYTHON_HOME}/lib/libpython{py_version_short}.dylib + -DPython3_NumPy_INCLUDE_DIR={purelib}/numpy/_core/include +# {% endif %} diff --git a/recipes/pyarrow/patches/arrow-python-optlib.patch b/recipes/pyarrow/patches/arrow-python-optlib.patch new file mode 100644 index 0000000..168973d --- /dev/null +++ b/recipes/pyarrow/patches/arrow-python-optlib.patch @@ -0,0 +1,32 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -530,12 +530,23 @@ + if(CMAKE_CONFIGURATION_TYPES) + target_compile_definitions(arrow_python PRIVATE PYARROW_BUILD_TYPE="$") + endif() +-set_target_properties(arrow_python PROPERTIES VERSION "${PYARROW_FULL_SO_VERSION}" +- SOVERSION "${PYARROW_SO_VERSION}") +-install(TARGETS arrow_python +- ARCHIVE DESTINATION . +- LIBRARY DESTINATION . +- RUNTIME DESTINATION .) ++# mobile-forge: no SO version -> a single unversioned libarrow_python.dylib ++# (versioned dylib + symlinks break flet's iOS lipo framework relocation; ++# matches the flet-lib* single-lib shape). And route the install into the ++# wheel's opt/lib (sibling of the pyarrow package via scikit-build's platlib ++# staging dir) so it rides the same per-platform routing as libarrow / ++# libarrow_compute: APK jniLibs on Android, *.fwork frameworks on iOS. ++if(SKBUILD_PLATLIB_DIR) ++ install(TARGETS arrow_python ++ ARCHIVE DESTINATION "${SKBUILD_PLATLIB_DIR}/opt/lib" ++ LIBRARY DESTINATION "${SKBUILD_PLATLIB_DIR}/opt/lib" ++ RUNTIME DESTINATION "${SKBUILD_PLATLIB_DIR}/opt/lib") ++else() ++ install(TARGETS arrow_python ++ ARCHIVE DESTINATION . ++ LIBRARY DESTINATION . ++ RUNTIME DESTINATION .) ++endif() + + set(PYARROW_CPP_ENCRYPTION_SRCS ${PYARROW_CPP_SOURCE_DIR}/parquet_encryption.cc) + if(NOT PYARROW_BUILD_PARQUET_ENCRYPTION) diff --git a/recipes/pyarrow/patches/mobile.patch b/recipes/pyarrow/patches/mobile.patch new file mode 100644 index 0000000..655520b --- /dev/null +++ b/recipes/pyarrow/patches/mobile.patch @@ -0,0 +1,38 @@ +--- a/pyarrow/__init__.py ++++ b/pyarrow/__init__.py +@@ -56,6 +56,35 @@ + except ImportError: + __version__ = None + ++# mobile-forge (iOS): flet moves each opt/lib arrow dylib into a signed framework ++# under the app bundle and leaves a ".fwork" TEXT marker at opt/lib/.fwork ++# whose content is the app-relative framework path (e.g. ++# "Frameworks/opt.lib.libarrow.framework/opt.lib.libarrow"). Because flet only ++# rewrites the install-id for .so (not .dylib), the arrow frameworks keep their ++# original @rpath/libarrow*.dylib install-id, so RTLD_GLOBAL-preloading each ++# framework binary before importing pyarrow.lib lets the ext modules' direct ++# @rpath load-commands bind by install-name. No-op on Android/desktop (no markers). ++try: ++ import ctypes as _ctypes ++ _optlib = _os.path.join(_os.path.dirname(_os.path.dirname(_os.path.abspath(__file__))), "opt", "lib") ++ for _base in ("libarrow", "libarrow_compute", "libarrow_python"): ++ _fwork = _os.path.join(_optlib, _base + ".fwork") ++ if not _os.path.exists(_fwork): ++ continue ++ _rel = open(_fwork).read().strip() ++ _dir = _optlib ++ for _ in range(12): ++ _dir = _os.path.dirname(_dir) ++ _fb = _os.path.join(_dir, _rel) ++ if _os.path.exists(_fb): ++ try: ++ _ctypes.CDLL(_fb, mode=_ctypes.RTLD_GLOBAL) ++ except OSError: ++ pass ++ break ++except Exception: ++ pass ++ + from pyarrow.lib import (BuildInfo, CppBuildInfo, RuntimeInfo, set_timezone_db_path, + MonthDayNano, VersionInfo, build_info, cpp_build_info, + cpp_version, cpp_version_info, runtime_info, diff --git a/recipes/pyarrow/tests/test_pyarrow.py b/recipes/pyarrow/tests/test_pyarrow.py new file mode 100644 index 0000000..9efb38c --- /dev/null +++ b/recipes/pyarrow/tests/test_pyarrow.py @@ -0,0 +1,80 @@ +"""On-device smoke tests for a compute-only pyarrow (libarrow + libarrow_compute; +no parquet / dataset / acero / flight / re2 / utf8proc, and numpy is absent on the +device). Everything here uses pure-Python value paths (.to_pylist() / .as_py()); +nothing calls .to_numpy()/.to_pandas() or any re2/utf8proc string kernel. + +Tests are ordered so a failure pinpoints the layer: import first (core libarrow), +the compute kernels last (the separate libarrow_compute .so + its rpath).""" + + +def test_import_version(): + """Canary: importing pyarrow loads the `pyarrow.lib` Cython module, which + dynamically links the bundled/sibling libarrow — proof the C++ core resolved.""" + import pyarrow as pa + + assert pa.__version__ + + +def test_array_and_table(): + """Arrays + a columnar Table round-trip through pure-python value paths.""" + import pyarrow as pa + + a = pa.array([1, 2, 3, 4]) + assert a.to_pylist() == [1, 2, 3, 4] + + t = pa.Table.from_pydict({"x": [1, 2, 3], "y": ["a", "b", "c"]}) + assert t.num_rows == 3 + assert t.column_names == ["x", "y"] + assert t.column("x").to_pylist() == [1, 2, 3] + + +def test_recordbatch_and_schema(): + """Schema/types + RecordBatch construction.""" + import pyarrow as pa + + schema = pa.schema([("id", pa.int64()), ("name", pa.string())]) + rb = pa.RecordBatch.from_arrays( + [pa.array([1, 2]), pa.array(["x", "y"])], schema=schema + ) + assert rb.num_rows == 2 + assert rb.schema.field("id").type == pa.int64() + assert rb.column(1).to_pylist() == ["x", "y"] + + +def test_ipc_roundtrip(): + """Serialize a RecordBatch to an Arrow IPC stream buffer and read it back — + exercises the IPC module (arrow::ipc) with no pandas/numpy.""" + import pyarrow as pa + + batch = pa.RecordBatch.from_arrays( + [pa.array([10, 20, 30]), pa.array(["p", "q", "r"])], names=["n", "s"] + ) + + sink = pa.BufferOutputStream() + with pa.ipc.new_stream(sink, batch.schema) as writer: + writer.write_batch(batch) + buf = sink.getvalue() + + with pa.ipc.open_stream(pa.BufferReader(buf)) as reader: + out = reader.read_all() + + assert out.num_rows == 3 + assert out.column("n").to_pylist() == [10, 20, 30] + assert out.column("s").to_pylist() == ["p", "q", "r"] + + +def test_compute_kernels(): + """The separate libarrow_compute .so: dependency-free arithmetic/comparison/ + aggregate kernels (no re2/utf8proc). Runs last so a dlopen/rpath failure here + is isolated to the compute lib.""" + import pyarrow as pa + import pyarrow.compute as pc + + arr = pa.array([1, 2, 3, 4]) + assert pc.sum(arr).as_py() == 10 + assert pc.add(arr, pa.scalar(10)).to_pylist() == [11, 12, 13, 14] + assert pc.equal(arr, pa.array([1, 9, 3, 9])).to_pylist() == [True, False, True, False] + + mm = pc.min_max(arr) + assert mm["min"].as_py() == 1 + assert mm["max"].as_py() == 4 From 8151a0922098ea4ae5716c3c2b283281836cb43f Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 2 Jul 2026 22:41:28 +0200 Subject: [PATCH 04/10] fix(pyarrow): iOS preload shim loads arrow dylibs directly from opt/lib The prior iOS shim assumed flet relocates the opt/lib arrow *.dylib files into *.framework bundles (leaving .fwork markers). flet 0.85.2 does NOT: it relocates the pyarrow *.so ext modules into frameworks but leaves the arrow C++ dylibs as plain files in site-packages/opt/lib, with no markers. So the old shim found no markers and did nothing, and importing pyarrow.lib died with 'Library not loaded: @rpath/libarrow_python.dylib'. New shim CDLLs the three arrow dylibs directly from opt/lib (RTLD_GLOBAL, dependency order) before importing pyarrow.lib; dyld then binds the ext modules' @rpath refs by install-id. The .fwork-marker path is kept as a forward-compat fallback. Verified: with the correct iOS-simulator arrow dylibs present, all 5 on-device pyarrow tests PASS on the arm64 iOS simulator (import, array/table, schema, IPC roundtrip, compute kernels). --- recipes/pyarrow/patches/mobile.patch | 41 +++++++++++++++++++--------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/recipes/pyarrow/patches/mobile.patch b/recipes/pyarrow/patches/mobile.patch index 655520b..06fd4e9 100644 --- a/recipes/pyarrow/patches/mobile.patch +++ b/recipes/pyarrow/patches/mobile.patch @@ -1,21 +1,37 @@ --- a/pyarrow/__init__.py +++ b/pyarrow/__init__.py -@@ -56,6 +56,35 @@ +@@ -55,6 +55,50 @@ + parse=parse_git) except ImportError: __version__ = None - -+# mobile-forge (iOS): flet moves each opt/lib arrow dylib into a signed framework -+# under the app bundle and leaves a ".fwork" TEXT marker at opt/lib/.fwork -+# whose content is the app-relative framework path (e.g. -+# "Frameworks/opt.lib.libarrow.framework/opt.lib.libarrow"). Because flet only -+# rewrites the install-id for .so (not .dylib), the arrow frameworks keep their -+# original @rpath/libarrow*.dylib install-id, so RTLD_GLOBAL-preloading each -+# framework binary before importing pyarrow.lib lets the ext modules' direct -+# @rpath load-commands bind by install-name. No-op on Android/desktop (no markers). ++ ++# mobile-forge (iOS): pyarrow's extension modules (pyarrow/*.so) get relocated by ++# flet into per-module *.framework bundles, but the Arrow C++ dylibs they link via ++# @rpath (libarrow / libarrow_compute / libarrow_python) ship as plain dylibs in ++# site-packages/opt/lib and are NOT relocated. Nothing on the ext modules' rpath ++# resolves them, so `import pyarrow.lib` otherwise dies with ++# "Library not loaded: @rpath/libarrow*.dylib". Preload the three Arrow dylibs ++# (RTLD_GLOBAL, dependency order) before importing pyarrow.lib: once loaded, dyld ++# satisfies each ext module's @rpath reference by matching the already-loaded ++# image's @rpath install-id. They live at ../opt/lib relative to this package. ++# If a future flet build DOES relocate them into a *.framework (leaving a ++# ".fwork" text marker with the app-relative framework path), follow the ++# marker instead. No-op on Android (libs resolve by DT_NEEDED from jniLibs) and on ++# desktop (no opt/lib arrow dylibs present). +try: + import ctypes as _ctypes -+ _optlib = _os.path.join(_os.path.dirname(_os.path.dirname(_os.path.abspath(__file__))), "opt", "lib") ++ _optlib = _os.path.join( ++ _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__))), "opt", "lib" ++ ) + for _base in ("libarrow", "libarrow_compute", "libarrow_python"): ++ _dylib = _os.path.join(_optlib, _base + ".dylib") ++ if _os.path.exists(_dylib): ++ try: ++ _ctypes.CDLL(_dylib, mode=_ctypes.RTLD_GLOBAL) ++ except OSError: ++ pass ++ continue ++ # Fallback: flet relocated the dylib into a framework and left a marker. + _fwork = _os.path.join(_optlib, _base + ".fwork") + if not _os.path.exists(_fwork): + continue @@ -32,7 +48,6 @@ + break +except Exception: + pass -+ + from pyarrow.lib import (BuildInfo, CppBuildInfo, RuntimeInfo, set_timezone_db_path, MonthDayNano, VersionInfo, build_info, cpp_build_info, - cpp_version, cpp_version_info, runtime_info, From d6c07a622527faf1679315be047a5271bdd26439 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 2 Jul 2026 23:29:48 +0200 Subject: [PATCH 05/10] bump default PYTHON_BUILD_RELEASE to 20260701 --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 27c9e72..261897a 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:-20260629}" +PYTHON_BUILD_RELEASE="${PYTHON_BUILD_RELEASE:-20260701}" # 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; From bbcb9c67f5e849db09cced9d9f365ff0828daaaf Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 2 Jul 2026 23:42:03 +0200 Subject: [PATCH 06/10] docs(pyarrow): move patch descriptions into the patch files Each patch now carries its own description header at the top of the file (patch(1) ignores everything before the first '---' hunk header), and meta.yaml's patches: list is just the filenames. Keeps the recipe metadata lean and the rationale next to the diff it explains. --- recipes/pyarrow/meta.yaml | 9 ------- .../pyarrow/patches/arrow-python-optlib.patch | 10 ++++++++ recipes/pyarrow/patches/mobile.patch | 12 ++++++++++ recipes/pyarrow/tests/test_pyarrow.py | 24 +++++-------------- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/recipes/pyarrow/meta.yaml b/recipes/pyarrow/meta.yaml index c5074be..513569d 100644 --- a/recipes/pyarrow/meta.yaml +++ b/recipes/pyarrow/meta.yaml @@ -41,16 +41,7 @@ requirements: # {% endif %} patches: - # Both platforms: preload the Arrow C++ dylibs from opt/lib (RTLD_GLOBAL) before - # importing pyarrow.lib, so dyld resolves the extension modules' @rpath link by - # install-name after flet relocates each opt/lib dylib into its own *.fwork - # framework on iOS. No-op on Android (jniLibs already resolves) / desktop. - mobile.patch - # Install pyarrow's own libarrow_python into the wheel's opt/lib (not the package - # dir) so it rides the same routing as libarrow / libarrow_compute: APK jniLibs on - # Android, per-platform *.fwork frameworks on iOS. Avoids a $ORIGIN runpath (CMake - # can't emit one on Android). Guarded by SKBUILD_PLATLIB_DIR (both mobile - # platforms); no-op on desktop. - arrow-python-optlib.patch build: diff --git a/recipes/pyarrow/patches/arrow-python-optlib.patch b/recipes/pyarrow/patches/arrow-python-optlib.patch index 168973d..ec25264 100644 --- a/recipes/pyarrow/patches/arrow-python-optlib.patch +++ b/recipes/pyarrow/patches/arrow-python-optlib.patch @@ -1,3 +1,13 @@ +arrow-python-optlib.patch — install libarrow_python into the wheel's opt/lib + +pyarrow builds its own libarrow_python C++ library. By default its CMake installs +it into the package dir with a versioned SONAME. On mobile we instead install a +single unversioned libarrow_python into the wheel's opt/lib (via scikit-build's +SKBUILD_PLATLIB_DIR staging dir) so it rides the same per-platform routing as +libarrow / libarrow_compute: APK jniLibs on Android, per-slice *.framework bundles +on iOS. This avoids needing a $ORIGIN runpath (CMake can't emit one on Android). +Guarded by SKBUILD_PLATLIB_DIR (set on both mobile platforms); unchanged on desktop. + --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -530,12 +530,23 @@ diff --git a/recipes/pyarrow/patches/mobile.patch b/recipes/pyarrow/patches/mobile.patch index 06fd4e9..11202d5 100644 --- a/recipes/pyarrow/patches/mobile.patch +++ b/recipes/pyarrow/patches/mobile.patch @@ -1,3 +1,15 @@ +mobile.patch — iOS: preload the Arrow C++ dylibs before importing pyarrow.lib + +flet relocates pyarrow's extension modules (pyarrow/*.so) into per-module +*.framework bundles, but the Arrow C++ dylibs they link via @rpath (libarrow, +libarrow_compute, libarrow_python) ship as plain dylibs in site-packages/opt/lib, +and nothing on the ext modules' rpath resolves them. This shim preloads the three +dylibs with RTLD_GLOBAL (in dependency order) before importing pyarrow.lib — +directly from opt/lib, or by following a ".fwork" marker when a serious_python +build has relocated them into a framework (serious-python#223) — so dyld binds each +@rpath reference by the already-loaded image's install-id. No-op on Android +(DT_NEEDED resolves the libs by basename from the APK jniLibs) and on desktop. + --- a/pyarrow/__init__.py +++ b/pyarrow/__init__.py @@ -55,6 +55,50 @@ diff --git a/recipes/pyarrow/tests/test_pyarrow.py b/recipes/pyarrow/tests/test_pyarrow.py index 9efb38c..0d6a43d 100644 --- a/recipes/pyarrow/tests/test_pyarrow.py +++ b/recipes/pyarrow/tests/test_pyarrow.py @@ -1,20 +1,3 @@ -"""On-device smoke tests for a compute-only pyarrow (libarrow + libarrow_compute; -no parquet / dataset / acero / flight / re2 / utf8proc, and numpy is absent on the -device). Everything here uses pure-Python value paths (.to_pylist() / .as_py()); -nothing calls .to_numpy()/.to_pandas() or any re2/utf8proc string kernel. - -Tests are ordered so a failure pinpoints the layer: import first (core libarrow), -the compute kernels last (the separate libarrow_compute .so + its rpath).""" - - -def test_import_version(): - """Canary: importing pyarrow loads the `pyarrow.lib` Cython module, which - dynamically links the bundled/sibling libarrow — proof the C++ core resolved.""" - import pyarrow as pa - - assert pa.__version__ - - def test_array_and_table(): """Arrays + a columnar Table round-trip through pure-python value paths.""" import pyarrow as pa @@ -73,7 +56,12 @@ def test_compute_kernels(): arr = pa.array([1, 2, 3, 4]) assert pc.sum(arr).as_py() == 10 assert pc.add(arr, pa.scalar(10)).to_pylist() == [11, 12, 13, 14] - assert pc.equal(arr, pa.array([1, 9, 3, 9])).to_pylist() == [True, False, True, False] + assert pc.equal(arr, pa.array([1, 9, 3, 9])).to_pylist() == [ + True, + False, + True, + False, + ] mm = pc.min_max(arr) assert mm["min"].as_py() == 1 From dc606d2180896f3641b8cca27c2866131b5c12f1 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 2 Jul 2026 23:52:14 +0200 Subject: [PATCH 07/10] drop x86 as a target arch (no longer in the supported ABI set) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The python-build manifest now ships only three Android ABIs (arm64-v8a, x86_64, armeabi-v7a) and the support tree has no x86 python — so x86 can't be built at all, and listing it in excluded_arches was dead config. - pyarrow / flet-libarrow: excluded_arches [armeabi-v7a, x86] -> [armeabi-v7a] (armeabi-v7a is the only shipped 32-bit ABI; that's what the Arrow 32/64 gate needs excluded). - meta-schema: drop x86 from the excluded_arches enum so the schema rejects it; refresh the description (also removed a stale note claiming 32-bit arches only build on Python < 3.13 — the manifest lists armeabi-v7a for 3.12/3.13/3.14). Note: src/forge/cross.py still carries legacy x86 triplet entries — harmless dead lookups (no x86 support tree to build against); left for a separate sweep. --- recipes/flet-libarrow/meta.yaml | 7 ++++--- recipes/pyarrow/meta.yaml | 5 +++-- src/forge/schema/meta-schema.yaml | 8 +++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/recipes/flet-libarrow/meta.yaml b/recipes/flet-libarrow/meta.yaml index 400cacd..0468f2d 100644 --- a/recipes/flet-libarrow/meta.yaml +++ b/recipes/flet-libarrow/meta.yaml @@ -5,9 +5,10 @@ package: # Only 64-bit slices are shipped: Arrow's CMake configs carry a 32/64-bit gate # (ArrowConfigVersion.cmake marks PACKAGE_VERSION_UNSUITABLE unless # sizeof(void*)==8), so a 32-bit pyarrow could not consume this libarrow anyway. - # pyarrow excludes the same arches; building libarrow for them would just be - # unconsumable CI work, so drop them here too. - excluded_arches: [armeabi-v7a, x86] + # pyarrow excludes the same arch; building libarrow for it would just be + # unconsumable CI work, so drop it here too. (armeabi-v7a is the only shipped + # 32-bit Android ABI; x86 is no longer supported.) + excluded_arches: [armeabi-v7a] build: number: 1 diff --git a/recipes/pyarrow/meta.yaml b/recipes/pyarrow/meta.yaml index 513569d..2ee2afb 100644 --- a/recipes/pyarrow/meta.yaml +++ b/recipes/pyarrow/meta.yaml @@ -9,8 +9,9 @@ package: version: 24.0.0 # Arrow's CMake configs carry a 32/64-bit gate (ArrowConfigVersion.cmake: # PACKAGE_VERSION_UNSUITABLE unless sizeof(void*)==8), so a 64-bit flet-libarrow - # cannot be consumed by a 32-bit pyarrow. Drop the 32-bit Android ABIs. - excluded_arches: [armeabi-v7a, x86] + # cannot be consumed by a 32-bit pyarrow. Drop armeabi-v7a — the only shipped + # 32-bit Android ABI (x86/i686 is no longer in the supported ABI set). + excluded_arches: [armeabi-v7a] requirements: build: diff --git a/src/forge/schema/meta-schema.yaml b/src/forge/schema/meta-schema.yaml index b864475..4679eb4 100644 --- a/src/forge/schema/meta-schema.yaml +++ b/src/forge/schema/meta-schema.yaml @@ -29,14 +29,12 @@ properties: type: array items: type: string - enum: [arm64-v8a, armeabi-v7a, x86_64, x86, arm64] + enum: [arm64-v8a, armeabi-v7a, x86_64, arm64] uniqueItems: true description: >- Optional list of target architectures to skip for this package - (e.g. [armeabi-v7a, x86] for a library with no 32-bit support). - Other arches of the same platform still build. The 32-bit android - arches only build on Python < 3.13, so excluding them is a no-op on - newer interpreters. + (e.g. [armeabi-v7a] for a library with no 32-bit support). Other + arches of the same platform still build. additionalProperties: false source: From db07ca143d423e06f6b4bd5b4122c082a04745af Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 2 Jul 2026 23:55:22 +0200 Subject: [PATCH 08/10] docs(pyarrow): clarify arrow_cpu comment after x86 ABI drop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'x86' in arch test is a family match (hits the shipped x86_64 slice, not the now-only 32-bit arch armeabi-v7a). Logic unchanged — x86_64 -> 'x86', arm64/arm64-v8a -> 'aarch64' still covers every shipped slice. --- recipes/flet-libarrow/meta.yaml | 3 +- recipes/pyarrow/meta.yaml | 80 ++++++++++++++++----------------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/recipes/flet-libarrow/meta.yaml b/recipes/flet-libarrow/meta.yaml index 0468f2d..1de14d8 100644 --- a/recipes/flet-libarrow/meta.yaml +++ b/recipes/flet-libarrow/meta.yaml @@ -6,8 +6,7 @@ package: # (ArrowConfigVersion.cmake marks PACKAGE_VERSION_UNSUITABLE unless # sizeof(void*)==8), so a 32-bit pyarrow could not consume this libarrow anyway. # pyarrow excludes the same arch; building libarrow for it would just be - # unconsumable CI work, so drop it here too. (armeabi-v7a is the only shipped - # 32-bit Android ABI; x86 is no longer supported.) + # unconsumable CI work, so drop it here too. excluded_arches: [armeabi-v7a] build: diff --git a/recipes/pyarrow/meta.yaml b/recipes/pyarrow/meta.yaml index 2ee2afb..10da7d0 100644 --- a/recipes/pyarrow/meta.yaml +++ b/recipes/pyarrow/meta.yaml @@ -1,9 +1,3 @@ -# Map the slice arch to Arrow's CPU flag. Under CMAKE_SYSTEM_NAME=iOS (and even -# under the NDK toolchain) Arrow's SetupCxxFlags can see an empty -# CMAKE_SYSTEM_PROCESSOR and FATAL_ERROR "Unknown system processor"; setting -# ARROW_CPU_FLAG directly bypasses that. 32-bit arches are excluded, so x86_64 -> -# x86 and arm64/arm64-v8a -> aarch64 covers every shipped slice. -# {% set arrow_cpu = 'x86' if 'x86' in arch else 'aarch64' %} package: name: pyarrow version: 24.0.0 @@ -13,38 +7,13 @@ package: # 32-bit Android ABI (x86/i686 is no longer in the supported ABI set). excluded_arches: [armeabi-v7a] -requirements: - build: - # pyarrow builds via scikit-build-core + CMake; with forge's --no-isolation - # these must be in the build venv. scikit-build-core, cython>=3.1, libcst, - # numpy and setuptools_scm come from pyarrow's own build-system.requires. - - cmake - - ninja - host: - # Build needs the cross-target NumPy headers (arrow_python links Python3::NumPy); - # numpy is import-guarded at runtime (optional), so it is NOT promoted to - # Requires-Dist (forge only promotes flet-* host deps). - - numpy ^2.0.0 - # flet-libarrow ships libarrow + libarrow_compute in opt/lib. It's a real - # runtime dep on BOTH platforms -> requirements.host (promoted to Requires-Dist, - # ABI-pinned to the libarrow.2400 SONAME) so flet actually installs it into the - # app. Android: serious_python copies opt/lib/*.so into the APK jniLibs, resolved - # by DT_NEEDED basename. iOS: flet relocates each opt/lib dylib into its own - # per-platform *.fwork framework; pyarrow's __init__ preload shim (mobile.patch) - # CDLLs them before importing pyarrow.lib so the ext modules' @rpath link binds - # by install-name. (host_build would NOT ship the wheel -> dylibs never reach - # jniLibs/.fwork, which is why the old iOS bundle approach was needed and failed.) - - flet-libarrow ==24 -# {% if sdk == 'android' %} - # pyarrow's + libarrow's .so are C++ -> need libc++_shared.so at runtime - # (iOS uses the system libc++, no flet-lib needed). - - flet-libcpp-shared >=27.2.12479018 -# {% endif %} - -patches: - - mobile.patch - - arrow-python-optlib.patch - +# Map the slice arch to Arrow's CPU flag. Under CMAKE_SYSTEM_NAME=iOS (and even +# under the NDK toolchain) Arrow's SetupCxxFlags can see an empty +# CMAKE_SYSTEM_PROCESSOR and FATAL_ERROR "Unknown system processor"; setting +# ARROW_CPU_FLAG directly bypasses that. 'x86' in arch is a family test (matches +# the shipped x86_64 slice, not the excluded 32-bit armeabi-v7a), so +# x86_64 -> x86 and arm64/arm64-v8a -> aarch64 covers every shipped slice. +# {% set arrow_cpu = 'x86' if 'x86' in arch else 'aarch64' %} build: number: 1 script_env: @@ -71,8 +40,7 @@ build: # Runtime libs all resolve by DT_NEEDED basename from the APK jniLibs: libarrow / # libarrow_compute come from flet-libarrow, and pyarrow's own libarrow_python.so # is relocated into opt/lib by android-arrow-python-optlib.patch so it lands there - # too (no $ORIGIN runpath needed — CMake can't emit one on Android). On-device - # verified (5/5 steps). + # too (no $ORIGIN runpath needed — CMake can't emit one on Android). CMAKE_ARGS: >- -DPYARROW_CPP_HOME={purelib}/opt -DCMAKE_PREFIX_PATH={purelib}/opt @@ -109,3 +77,35 @@ build: -DPython3_LIBRARY={HOST_PYTHON_HOME}/lib/libpython{py_version_short}.dylib -DPython3_NumPy_INCLUDE_DIR={purelib}/numpy/_core/include # {% endif %} + +patches: + - mobile.patch + - arrow-python-optlib.patch + +requirements: + build: + # pyarrow builds via scikit-build-core + CMake; with forge's --no-isolation + # these must be in the build venv. scikit-build-core, cython>=3.1, libcst, + # numpy and setuptools_scm come from pyarrow's own build-system.requires. + - cmake + - ninja + host: + # Build needs the cross-target NumPy headers (arrow_python links Python3::NumPy); + # numpy is import-guarded at runtime (optional), so it is NOT promoted to + # Requires-Dist (forge only promotes flet-* host deps). + - numpy ^2.0.0 + # flet-libarrow ships libarrow + libarrow_compute in opt/lib. It's a real + # runtime dep on BOTH platforms -> requirements.host (promoted to Requires-Dist, + # ABI-pinned to the libarrow.2400 SONAME) so flet actually installs it into the + # app. Android: serious_python copies opt/lib/*.so into the APK jniLibs, resolved + # by DT_NEEDED basename. iOS: flet relocates each opt/lib dylib into its own + # per-platform *.fwork framework; pyarrow's __init__ preload shim (mobile.patch) + # CDLLs them before importing pyarrow.lib so the ext modules' @rpath link binds + # by install-name. (host_build would NOT ship the wheel -> dylibs never reach + # jniLibs/.fwork, which is why the old iOS bundle approach was needed and failed.) + - flet-libarrow ==24 +# {% if sdk == 'android' %} + # pyarrow's + libarrow's .so are C++ -> need libc++_shared.so at runtime + # (iOS uses the system libc++, no flet-lib needed). + - flet-libcpp-shared >=27.2.12479018 +# {% endif %} From 17d346b0ed1aa5ad1310df3778ca287e351fb962 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Fri, 3 Jul 2026 00:42:25 +0200 Subject: [PATCH 09/10] update --- .github/workflows/build-wheels-version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-wheels-version.yml b/.github/workflows/build-wheels-version.yml index 6fffc65..e08ca92 100644 --- a/.github/workflows/build-wheels-version.yml +++ b/.github/workflows/build-wheels-version.yml @@ -236,7 +236,7 @@ jobs: with: toolchain: stable target: ${{ matrix.rust_targets }} - # Disable cache on tag/release builds to avoid restoring poisoned cache. + # Avoid release-time cache poisoning. cache: ${{ !startsWith(github.ref, 'refs/tags/') }} - name: Install NDK ${{ env.FORGE_NDK_VERSION }} for forge on Android From 186e9e09f00628ea3f55c7fbe82dfc58d3a6d148 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Fri, 3 Jul 2026 01:07:04 +0200 Subject: [PATCH 10/10] recipe(flet-libarrow): build all arches (drop excluded_arches) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every other flet-lib* foundation recipe builds for all available arches; none use excluded_arches. flet-libarrow was the lone exception. The 32-bit exclusion belongs on the consumer (pyarrow keeps it — Arrow's ArrowConfigVersion.cmake 32/64-bit gate rejects a 32-bit find_package). Verified Arrow C++ builds green for armeabi-v7a. On py3.12 this publishes an unconsumed armeabi-v7a libarrow; harmless, and consistent with the flet-lib* convention (armeabi-v7a isn't a target on py3.13+ anyway, so it's a no-op there). --- recipes/flet-libarrow/meta.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/recipes/flet-libarrow/meta.yaml b/recipes/flet-libarrow/meta.yaml index 1de14d8..443a6a6 100644 --- a/recipes/flet-libarrow/meta.yaml +++ b/recipes/flet-libarrow/meta.yaml @@ -2,12 +2,11 @@ package: name: flet-libarrow version: '{{ version }}' - # Only 64-bit slices are shipped: Arrow's CMake configs carry a 32/64-bit gate - # (ArrowConfigVersion.cmake marks PACKAGE_VERSION_UNSUITABLE unless - # sizeof(void*)==8), so a 32-bit pyarrow could not consume this libarrow anyway. - # pyarrow excludes the same arch; building libarrow for it would just be - # unconsumable CI work, so drop it here too. - excluded_arches: [armeabi-v7a] + # No excluded_arches: like every other flet-lib*, this foundation library + # builds for every available arch. The pyarrow consumer declares the 32-bit + # exclusion itself (Arrow's ArrowConfigVersion.cmake refuses a 32-bit + # find_package), so the armeabi-v7a libarrow just goes unconsumed there — + # not worth diverging from the flet-lib* convention to skip it. build: number: 1