Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-wheels-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
158 changes: 158 additions & 0 deletions recipes/flet-libarrow/build.sh
Original file line number Diff line number Diff line change
@@ -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=<aarch64|aarch32|x86> — 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<name>.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<name>.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
28 changes: 28 additions & 0 deletions recipes/flet-libarrow/meta.yaml
Original file line number Diff line number Diff line change
@@ -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 %}
111 changes: 111 additions & 0 deletions recipes/pyarrow/meta.yaml
Original file line number Diff line number Diff line change
@@ -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_<NAME> 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 %}
42 changes: 42 additions & 0 deletions recipes/pyarrow/patches/arrow-python-optlib.patch
Original file line number Diff line number Diff line change
@@ -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="$<CONFIG>")
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)
Loading
Loading