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 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..443a6a6 --- /dev/null +++ b/recipes/flet-libarrow/meta.yaml @@ -0,0 +1,28 @@ +{% set version = "24.0.0" %} +package: + name: flet-libarrow + version: '{{ version }}' + # 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 + +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 %} diff --git a/recipes/pyarrow/meta.yaml b/recipes/pyarrow/meta.yaml new file mode 100644 index 0000000..10da7d0 --- /dev/null +++ b/recipes/pyarrow/meta.yaml @@ -0,0 +1,111 @@ +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 armeabi-v7a — the only shipped + # 32-bit Android ABI (x86/i686 is no longer in the supported ABI set). + excluded_arches: [armeabi-v7a] + +# 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: + # 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). + 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 %} + +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 %} diff --git a/recipes/pyarrow/patches/arrow-python-optlib.patch b/recipes/pyarrow/patches/arrow-python-optlib.patch new file mode 100644 index 0000000..ec25264 --- /dev/null +++ b/recipes/pyarrow/patches/arrow-python-optlib.patch @@ -0,0 +1,42 @@ +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 @@ + 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..11202d5 --- /dev/null +++ b/recipes/pyarrow/patches/mobile.patch @@ -0,0 +1,65 @@ +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 @@ + parse=parse_git) + except ImportError: + __version__ = None ++ ++# 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" ++ ) ++ 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 ++ _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, diff --git a/recipes/pyarrow/tests/test_pyarrow.py b/recipes/pyarrow/tests/test_pyarrow.py new file mode 100644 index 0000000..0d6a43d --- /dev/null +++ b/recipes/pyarrow/tests/test_pyarrow.py @@ -0,0 +1,68 @@ +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 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 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; 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: