Skip to content

Commit 3c4b619

Browse files
committed
Add tooling to support iOS build.
1 parent ef0bab0 commit 3c4b619

30 files changed

+413
-64
lines changed

.github/workflows/wheels-dependencies.sh

Lines changed: 171 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,82 @@
11
#!/bin/bash
22

3-
# Setup that needs to be done before multibuild utils are invoked
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).
6+
if [[ -z "$CIBW_ARCHS" ]]; then
7+
echo "ERROR: Pillow builds require CIBW_ARCHS be defined."
8+
exit 1
9+
fi
10+
if [[ "$CIBW_ARCHS" == *" "* ]]; then
11+
echo "ERROR: Pillow builds only support a single architecture in CIBW_ARCHS."
12+
exit 1
13+
fi
14+
15+
# Setup that needs to be done before multibuild utils are invoked. Process
16+
# potential cross-build platforms before native platforms to ensure that we pick
17+
# up the cross environment.
418
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).
9-
if [[ -z "$CIBW_ARCHS" ]]; then
10-
echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined."
11-
exit 1
19+
if [[ "$CIBW_PLATFORM" == "ios" ]]; then
20+
# On iOS, CIBW_ARCHS is actually a multi-arch - arm64_iphoneos,
21+
# arm64_iphonesimulator or x86_64_iphonesimulator. Split into the CPU
22+
# platform, and the iOS SDK.
23+
PLAT=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\1/")
24+
IOS_SDK=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\2/")
25+
26+
# Build iOS builds in `build/iphoneos` or `build/iphonesimulator/`
27+
# (depending on the build target). Install them into `build/deps/iphoneos`
28+
# or `build/deps/iphonesimulator`
29+
WORKDIR=$(pwd)/build/$IOS_SDK
30+
BUILD_PREFIX=$(pwd)/build/deps/$IOS_SDK
31+
PATCH_DIR=$(pwd)/patches/iOS
32+
33+
# GNU tooling insists on using aarch64 rather than arm64
34+
if [[ $PLAT == "arm64" ]]; then
35+
GNU_ARCH=aarch64
36+
else
37+
GNU_ARCH=x86_64
1238
fi
13-
if [[ "$CIBW_ARCHS" == *" "* ]]; then
14-
echo "ERROR: Pillow macOS builds only support a single architecture in CIBW_ARCHS."
15-
exit 1
39+
40+
IOS_SDK_PATH=$(xcrun --sdk $IOS_SDK --show-sdk-path)
41+
if [[ "$IOS_SDK" == "iphonesimulator" ]]; then
42+
CMAKE_SYSTEM_NAME=iOS
43+
IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET-simulator
44+
else
45+
CMAKE_SYSTEM_NAME=iOS
46+
IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET
1647
fi
1748

49+
# GNU Autotools doesn't recognize the existence of arm64-apple-ios-simulator
50+
# as a valid host. However, the only difference between arm64-apple-ios and
51+
# arm64-apple-ios-simulator is the choice of sysroot, and that is
52+
# coordinated by CC,CFLAGS etc. From the perspective of configure, the two
53+
# platforms are identical, so we can use arm64-apple-ios consistently.
54+
# This (mostly) avoids us needing to patch config.sub in dependency sources.
55+
HOST_CONFIGURE_FLAGS="--disable-shared --enable-static --host=$GNU_ARCH-apple-ios --build=$GNU_ARCH-apple-darwin"
56+
57+
# Cmake has native support for iOS. However, most of that support is based
58+
# on using the Xcode builder, which isn't very helpful for most of Pillow's
59+
# dependencies. Therefore, we lean on the OSX configurations, plus CC/CFLAGS
60+
# etc to ensure the right sysroot is selected.
61+
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"
62+
63+
# Meson needs to be pointed at a cross-platform configuration file
64+
# This will be generated once CC etc have been evaluated.
65+
HOST_MESON_FLAGS="--cross-file $WORKDIR/meson-cross.txt -Dprefer_static=true -Ddefault_library=static"
66+
67+
elif [[ "$(uname -s)" == "Darwin" ]]; then
1868
# Build macOS dependencies in `build/darwin`
1969
# Install them into `build/deps/darwin`
70+
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
2071
WORKDIR=$(pwd)/build/darwin
2172
BUILD_PREFIX=$(pwd)/build/deps/darwin
2273
else
2374
# Build prefix will default to /usr/local
75+
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
2476
WORKDIR=$(pwd)/build
2577
MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
2678
MB_ML_VER=${AUDITWHEEL_POLICY:9}
2779
fi
28-
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
2980

3081
# Define custom utilities
3182
source wheels/multibuild/common_utils.sh
@@ -36,7 +87,9 @@ fi
3687

3788
ARCHIVE_SDIR=pillow-depends-main
3889

