Skip to content

Commit da10ed1

Browse files
Add support for iOS (#9030)
Co-authored-by: Andrew Murray <[email protected]>
1 parent be2b4e7 commit da10ed1

31 files changed

+406
-58
lines changed

.github/workflows/wheels-dependencies.sh

Lines changed: 169 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,98 @@
11
#!/bin/bash
22

3-
# Setup that needs to be done before multibuild utils are invoked
4-
PROJECTDIR=$(pwd)
5-
if [[ "$(uname -s)" == "Darwin" ]]; then
6-
# Safety check - macOS builds require that CIBW_ARCHS is set, and that it
7-
# only contains a single value (even though cibuildwheel allows multiple
8-
# values in CIBW_ARCHS).
3+
# Safety check - Pillow builds require that CIBW_ARCHS is set, and that it only
4+
# contains a single value (even though cibuildwheel allows multiple values in
5+
# CIBW_ARCHS). This check doesn't work on Linux because of how the CIBW_ARCHS
6+
# variable is exposed.
7+
function check_cibw_archs {
98
if [[ -z "$CIBW_ARCHS" ]]; then
10-
echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined."
9+
echo "ERROR: Pillow builds require CIBW_ARCHS be defined."
1110
exit 1
1211
fi
1312
if [[ "$CIBW_ARCHS" == *" "* ]]; then
14-
echo "ERROR: Pillow macOS builds only support a single architecture in CIBW_ARCHS."
13+
echo "ERROR: Pillow builds only support a single architecture in CIBW_ARCHS."
1514
exit 1
1615
fi
16+
}
17+
18+
# Setup that needs to be done before multibuild utils are invoked. Process
19+
# potential cross-build platforms before native platforms to ensure that we pick
20+
# up the cross environment.
21+
PROJECTDIR=$(pwd)
22+
if [[ "$CIBW_PLATFORM" == "ios" ]]; then
23+
check_cibw_archs
24+
# On iOS, CIBW_ARCHS is actually a multi-arch - arm64_iphoneos,
25+
# arm64_iphonesimulator or x86_64_iphonesimulator. Split into the CPU
26+
# platform, and the iOS SDK.
27+
PLAT=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\1/")
28+
IOS_SDK=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\2/")
29+
30+
# Build iOS builds in `build/iphoneos` or `build/iphonesimulator`
31+
# (depending on the build target). Install them into `build/deps/iphoneos`
32+
# or `build/deps/iphonesimulator`
33+
WORKDIR=$(pwd)/build/$IOS_SDK
34+
BUILD_PREFIX=$(pwd)/build/deps/$IOS_SDK
35+
PATCH_DIR=$(pwd)/patches/iOS
36+
37+
# GNU tooling insists on using aarch64 rather than arm64
38+
if [[ $PLAT == "arm64" ]]; then
39+
GNU_ARCH=aarch64
40+
else
41+
GNU_ARCH=x86_64
42+
fi
43+
44+
IOS_SDK_PATH=$(xcrun --sdk $IOS_SDK --show-sdk-path)
45+
CMAKE_SYSTEM_NAME=iOS
46+
IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET
47+
if [[ "$IOS_SDK" == "iphonesimulator" ]]; then
48+
IOS_HOST_TRIPLE=$IOS_HOST_TRIPLE-simulator
49+
fi
1750

51+
# GNU Autotools doesn't recognize the existence of arm64-apple-ios-simulator
52+
# as a valid host. However, the only difference between arm64-apple-ios and
53+
# arm64-apple-ios-simulator is the choice of sysroot, and that is
54+
# coordinated by CC, CFLAGS etc. From the perspective of configure, the two
55+
# platforms are identical, so we can use arm64-apple-ios consistently.
56+
# This (mostly) avoids us needing to patch config.sub in dependency sources.
57+
HOST_CONFIGURE_FLAGS="--disable-shared --enable-static --host=$GNU_ARCH-apple-ios --build=$GNU_ARCH-apple-darwin"
58+
59+
# CMake has native support for iOS. However, most of that support is based
60+
# on using the Xcode builder, which isn't very helpful for most of Pillow's
61+
# dependencies. Therefore, we lean on the OSX configurations, plus CC, CFLAGS
62+
# etc. to ensure the right sysroot is selected.
63+
HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO"
64+
65+
# Meson needs to be pointed at a cross-platform configuration file
66+
# This will be generated once CC etc. have been evaluated.
67+
HOST_MESON_FLAGS="--cross-file $WORKDIR/meson-cross.txt -Dprefer_static=true -Ddefault_library=static"
68+
69+
elif [[ "$(uname -s)" == "Darwin" ]]; then
70+
check_cibw_archs
1871
# Build macOS dependencies in `build/darwin`
1972
# Install them into `build/deps/darwin`
73+
PLAT=$CIBW_ARCHS
2074
WORKDIR=$(pwd)/build/darwin
2175
BUILD_PREFIX=$(pwd)/build/deps/darwin
2276
else
2377
# Build prefix will default to /usr/local
78+
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
2479
WORKDIR=$(pwd)/build
2580
MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
2681
MB_ML_VER=${AUDITWHEEL_POLICY:9}
2782
fi
28-
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
2983

3084
# Define custom utilities
3185
source wheels/multibuild/common_utils.sh
3286
source wheels/multibuild/library_builders.sh
33-
if [ -z "$IS_MACOS" ]; then
87+
if [[ -z "$IS_MACOS" ]]; then
3488
source wheels/multibuild/manylinux_utils.sh
3589
fi
3690

3791
ARCHIVE_SDIR=pillow-depends-main
3892

39-
# Package versions for fresh source builds
93+
# Package versions for fresh source builds. Version numbers with "Patched"
94+
# annotations have a source code patch that is required for some platforms. If
95+
# you change those versions, ensure the patch is also updated.
4096
FREETYPE_VERSION=2.13.3
4197
HARFBUZZ_VERSION=11.2.1
4298
LIBPNG_VERSION=1.6.49
@@ -47,32 +103,58 @@ TIFF_VERSION=4.7.0
47103
LCMS2_VERSION=2.17
48104
ZLIB_VERSION=1.3.1
49105
ZLIB_NG_VERSION=2.2.4
50-
LIBWEBP_VERSION=1.5.0
106+
LIBWEBP_VERSION=1.5.0 # Patched; next release won't need patching. See patch file.
51107
BZIP2_VERSION=1.0.8
52108
LIBXCB_VERSION=1.17.0
53-
BROTLI_VERSION=1.1.0
109+
BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file.
54110
LIBAVIF_VERSION=1.3.0
55111

56112
function build_pkg_config {
57113
if [ -e pkg-config-stamp ]; then return; fi
58-
# This essentially duplicates the Homebrew recipe
59-
CFLAGS="$CFLAGS -Wno-int-conversion" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
114+
# This essentially duplicates the Homebrew recipe.
115+
# On iOS, we need a binary that can be executed on the build machine; but we
116+
# can create a host-specific pc-path to store iOS .pc files. To ensure a
117+
# macOS-compatible build, we temporarily clear environment flags that set
118+
# iOS-specific values.
119+
if [[ -n "$IOS_SDK" ]]; then
120+
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
121+
ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET
122+
unset HOST_CONFIGURE_FLAGS
123+
unset IPHONEOS_DEPLOYMENT_TARGET
124+
fi
125+
126+
CFLAGS="$CFLAGS -Wno-int-conversion" CPPFLAGS="" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
60127
--disable-debug --disable-host-tool --with-internal-glib \
61128
--with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
62129
--with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
130+
131+
if [[ -n "$IOS_SDK" ]]; then
132+
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
133+
IPHONEOS_DEPLOYMENT_TARGET=$ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET
134+
fi;
135+
63136
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
64137
touch pkg-config-stamp
65138
}
66139

67140
function build_zlib_ng {
68141
if [ -e zlib-stamp ]; then return; fi
142+
# zlib-ng uses a "configure" script, but it's not a GNU autotools script, so
143+
# it doesn't honor the usual flags. Temporarily disable any
144+
# cross-compilation flags.
145+
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
146+
unset HOST_CONFIGURE_FLAGS
147+
69148
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat
70149

71-
if [ -n "$IS_MACOS" ]; then
150+
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
151+
152+
if [[ -n "$IS_MACOS" ]] && [[ -z "$IOS_SDK" ]]; then
72153
# Ensure that on macOS, the library name is an absolute path, not an
73154
# @rpath, so that delocate picks up the right library (and doesn't need
74155
# DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an
75-
# option to control the install_name.
156+
# option to control the install_name. This isn't needed on iOS, as iOS
157+
# only builds the static library.
76158
install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib
77159
fi
78160
touch zlib-stamp
@@ -82,7 +164,7 @@ function build_brotli {
82164
if [ -e brotli-stamp ]; then return; fi
83165
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
84166
(cd $out_dir \
85-
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
167+
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \
86168
&& make install)
87169
touch brotli-stamp
88170
}
@@ -93,7 +175,7 @@ function build_harfbuzz {
93175

94176
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
95177
(cd $out_dir \
96-
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled)
178+
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled $HOST_MESON_FLAGS)
97179
(cd $out_dir/build \
98180
&& meson install)
99181
touch harfbuzz-stamp
@@ -164,19 +246,19 @@ function build {
164246
fi
165247

166248
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
167-
if [ -n "$IS_MACOS" ]; then
249+
if [[ -n "$IS_MACOS" ]]; then
168250
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
169251
build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
170252
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
171253
else
172-
sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
254+
sed "s/\${pc_sysrootdir\}//" $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
173255
fi
174256
build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
175257

176258
build_libjpeg_turbo
177-
if [ -n "$IS_MACOS" ]; then
259+
if [[ -n "$IS_MACOS" ]]; then
178260
# Custom tiff build to include jpeg; by default, configure won't include
179-
# headers/libs in the custom macOS prefix. Explicitly disable webp,
261+
# headers/libs in the custom macOS/iOS prefix. Explicitly disable webp,
180262
# libdeflate and zstd, because on x86_64 macs, it will pick up the
181263
# Homebrew versions of those libraries from /usr/local.
182264
build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \
@@ -186,7 +268,10 @@ function build {
186268
build_tiff
187269
fi
188270

189-
build_libavif
271+
if [[ -z "$IOS_SDK" ]]; then
272+
# Short term workaround; don't build libavif on iOS
273+
build_libavif
274+
fi
190275
build_libpng
191276
build_lcms2
192277
build_openjpeg
@@ -201,14 +286,44 @@ function build {
201286

202287
build_brotli
203288

204-
if [ -n "$IS_MACOS" ]; then
289+
if [[ -n "$IS_MACOS" ]]; then
205290
# Custom freetype build
206291
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
207292
else
208293
build_freetype
209294
fi
210295

211-
build_harfbuzz
296+
if [[ -z "$IOS_SDK" ]]; then
297+
# On iOS, there's no vendor-provided raqm, and we can't ship it due to
298+
# licensing, so there's no point building harfbuzz.
299+
build_harfbuzz
300+
fi
301+
}
302+
303+
function create_meson_cross_config {
304+
cat << EOF > $WORKDIR/meson-cross.txt
305+
[binaries]
306+
pkg-config = '$BUILD_PREFIX/bin/pkg-config'
307+
cmake = '$(which cmake)'
308+
c = '$CC'
309+
cpp = '$CXX'
310+
strip = '$STRIP'
311+
312+
[built-in options]
313+
c_args = '$CFLAGS -I$BUILD_PREFIX/include'
314+
cpp_args = '$CXXFLAGS -I$BUILD_PREFIX/include'
315+
c_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
316+
cpp_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
317+
318+
[host_machine]
319+
system = 'darwin'
320+
subsystem = 'ios'
321+
kernel = 'xnu'
322+
cpu_family = '$(uname -m)'
323+
cpu = '$(uname -m)'
324+
endian = 'little'
325+
326+
EOF
212327
}
213328

214329
# Perform all dependency builds in the build subfolder.
@@ -227,24 +342,40 @@ if [[ ! -d $WORKDIR/pillow-depends-main ]]; then
227342
fi
228343

229344
if [[ -n "$IS_MACOS" ]]; then
230-
# Homebrew (or similar packaging environments) install can contain some of
231-
# the libraries that we're going to build. However, they may be compiled
232-
# with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use,
233-
# and they may bring in other dependencies that we don't want. The same will
234-
# be true of any other locations on the path. To avoid conflicts, strip the
235-
# path down to the bare minimum (which, on macOS, won't include any
236-
# development dependencies).
237-
export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
238-
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
239-
240345
# Ensure the basic structure of the build prefix directory exists.
241346
mkdir -p "$BUILD_PREFIX/bin"
242347
mkdir -p "$BUILD_PREFIX/lib"
243348

244-
# Ensure pkg-config is available
349+
# Ensure pkg-config is available. This is done *before* setting CC, CFLAGS
350+
# etc. to ensure that the build is *always* a macOS build, even when building
351+
# for iOS.
245352
build_pkg_config
246-
# Ensure cmake is available
353+
354+
# Ensure cmake is available, and that the default prefix used by CMake is
355+
# the build prefix
247356
python3 -m pip install cmake
357+
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
358+
359+
if [[ -n "$IOS_SDK" ]]; then
360+
export AR="$(xcrun --find --sdk $IOS_SDK ar)"
361+
export CPP="$(xcrun --find --sdk $IOS_SDK clang) -E"
362+
export CC=$(xcrun --find --sdk $IOS_SDK clang)
363+
export CXX=$(xcrun --find --sdk $IOS_SDK clang++)
364+
export LD=$(xcrun --find --sdk $IOS_SDK ld)
365+
export STRIP=$(xcrun --find --sdk $IOS_SDK strip)
366+
367+
CPPFLAGS="$CPPFLAGS --sysroot=$IOS_SDK_PATH"
368+
CFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
369+
CXXFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
370+
371+
# Having IPHONEOS_DEPLOYMENT_TARGET in the environment causes problems
372+
# with some cross-building toolchains, because it introduces implicit
373+
# behavior into clang.
374+
unset IPHONEOS_DEPLOYMENT_TARGET
375+
376+
# Now that we know CC etc., we can create a meson cross-configuration file
377+
create_meson_cross_config
378+
fi
248379
fi
249380

250381
wrap_wheel_builder build

.github/workflows/wheels-test.ps1

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@ if (Test-Path $venv\Scripts\pypy.exe) {
1515
$python = "python.exe"
1616
}
1717
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
18-
if ("$venv" -like "*\cibw-run-*-win_amd64\*") {
19-
& $venv\Scripts\$python -m pip install numpy
20-
}
2118
cd $pillow
2219
& $venv\Scripts\$python -VV
2320
if (!$?) { exit $LASTEXITCODE }
2421
& $venv\Scripts\$python selftest.py
2522
if (!$?) { exit $LASTEXITCODE }
26-
& $venv\Scripts\$python -m pytest -vv -x Tests\check_wheel.py
23+
& $venv\Scripts\$python -m pytest -vv -x checks\check_wheel.py
2724
if (!$?) { exit $LASTEXITCODE }
2825
& $venv\Scripts\$python -m pytest -vv -x Tests
2926
if (!$?) { exit $LASTEXITCODE }

.github/workflows/wheels-test.sh

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ else
2525
yum install -y fribidi
2626
fi
2727

28-
python3 -m pip install numpy
29-
3028
if [ ! -d "test-images-main" ]; then
3129
curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
3230
unzip pillow-test-images.zip
@@ -35,5 +33,5 @@ fi
3533

3634
# Runs tests
3735
python3 selftest.py
38-
python3 -m pytest -vv -x Tests/check_wheel.py
36+
python3 -m pytest -vv -x checks/check_wheel.py
3937
python3 -m pytest -vv -x

0 commit comments

Comments
 (0)