39-
# Package versions for fresh source builds
90+
# Package versions for fresh source builds. Version numbers with "Patched"
91+
# annotations have a source code patch that is required for some platforms. If
92+
# you change those versions, ensure the patch is also updated.
4093
FREETYPE_VERSION=2.13.3
4194
HARFBUZZ_VERSION=11.2.1
4295
LIBPNG_VERSION=1.6.49
@@ -47,52 +100,83 @@ TIFF_VERSION=4.7.0
47100
LCMS2_VERSION=2.17
48101
ZLIB_VERSION=1.3.1
49102
ZLIB_NG_VERSION=2.2.4
50-
LIBWEBP_VERSION=1.5.0
103+
LIBWEBP_VERSION=1.5.0 # Patched
51104
BZIP2_VERSION=1.0.8
52105
LIBXCB_VERSION=1.17.0
53-
BROTLI_VERSION=1.1.0
106+
BROTLI_VERSION=1.1.0 # Patched
54107

55108
function build_pkg_config {
56109
if [ -e pkg-config-stamp ]; then return; fi
57-
# This essentially duplicates the Homebrew recipe
58-
CFLAGS="$CFLAGS -Wno-int-conversion" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
110+
# This essentially duplicates the Homebrew recipe.
111+
# On iOS, we need a binary that can be executed on the build machine; but we
112+
# can create a host-specific pc-path to store iOS .pc files. To ensure a
113+
# macOS-compatible build, we temporarily clear environment flags that set
114+
# iOS-specific values.
115+
if [[ -n "$IOS_SDK" ]]; then
116+
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
117+
ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET
118+
unset HOST_CONFIGURE_FLAGS
119+
unset IPHONEOS_DEPLOYMENT_TARGET
120+
fi
121+
122+
CFLAGS="$CFLAGS -Wno-int-conversion" CPPFLAGS="" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
59123
--disable-debug --disable-host-tool --with-internal-glib \
60124
--with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
61125
--with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
126+
127+
if [[ -n "$IOS_SDK" ]]; then
128+
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
129+
IPHONEOS_DEPLOYMENT_TARGET=$ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET
130+
fi;
131+
62132
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
63133
touch pkg-config-stamp
64134
}
65135

66136
function build_zlib_ng {
67137
if [ -e zlib-stamp ]; then return; fi
138+
# zlib-ng uses a "configure" script, but it's not a GNU autotools script, so
139+
# it doesn't honor the usual flags. Temporarily disable any
140+
# cross-compilation flags.
141+
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
142+
unset HOST_CONFIGURE_FLAGS
143+
68144
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat
69145

70-
if [ -n "$IS_MACOS" ]; then
146+
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
147+
148+
if [ -n "$IS_MACOS" ] && [ -z "$IOS_SDK" ]; then
71149
# Ensure that on macOS, the library name is an absolute path, not an
72150
# @rpath, so that delocate picks up the right library (and doesn't need
73151
# DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an
74-
# option to control the install_name.
152+
# option to control the install_name. This isn't needed on iOS, as iOS
153+
# only builds the static library.
75154
install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib
76155
fi
77156
touch zlib-stamp
78157
}
79158

80159
function build_brotli {
81160
if [ -e brotli-stamp ]; then return; fi
161+
local name=brotli
162+
local version=$BROTLI_VERSION
82163
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
83164
(cd $out_dir \
84-
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
165+
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \
85166
&& make install)
86167
touch brotli-stamp
87168
}
88169

89170
function build_harfbuzz {
90171
if [ -e harfbuzz-stamp ]; then return; fi
91-
python3 -m pip install meson ninja
172+
local name=harfbuzz
173+
local version=$HARFBUZZ_VERSION
174+
175+
python3 -m pip install --disable-pip-version-check meson ninja
92176

93177
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
94178
(cd $out_dir \
95-
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled)
179+
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled $HOST_MESON_FLAGS)
96180
(cd $out_dir/build \
97181
&& meson install)
98182
touch harfbuzz-stamp
@@ -110,19 +194,19 @@ function build {
110194
fi
111195

112196
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
113-
if [ -n "$IS_MACOS" ]; then
197+
if [[ -n "$IS_MACOS" ]]; then
114198
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
115199
build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
116200
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
117201
else
118-
sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
202+
sed "s/\$\{pc_sysrootdir\}//" $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
119203
fi
120204
build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
121205

122206
build_libjpeg_turbo
123-
if [ -n "$IS_MACOS" ]; then
207+
if [[ -n "$IS_MACOS" ]]; then
124208
# Custom tiff build to include jpeg; by default, configure won't include
125-
# headers/libs in the custom macOS prefix. Explicitly disable webp,
209+
# headers/libs in the custom macOS/iOS prefix. Explicitly disable webp,
126210
# libdeflate and zstd, because on x86_64 macs, it will pick up the
127211
# Homebrew versions of those libraries from /usr/local.
128212
build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \
@@ -146,14 +230,44 @@ function build {
146230

147231
build_brotli
148232

149-
if [ -n "$IS_MACOS" ]; then
233+
if [[ -n "$IS_MACOS" ]]; then
150234
# Custom freetype build
151235
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
152236
else
153237
build_freetype
154238
fi
155239

156-
build_harfbuzz
240+
if [[ -z "$IOS_SDK" ]]; then
241+
# On iOS, there's no vendor-provided raqm, and we can't ship it due to
242+
# licensing, so there's no point building harfbuzz.
243+
build_harfbuzz
244+
fi
245+
}
246+
247+
function create_meson_cross_config {
248+
cat << EOF > $WORKDIR/meson-cross.txt
249+
[binaries]
250+
pkg-config = '$BUILD_PREFIX/bin/pkg-config'
251+
cmake = '$(which cmake)'
252+
c = '$CC'
253+
cpp = '$CXX'
254+
strip = '$STRIP'
255+
256+
[built-in options]
257+
c_args = '$CFLAGS -I$BUILD_PREFIX/include'
258+
cpp_args = '$CXXFLAGS -I$BUILD_PREFIX/include'
259+
c_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
260+
cpp_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
261+
262+
[host_machine]
263+
system = 'darwin'
264+
subsystem = 'ios'
265+
kernel = 'xnu'
266+
cpu_family = '$(uname -m)'
267+
cpu = '$(uname -m)'
268+
endian = 'little'
269+
270+
EOF
157271
}
158272

159273
# Perform all dependency builds in the build subfolder.
@@ -172,24 +286,40 @@ if [[ ! -d $WORKDIR/pillow-depends-main ]]; then
172286
fi
173287

174288
if [[ -n "$IS_MACOS" ]]; then
175-
# Homebrew (or similar packaging environments) install can contain some of
176-
# the libraries that we're going to build. However, they may be compiled
177-
# with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use,
178-
# and they may bring in other dependencies that we don't want. The same will
179-
# be true of any other locations on the path. To avoid conflicts, strip the
180-
# path down to the bare minimum (which, on macOS, won't include any
181-
# development dependencies).
182-
export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
183-
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
184-
185289
# Ensure the basic structure of the build prefix directory exists.
186290
mkdir -p "$BUILD_PREFIX/bin"
187291
mkdir -p "$BUILD_PREFIX/lib"
188292

189-
# Ensure pkg-config is available
293+
# Ensure pkg-config is available. This is done *before* setting CC, CFLAGS
294+
# etc to ensure that the build is *always* a macOS build, even when building
295+
# for iOS.
190296
build_pkg_config
191-
# Ensure cmake is available
192-
python3 -m pip install cmake
297+
298+
# Ensure cmake is available, and that the default prefix used by CMake is
299+
# the build prefix
300+
python3 -m pip install --disable-pip-version-check cmake
301+
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
302+
303+
if [[ -n "$IOS_SDK" ]]; then
304+
export AR="$(xcrun --find --sdk $IOS_SDK ar)"
305+
export CPP="$(xcrun --find --sdk $IOS_SDK clang) -E"
306+
export CC=$(xcrun --find --sdk $IOS_SDK clang)
307+
export CXX=$(xcrun --find --sdk $IOS_SDK clang++)
308+
export LD=$(xcrun --find --sdk $IOS_SDK ld)
309+
export STRIP=$(xcrun --find --sdk $IOS_SDK strip)
310+
311+
CPPFLAGS="$CPPFLAGS --sysroot=$IOS_SDK_PATH"
312+
CFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
313+
CXXFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
314+
315+
# Having IPHONEOS_DEPLOYMENT_TARGET in the environment causes problems
316+
# with some cross-building toolchains, because it introduces implicit
317+
# behavior into clang.
318+
unset IPHONEOS_DEPLOYMENT_TARGET
319+
320+
# Now that we know CC etc, we can create a meson cross-configuration file
321+
create_meson_cross_config
322+
fi
193323
fi
194324

195325
wrap_wheel_builder build

.github/workflows/wheels-test.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ cd $pillow
2323
if (!$?) { exit $LASTEXITCODE }
2424
& $venv\Scripts\$python selftest.py
2525
if (!$?) { exit $LASTEXITCODE }
26-
& $venv\Scripts\$python -m pytest -vx Tests\check_wheel.py
26+
& $venv\Scripts\$python -m pytest -vx checks\check_wheel.py
2727
if (!$?) { exit $LASTEXITCODE }
2828
& $venv\Scripts\$python -m pytest -vx Tests
2929
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 Tests/check_wheel.py
36+
python3 -m pytest checks/check_wheel.py
3937
python3 -m pytest

0 commit comments

Comments
 (0)