From 3c4b6191ebedf05d751e99b1e941cefefaa61032 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 17 Feb 2025 08:54:20 +0800 Subject: [PATCH 01/34] Add tooling to support iOS build. --- .github/workflows/wheels-dependencies.sh | 212 ++++++++++++++---- .github/workflows/wheels-test.ps1 | 2 +- .github/workflows/wheels-test.sh | 4 +- .github/workflows/wheels.yml | 30 ++- .gitignore | 4 + .pre-commit-config.yaml | 6 +- Tests/oss-fuzz/test_fuzzers.py | 8 +- Tests/test_main.py | 4 + Tests/test_pyroma.py | 4 +- {Tests => checks}/32bit_segfault_check.py | 0 {Tests => checks}/check_fli_oob.py | 0 {Tests => checks}/check_fli_overflow.py | 0 {Tests => checks}/check_icns_dos.py | 0 {Tests => checks}/check_imaging_leaks.py | 0 {Tests => checks}/check_j2k_dos.py | 0 {Tests => checks}/check_j2k_leaks.py | 0 {Tests => checks}/check_j2k_overflow.py | 0 {Tests => checks}/check_jp2_overflow.py | 0 {Tests => checks}/check_jpeg_leaks.py | 0 {Tests => checks}/check_large_memory.py | 0 {Tests => checks}/check_large_memory_numpy.py | 0 {Tests => checks}/check_libtiff_segfault.py | 0 {Tests => checks}/check_png_dos.py | 0 {Tests => checks}/check_release_notes.py | 0 {Tests => checks}/check_wheel.py | 11 +- patches/README.md | 14 ++ patches/iOS/brotli-1.1.0.tar.gz.patch | 43 ++++ patches/iOS/libwebp-1.5.0.tar.gz.patch | 39 ++++ pyproject.toml | 40 +++- setup.py | 56 ++++- 30 files changed, 413 insertions(+), 64 deletions(-) rename {Tests => checks}/32bit_segfault_check.py (100%) rename {Tests => checks}/check_fli_oob.py (100%) rename {Tests => checks}/check_fli_overflow.py (100%) rename {Tests => checks}/check_icns_dos.py (100%) rename {Tests => checks}/check_imaging_leaks.py (100%) rename {Tests => checks}/check_j2k_dos.py (100%) rename {Tests => checks}/check_j2k_leaks.py (100%) rename {Tests => checks}/check_j2k_overflow.py (100%) rename {Tests => checks}/check_jp2_overflow.py (100%) rename {Tests => checks}/check_jpeg_leaks.py (100%) rename {Tests => checks}/check_large_memory.py (100%) rename {Tests => checks}/check_large_memory_numpy.py (100%) rename {Tests => checks}/check_libtiff_segfault.py (100%) rename {Tests => checks}/check_png_dos.py (100%) rename {Tests => checks}/check_release_notes.py (100%) rename {Tests => checks}/check_wheel.py (72%) create mode 100644 patches/README.md create mode 100644 patches/iOS/brotli-1.1.0.tar.gz.patch create mode 100644 patches/iOS/libwebp-1.5.0.tar.gz.patch diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 996d32bc253..addf0b46bf0 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -1,31 +1,82 @@ #!/bin/bash -# Setup that needs to be done before multibuild utils are invoked +# Safety check - Pillow builds require that CIBW_ARCHS is set, and that it only +# contains a single value (even though cibuildwheel allows multiple values in +# CIBW_ARCHS). +if [[ -z "$CIBW_ARCHS" ]]; then + echo "ERROR: Pillow builds require CIBW_ARCHS be defined." + exit 1 +fi +if [[ "$CIBW_ARCHS" == *" "* ]]; then + echo "ERROR: Pillow builds only support a single architecture in CIBW_ARCHS." + exit 1 +fi + +# Setup that needs to be done before multibuild utils are invoked. Process +# potential cross-build platforms before native platforms to ensure that we pick +# up the cross environment. PROJECTDIR=$(pwd) -if [[ "$(uname -s)" == "Darwin" ]]; then - # Safety check - macOS builds require that CIBW_ARCHS is set, and that it - # only contains a single value (even though cibuildwheel allows multiple - # values in CIBW_ARCHS). - if [[ -z "$CIBW_ARCHS" ]]; then - echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined." - exit 1 +if [[ "$CIBW_PLATFORM" == "ios" ]]; then + # On iOS, CIBW_ARCHS is actually a multi-arch - arm64_iphoneos, + # arm64_iphonesimulator or x86_64_iphonesimulator. Split into the CPU + # platform, and the iOS SDK. + PLAT=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\1/") + IOS_SDK=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\2/") + + # Build iOS builds in `build/iphoneos` or `build/iphonesimulator/` + # (depending on the build target). Install them into `build/deps/iphoneos` + # or `build/deps/iphonesimulator` + WORKDIR=$(pwd)/build/$IOS_SDK + BUILD_PREFIX=$(pwd)/build/deps/$IOS_SDK + PATCH_DIR=$(pwd)/patches/iOS + + # GNU tooling insists on using aarch64 rather than arm64 + if [[ $PLAT == "arm64" ]]; then + GNU_ARCH=aarch64 + else + GNU_ARCH=x86_64 fi - if [[ "$CIBW_ARCHS" == *" "* ]]; then - echo "ERROR: Pillow macOS builds only support a single architecture in CIBW_ARCHS." - exit 1 + + IOS_SDK_PATH=$(xcrun --sdk $IOS_SDK --show-sdk-path) + if [[ "$IOS_SDK" == "iphonesimulator" ]]; then + CMAKE_SYSTEM_NAME=iOS + IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET-simulator + else + CMAKE_SYSTEM_NAME=iOS + IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET fi + # GNU Autotools doesn't recognize the existence of arm64-apple-ios-simulator + # as a valid host. However, the only difference between arm64-apple-ios and + # arm64-apple-ios-simulator is the choice of sysroot, and that is + # coordinated by CC,CFLAGS etc. From the perspective of configure, the two + # platforms are identical, so we can use arm64-apple-ios consistently. + # This (mostly) avoids us needing to patch config.sub in dependency sources. + HOST_CONFIGURE_FLAGS="--disable-shared --enable-static --host=$GNU_ARCH-apple-ios --build=$GNU_ARCH-apple-darwin" + + # Cmake has native support for iOS. However, most of that support is based + # on using the Xcode builder, which isn't very helpful for most of Pillow's + # dependencies. Therefore, we lean on the OSX configurations, plus CC/CFLAGS + # etc to ensure the right sysroot is selected. + 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" + + # Meson needs to be pointed at a cross-platform configuration file + # This will be generated once CC etc have been evaluated. + HOST_MESON_FLAGS="--cross-file $WORKDIR/meson-cross.txt -Dprefer_static=true -Ddefault_library=static" + +elif [[ "$(uname -s)" == "Darwin" ]]; then # Build macOS dependencies in `build/darwin` # Install them into `build/deps/darwin` + PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}" WORKDIR=$(pwd)/build/darwin BUILD_PREFIX=$(pwd)/build/deps/darwin else # Build prefix will default to /usr/local + PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}" WORKDIR=$(pwd)/build MB_ML_LIBC=${AUDITWHEEL_POLICY::9} MB_ML_VER=${AUDITWHEEL_POLICY:9} fi -PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}" # Define custom utilities source wheels/multibuild/common_utils.sh @@ -36,7 +87,9 @@ fi ARCHIVE_SDIR=pillow-depends-main -# Package versions for fresh source builds +# Package versions for fresh source builds. Version numbers with "Patched" +# annotations have a source code patch that is required for some platforms. If +# you change those versions, ensure the patch is also updated. FREETYPE_VERSION=2.13.3 HARFBUZZ_VERSION=11.2.1 LIBPNG_VERSION=1.6.49 @@ -47,31 +100,57 @@ TIFF_VERSION=4.7.0 LCMS2_VERSION=2.17 ZLIB_VERSION=1.3.1 ZLIB_NG_VERSION=2.2.4 -LIBWEBP_VERSION=1.5.0 +LIBWEBP_VERSION=1.5.0 # Patched BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.17.0 -BROTLI_VERSION=1.1.0 +BROTLI_VERSION=1.1.0 # Patched function build_pkg_config { if [ -e pkg-config-stamp ]; then return; fi - # This essentially duplicates the Homebrew recipe - CFLAGS="$CFLAGS -Wno-int-conversion" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \ + # This essentially duplicates the Homebrew recipe. + # On iOS, we need a binary that can be executed on the build machine; but we + # can create a host-specific pc-path to store iOS .pc files. To ensure a + # macOS-compatible build, we temporarily clear environment flags that set + # iOS-specific values. + if [[ -n "$IOS_SDK" ]]; then + ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS + ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET + unset HOST_CONFIGURE_FLAGS + unset IPHONEOS_DEPLOYMENT_TARGET + fi + + CFLAGS="$CFLAGS -Wno-int-conversion" CPPFLAGS="" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \ --disable-debug --disable-host-tool --with-internal-glib \ --with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \ --with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include + + if [[ -n "$IOS_SDK" ]]; then + HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS + IPHONEOS_DEPLOYMENT_TARGET=$ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET + fi; + export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config touch pkg-config-stamp } function build_zlib_ng { if [ -e zlib-stamp ]; then return; fi + # zlib-ng uses a "configure" script, but it's not a GNU autotools script, so + # it doesn't honor the usual flags. Temporarily disable any + # cross-compilation flags. + ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS + unset HOST_CONFIGURE_FLAGS + build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat - if [ -n "$IS_MACOS" ]; then + HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS + + if [ -n "$IS_MACOS" ] && [ -z "$IOS_SDK" ]; then # Ensure that on macOS, the library name is an absolute path, not an # @rpath, so that delocate picks up the right library (and doesn't need # DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an - # option to control the install_name. + # option to control the install_name. This isn't needed on iOS, as iOS + # only builds the static library. install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib fi touch zlib-stamp @@ -79,20 +158,25 @@ function build_zlib_ng { function build_brotli { if [ -e brotli-stamp ]; then return; fi + local name=brotli + local version=$BROTLI_VERSION local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) (cd $out_dir \ - && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ + && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \ && make install) touch brotli-stamp } function build_harfbuzz { if [ -e harfbuzz-stamp ]; then return; fi - python3 -m pip install meson ninja + local name=harfbuzz + local version=$HARFBUZZ_VERSION + + python3 -m pip install --disable-pip-version-check meson ninja local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) (cd $out_dir \ - && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled) + && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled $HOST_MESON_FLAGS) (cd $out_dir/build \ && meson install) touch harfbuzz-stamp @@ -110,19 +194,19 @@ function build { fi build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto - if [ -n "$IS_MACOS" ]; then + if [[ -n "$IS_MACOS" ]]; then build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist else - sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc + sed "s/\$\{pc_sysrootdir\}//" $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc fi build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib build_libjpeg_turbo - if [ -n "$IS_MACOS" ]; then + if [[ -n "$IS_MACOS" ]]; then # Custom tiff build to include jpeg; by default, configure won't include - # headers/libs in the custom macOS prefix. Explicitly disable webp, + # headers/libs in the custom macOS/iOS prefix. Explicitly disable webp, # libdeflate and zstd, because on x86_64 macs, it will pick up the # Homebrew versions of those libraries from /usr/local. build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \ @@ -146,14 +230,44 @@ function build { build_brotli - if [ -n "$IS_MACOS" ]; then + if [[ -n "$IS_MACOS" ]]; then # Custom freetype build build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no else build_freetype fi - build_harfbuzz + if [[ -z "$IOS_SDK" ]]; then + # On iOS, there's no vendor-provided raqm, and we can't ship it due to + # licensing, so there's no point building harfbuzz. + build_harfbuzz + fi +} + +function create_meson_cross_config { + cat << EOF > $WORKDIR/meson-cross.txt +[binaries] +pkg-config = '$BUILD_PREFIX/bin/pkg-config' +cmake = '$(which cmake)' +c = '$CC' +cpp = '$CXX' +strip = '$STRIP' + +[built-in options] +c_args = '$CFLAGS -I$BUILD_PREFIX/include' +cpp_args = '$CXXFLAGS -I$BUILD_PREFIX/include' +c_link_args = '$CFLAGS -L$BUILD_PREFIX/lib' +cpp_link_args = '$CFLAGS -L$BUILD_PREFIX/lib' + +[host_machine] +system = 'darwin' +subsystem = 'ios' +kernel = 'xnu' +cpu_family = '$(uname -m)' +cpu = '$(uname -m)' +endian = 'little' + +EOF } # Perform all dependency builds in the build subfolder. @@ -172,24 +286,40 @@ if [[ ! -d $WORKDIR/pillow-depends-main ]]; then fi if [[ -n "$IS_MACOS" ]]; then - # Homebrew (or similar packaging environments) install can contain some of - # the libraries that we're going to build. However, they may be compiled - # with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use, - # and they may bring in other dependencies that we don't want. The same will - # be true of any other locations on the path. To avoid conflicts, strip the - # path down to the bare minimum (which, on macOS, won't include any - # development dependencies). - export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" - export CMAKE_PREFIX_PATH=$BUILD_PREFIX - # Ensure the basic structure of the build prefix directory exists. mkdir -p "$BUILD_PREFIX/bin" mkdir -p "$BUILD_PREFIX/lib" - # Ensure pkg-config is available + # Ensure pkg-config is available. This is done *before* setting CC, CFLAGS + # etc to ensure that the build is *always* a macOS build, even when building + # for iOS. build_pkg_config - # Ensure cmake is available - python3 -m pip install cmake + + # Ensure cmake is available, and that the default prefix used by CMake is + # the build prefix + python3 -m pip install --disable-pip-version-check cmake + export CMAKE_PREFIX_PATH=$BUILD_PREFIX + + if [[ -n "$IOS_SDK" ]]; then + export AR="$(xcrun --find --sdk $IOS_SDK ar)" + export CPP="$(xcrun --find --sdk $IOS_SDK clang) -E" + export CC=$(xcrun --find --sdk $IOS_SDK clang) + export CXX=$(xcrun --find --sdk $IOS_SDK clang++) + export LD=$(xcrun --find --sdk $IOS_SDK ld) + export STRIP=$(xcrun --find --sdk $IOS_SDK strip) + + CPPFLAGS="$CPPFLAGS --sysroot=$IOS_SDK_PATH" + CFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET" + CXXFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET" + + # Having IPHONEOS_DEPLOYMENT_TARGET in the environment causes problems + # with some cross-building toolchains, because it introduces implicit + # behavior into clang. + unset IPHONEOS_DEPLOYMENT_TARGET + + # Now that we know CC etc, we can create a meson cross-configuration file + create_meson_cross_config + fi fi wrap_wheel_builder build diff --git a/.github/workflows/wheels-test.ps1 b/.github/workflows/wheels-test.ps1 index 54e7fbbfc30..360d5b6502e 100644 --- a/.github/workflows/wheels-test.ps1 +++ b/.github/workflows/wheels-test.ps1 @@ -23,7 +23,7 @@ cd $pillow if (!$?) { exit $LASTEXITCODE } & $venv\Scripts\$python selftest.py if (!$?) { exit $LASTEXITCODE } -& $venv\Scripts\$python -m pytest -vx Tests\check_wheel.py +& $venv\Scripts\$python -m pytest -vx checks\check_wheel.py if (!$?) { exit $LASTEXITCODE } & $venv\Scripts\$python -m pytest -vx Tests if (!$?) { exit $LASTEXITCODE } diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index ce83a4278cd..771400d9dc5 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -25,8 +25,6 @@ else yum install -y fribidi fi -python3 -m pip install numpy - if [ ! -d "test-images-main" ]; then curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip unzip pillow-test-images.zip @@ -35,5 +33,5 @@ fi # Runs tests python3 selftest.py -python3 -m pytest Tests/check_wheel.py +python3 -m pytest checks/check_wheel.py python3 -m pytest diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 72516651f42..d1894ad862e 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -51,40 +51,66 @@ jobs: matrix: include: - name: "macOS 10.10 x86_64" + platform: macos os: macos-13 cibw_arch: x86_64 build: "cp3{9,10,11}*" macosx_deployment_target: "10.10" - name: "macOS 10.13 x86_64" + platform: macos os: macos-13 cibw_arch: x86_64 build: "cp3{12,13}*" macosx_deployment_target: "10.13" - name: "macOS 10.15 x86_64" + platform: macos os: macos-13 cibw_arch: x86_64 build: "pp3*" macosx_deployment_target: "10.15" - name: "macOS arm64" + platform: macos os: macos-latest cibw_arch: arm64 macosx_deployment_target: "11.0" - name: "manylinux2014 and musllinux x86_64" + platform: linux os: ubuntu-latest cibw_arch: x86_64 - name: "manylinux_2_28 x86_64" + platform: linux os: ubuntu-latest cibw_arch: x86_64 build: "*manylinux*" manylinux: "manylinux_2_28" - name: "manylinux2014 and musllinux aarch64" + platform: linux os: ubuntu-24.04-arm cibw_arch: aarch64 - name: "manylinux_2_28 aarch64" + platform: linux os: ubuntu-24.04-arm cibw_arch: aarch64 build: "*manylinux*" manylinux: "manylinux_2_28" + - name: "iOS arm64 device" + platform: ios + os: macos-13 + cibw_arch: arm64 + build: "*iphoneos" + iphoneos_deployment_target: "13.0" + - name: "iOS arm64 simulator" + platform: ios + os: macos-13 + cibw_arch: arm64 + build: "*iphonesimulator" + iphoneos_deployment_target: "13.0" + - name: "iOS x86_64 simulator" + platform: ios + os: macos-latest + cibw_arch: x86_64 + build: "*iphonesimulator" + iphoneos_deployment_target: "13.0" steps: - uses: actions/checkout@v4 with: @@ -103,6 +129,7 @@ jobs: run: | python3 -m cibuildwheel --output-dir wheelhouse env: + CIBW_PLATFORM: ${{ matrix.platform }} CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BUILD: ${{ matrix.build }} CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy @@ -111,10 +138,11 @@ jobs: CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} + IPHONEOS_DEPLOYMENT_TARGET: ${{ matrix.iphoneos_deployment_target }} - uses: actions/upload-artifact@v4 with: - name: dist-${{ matrix.os }}${{ matrix.macosx_deployment_target && format('-{0}', matrix.macosx_deployment_target) }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }} + name: dist-${{ matrix.name }} path: ./wheelhouse/*.whl windows: diff --git a/.gitignore b/.gitignore index 3033c2ea7ae..e373f5a738e 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,10 @@ docs/_build/ # JetBrains .idea +# Files downloaded as part of the build process +pillow-depends-main.zip +pillow-test-images.zip + # Extra test images installed from python-pillow/test-images Tests/images/README.md Tests/images/crash_1.tif diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1a054e008f..915b2ae21cc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: rev: v1.5.5 hooks: - id: remove-tabs - exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) + exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$) - repo: https://github.com/pre-commit/mirrors-clang-format rev: v20.1.5 @@ -46,9 +46,9 @@ repos: - id: check-yaml args: [--allow-multiple-documents] - id: end-of-file-fixer - exclude: ^Tests/images/ + exclude: ^Tests/images/|\.patch$ - id: trailing-whitespace - exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ + exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$ - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.33.0 diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index e42ec90aa54..9e357f30df8 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -10,8 +10,12 @@ from PIL import Image, features from Tests.helper import skip_unless_feature -if sys.platform.startswith("win32"): - pytest.skip("Fuzzer is linux only", allow_module_level=True) +if sys.platform.startswith("win32") or sys.platform in {"ios", "android"}: + pytest.skip( + "Fuzzer doesn't run on Windows or mobile", + allow_module_level=True, + ) + libjpeg_turbo_version = features.version("libjpeg_turbo") if libjpeg_turbo_version is not None: version = packaging.version.parse(libjpeg_turbo_version) diff --git a/Tests/test_main.py b/Tests/test_main.py index 2582dbee3db..a7886592c68 100644 --- a/Tests/test_main.py +++ b/Tests/test_main.py @@ -7,6 +7,10 @@ import pytest +@pytest.mark.skipif( + sys.platform in {"ios", "android"}, + reason="Not required on mobile", +) @pytest.mark.parametrize( "args, report", ((["PIL"], False), (["PIL", "--report"], True), (["PIL.report"], True)), diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index 8235daf3282..9669f485a6f 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,5 +1,7 @@ from __future__ import annotations +from importlib.metadata import metadata + import pytest from PIL import __version__ @@ -9,7 +11,7 @@ def test_pyroma() -> None: # Arrange - data = pyroma.projectdata.get_data(".") + data = pyroma.projectdata.map_metadata_keys(metadata("Pillow")) # Act rating = pyroma.ratings.rate(data) diff --git a/Tests/32bit_segfault_check.py b/checks/32bit_segfault_check.py similarity index 100% rename from Tests/32bit_segfault_check.py rename to checks/32bit_segfault_check.py diff --git a/Tests/check_fli_oob.py b/checks/check_fli_oob.py similarity index 100% rename from Tests/check_fli_oob.py rename to checks/check_fli_oob.py diff --git a/Tests/check_fli_overflow.py b/checks/check_fli_overflow.py similarity index 100% rename from Tests/check_fli_overflow.py rename to checks/check_fli_overflow.py diff --git a/Tests/check_icns_dos.py b/checks/check_icns_dos.py similarity index 100% rename from Tests/check_icns_dos.py rename to checks/check_icns_dos.py diff --git a/Tests/check_imaging_leaks.py b/checks/check_imaging_leaks.py similarity index 100% rename from Tests/check_imaging_leaks.py rename to checks/check_imaging_leaks.py diff --git a/Tests/check_j2k_dos.py b/checks/check_j2k_dos.py similarity index 100% rename from Tests/check_j2k_dos.py rename to checks/check_j2k_dos.py diff --git a/Tests/check_j2k_leaks.py b/checks/check_j2k_leaks.py similarity index 100% rename from Tests/check_j2k_leaks.py rename to checks/check_j2k_leaks.py diff --git a/Tests/check_j2k_overflow.py b/checks/check_j2k_overflow.py similarity index 100% rename from Tests/check_j2k_overflow.py rename to checks/check_j2k_overflow.py diff --git a/Tests/check_jp2_overflow.py b/checks/check_jp2_overflow.py similarity index 100% rename from Tests/check_jp2_overflow.py rename to checks/check_jp2_overflow.py diff --git a/Tests/check_jpeg_leaks.py b/checks/check_jpeg_leaks.py similarity index 100% rename from Tests/check_jpeg_leaks.py rename to checks/check_jpeg_leaks.py diff --git a/Tests/check_large_memory.py b/checks/check_large_memory.py similarity index 100% rename from Tests/check_large_memory.py rename to checks/check_large_memory.py diff --git a/Tests/check_large_memory_numpy.py b/checks/check_large_memory_numpy.py similarity index 100% rename from Tests/check_large_memory_numpy.py rename to checks/check_large_memory_numpy.py diff --git a/Tests/check_libtiff_segfault.py b/checks/check_libtiff_segfault.py similarity index 100% rename from Tests/check_libtiff_segfault.py rename to checks/check_libtiff_segfault.py diff --git a/Tests/check_png_dos.py b/checks/check_png_dos.py similarity index 100% rename from Tests/check_png_dos.py rename to checks/check_png_dos.py diff --git a/Tests/check_release_notes.py b/checks/check_release_notes.py similarity index 100% rename from Tests/check_release_notes.py rename to checks/check_release_notes.py diff --git a/Tests/check_wheel.py b/checks/check_wheel.py similarity index 72% rename from Tests/check_wheel.py rename to checks/check_wheel.py index 9602410da52..49ab3054b10 100644 --- a/Tests/check_wheel.py +++ b/checks/check_wheel.py @@ -5,7 +5,7 @@ from PIL import features -from .helper import is_pypy +from Tests.helper import is_pypy def test_wheel_modules() -> None: @@ -19,6 +19,9 @@ def test_wheel_modules() -> None: assert tkinter except ImportError: expected_modules.remove("tkinter") + elif sys.platform == "ios": + # tkinter is not available on iOS + expected_modules.remove("tkinter") assert set(features.get_supported_modules()) == expected_modules @@ -46,5 +49,11 @@ def test_wheel_features() -> None: expected_features.remove("xcb") elif sys.platform == "darwin" and not is_pypy() and platform.processor() != "arm": expected_features.remove("zlib_ng") + elif sys.platform == "ios": + # Can't distribute raqm due to licensing, and there's no system version; + # fribid and harfbuzz won't be available if raqm isn't available. + expected_features.remove("fribidi") + expected_features.remove("raqm") + expected_features.remove("harfbuzz") assert set(features.get_supported_features()) == expected_features diff --git a/patches/README.md b/patches/README.md new file mode 100644 index 00000000000..ff4a8f0994f --- /dev/null +++ b/patches/README.md @@ -0,0 +1,14 @@ +Although we try to use official sources for dependencies, sometimes the official +sources don't support a platform (especially mobile platforms), or there's a bug +fix/feature that is required to support Pillow's usage. + +This folder contains patches that must be applied to official sources, organized +by the platforms that need those patches. + +Each patch is against the root of the unpacked official tarball, and is named by +appending `.patch` to the end of the tarball that is to be patched. This +includes the full version number; so if the version is bumped, the patch will +at a minimum require a filename change. + +Wherever possible, these patches should be contributed upstream, in the hope that +future Pillow versions won't need to maintain these patches. diff --git a/patches/iOS/brotli-1.1.0.tar.gz.patch b/patches/iOS/brotli-1.1.0.tar.gz.patch new file mode 100644 index 00000000000..9e79266bbdd --- /dev/null +++ b/patches/iOS/brotli-1.1.0.tar.gz.patch @@ -0,0 +1,43 @@ +# Brotli doesn't have explicit support for iOS as a CMAKE_SYSTEM_NAME. +# +diff -ru brotli-1.1.0-orig/CMakeLists.txt brotli-1.1.0/CMakeLists.txt +--- brotli-1.1.0-orig/CMakeLists.txt 2023-08-29 19:00:29 ++++ brotli-1.1.0/CMakeLists.txt 2024-11-07 10:46:26 +@@ -114,6 +114,8 @@ + add_definitions(-DOS_MACOSX) + set(CMAKE_MACOS_RPATH TRUE) + set(CMAKE_INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib") ++elseif(${CMAKE_SYSTEM_NAME} MATCHES "iOS") ++ add_definitions(-DOS_IOS) + endif() + + if(BROTLI_EMSCRIPTEN) +@@ -174,10 +176,12 @@ + + # Installation + if(NOT BROTLI_BUNDLED_MODE) +- install( +- TARGETS brotli +- RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +- ) ++ if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "iOS") ++ install( ++ TARGETS brotli ++ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ++ ) ++ endif() + + install( + TARGETS ${BROTLI_LIBRARIES_CORE} +diff -ru brotli-1.1.0-orig/c/common/platform.h brotli-1.1.0/c/common/platform.h +--- brotli-1.1.0-orig/c/common/platform.h 2023-08-29 19:00:29 ++++ brotli-1.1.0/c/common/platform.h 2024-11-07 10:47:28 +@@ -33,7 +33,7 @@ + #include + #elif defined(OS_FREEBSD) + #include +-#elif defined(OS_MACOSX) ++#elif defined(OS_MACOSX) || defined(OS_IOS) + #include + /* Let's try and follow the Linux convention */ + #define BROTLI_X_BYTE_ORDER BYTE_ORDER diff --git a/patches/iOS/libwebp-1.5.0.tar.gz.patch b/patches/iOS/libwebp-1.5.0.tar.gz.patch new file mode 100644 index 00000000000..8fce7ce6404 --- /dev/null +++ b/patches/iOS/libwebp-1.5.0.tar.gz.patch @@ -0,0 +1,39 @@ +# libwebp example binaries require dependencies that aren't available for iOS builds. +# There's also no easy way to invoke the build to *exclude* the example builds. +# Since we don't need the examples anyway, remove them from the makefile. +# +# As a point of reference, libwebp provides an XCframework build script that involves +# 7 separate invocations of make to avoid building the examples. Patching the makefile +# to remove the examples is a simpler approach, and one that is more compatible with +# the existing multibuild infrastructure. +# +diff -ur libwebp-1.5.0-orig/Makefile.am libwebp-1.5.0/Makefile.am +--- libwebp-1.5.0-orig/Makefile.am 2024-12-20 09:17:50 ++++ libwebp-1.5.0/Makefile.am 2025-01-09 11:24:17 +@@ -5,5 +5,3 @@ + if BUILD_EXTRAS + SUBDIRS += extras + endif +- +-SUBDIRS += examples +diff -ur libwebp-1.5.0-orig/Makefile.in libwebp-1.5.0/Makefile.in +--- libwebp-1.5.0-orig/Makefile.in 2024-12-20 09:52:53 ++++ libwebp-1.5.0/Makefile.in 2025-01-09 11:24:17 +@@ -156,7 +156,7 @@ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +-DIST_SUBDIRS = sharpyuv src imageio man extras examples ++DIST_SUBDIRS = sharpyuv src imageio man extras + am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/src/webp/config.h.in AUTHORS COPYING ChangeLog \ + NEWS README.md ar-lib compile config.guess config.sub \ +@@ -351,7 +351,7 @@ + top_srcdir = @top_srcdir@ + webp_libname_prefix = @webp_libname_prefix@ + ACLOCAL_AMFLAGS = -I m4 +-SUBDIRS = sharpyuv src imageio man $(am__append_1) examples ++SUBDIRS = sharpyuv src imageio man $(am__append_1) + EXTRA_DIST = COPYING autogen.sh + all: all-recursive + diff --git a/pyproject.toml b/pyproject.toml index 683ab24ef07..709c141b1a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,15 +103,49 @@ before-all = ".github/workflows/wheels-dependencies.sh" build-verbosity = 1 config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" -# Disable platform guessing on macOS -macos.config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable platform-guessing=disable" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" +test-requires = [ + "numpy", +] +xbuild-tools = [ ] + +[tool.cibuildwheel.macos] +# Disable platform guessing on macOS to avoid picking up homebrew etc +config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable platform-guessing=disable" [tool.cibuildwheel.macos.environment] +# Isolate macOS build environment from homebrew etc PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" +[tool.cibuildwheel.ios] +# Disable platform guessing on iOS, and disable raqm (since there won't be a +# vendor version, and we can't distribute it due to licensing) +config-settings = "raqm=disable imagequant=disable platform-guessing=disable" + +# iOS needs to be given a specific pytest invocation and list of test sources. +test-sources = [ + "checks", + "Tests", + "pyproject.toml", + "selftest.py", +] +test-command = "python -m pytest -vv selftest.py checks/check_wheel.py Tests" + +# There's no numpy wheel for iOS (yet...) +test-requires = [ ] + +[[tool.cibuildwheel.overrides]] +# iOS environment is isolated by cibuildwheel, but needs the dependencies +select = "*_iphoneos" +environment.PATH = "$(pwd)/build/deps/iphoneos/bin:$PATH" + +[[tool.cibuildwheel.overrides]] +# iOS simulator environment is isolated by cibuildwheel, but needs the dependencies +select = "*_iphonesimulator" +environment.PATH = "$(pwd)/build/deps/iphonesimulator/bin:$PATH" + [tool.black] exclude = "wheels/multibuild" @@ -168,7 +202,7 @@ lint.isort.required-imports = [ max_supported_python = "3.13" [tool.pytest.ini_options] -addopts = "-ra --color=yes" +addopts = "-ra --color=auto" testpaths = [ "Tests", ] diff --git a/setup.py b/setup.py index 3716a7b9f66..99097971da4 100644 --- a/setup.py +++ b/setup.py @@ -474,6 +474,19 @@ def get_macos_sdk_path(self) -> str | None: sdk_path = commandlinetools_sdk_path return sdk_path + def get_ios_sdk_path(self) -> str: + try: + sdk = sys.implementation._multiarch.split("-")[-1] + _dbg("Using %s SDK", sdk) + return ( + subprocess.check_output(["xcrun", "--show-sdk-path", "--sdk", sdk]) + .strip() + .decode("latin1") + ) + except Exception: + msg = "Unable to identify location of iOS SDK." + raise ValueError(msg) + def build_extensions(self) -> None: library_dirs: list[str] = [] include_dirs: list[str] = [] @@ -615,14 +628,6 @@ def build_extensions(self) -> None: _add_directory(library_dirs, "/usr/X11/lib") _add_directory(include_dirs, "/usr/X11/include") - # Add the macOS SDK path. - sdk_path = self.get_macos_sdk_path() - if sdk_path: - _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) - _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) - - for extension in self.extensions: - extension.extra_compile_args = ["-Wno-nullability-completeness"] elif sys.platform.startswith(("linux", "gnu", "freebsd")): for dirname in _find_library_dirs_ldconfig(): _add_directory(library_dirs, dirname) @@ -677,6 +682,27 @@ def build_extensions(self) -> None: _add_directory(library_dirs, os.path.join(best_path, "lib")) _add_directory(include_dirs, os.path.join(best_path, "include")) + elif sys.platform == "darwin": + # Alwasy include the macOS SDK path. + sdk_path = self.get_macos_sdk_path() + if sdk_path: + _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) + _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) + + for extension in self.extensions: + extension.extra_compile_args = ["-Wno-nullability-completeness"] + + elif sys.platform == "ios": + # Always include the iOS SDK path. + sdk_path = self.get_ios_sdk_path() + + # Add the iOS SDK path. + _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) + _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) + + for extension in self.extensions: + extension.extra_compile_args = ["-Wno-nullability-completeness"] + # # insert new dirs *before* default libs, to avoid conflicts # between Python PYD stub libs and real libraries @@ -878,6 +904,9 @@ def build_extensions(self) -> None: # so we have to guess; by default it is defined in all Windows builds. # See #4237, #5243, #5359 for more information. defs.append(("USE_WIN32_FILEIO", None)) + elif sys.platform == "ios": + # Ensure transitive dependencies are linked. + libs.append("lzma") if feature.get("jpeg"): libs.append(feature.get("jpeg")) defs.append(("HAVE_LIBJPEG", None)) @@ -894,6 +923,9 @@ def build_extensions(self) -> None: defs.append(("HAVE_LIBIMAGEQUANT", None)) if feature.get("xcb"): libs.append(feature.get("xcb")) + if sys.platform == "ios": + # Ensure transitive dependencies are linked. + libs.append("Xau") defs.append(("HAVE_XCB", None)) if sys.platform == "win32": libs.extend(["kernel32", "user32", "gdi32"]) @@ -925,6 +957,11 @@ def build_extensions(self) -> None: libs.append(feature.get("fribidi")) else: # building FriBiDi shim from src/thirdparty srcs.append("src/thirdparty/fribidi-shim/fribidi.c") + + if sys.platform == "ios": + # Ensure transitive dependencies are linked. + libs.extend(["z", "bz2", "brotlicommon", "brotlidec", "png"]) + self._update_extension("PIL._imagingft", libs, defs, srcs) else: @@ -941,6 +978,9 @@ def build_extensions(self) -> None: webp = feature.get("webp") if isinstance(webp, str): libs = [webp, webp + "mux", webp + "demux"] + if sys.platform == "ios": + # Ensure transitive dependencies are linked. + libs.append("sharpyuv") self._update_extension("PIL._webp", libs) else: self._remove_extension("PIL._webp") From 59f3557f06aa5fc45405f298ca0e529362fa4096 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 21 Jun 2025 09:19:03 +0000 Subject: [PATCH 02/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- checks/check_wheel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/checks/check_wheel.py b/checks/check_wheel.py index 49ab3054b10..413dc747309 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -4,7 +4,6 @@ import sys from PIL import features - from Tests.helper import is_pypy From e430bde7e211182a24f7133d22049a13a715eb54 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 21 Jun 2025 17:33:09 +0800 Subject: [PATCH 03/34] Corrections to CI configuration. --- .github/workflows/wheels-dependencies.sh | 21 +++++++++++++-------- .github/workflows/wheels.yml | 9 +++------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index addf0b46bf0..982ba7b104a 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -3,20 +3,24 @@ # Safety check - Pillow builds require that CIBW_ARCHS is set, and that it only # contains a single value (even though cibuildwheel allows multiple values in # CIBW_ARCHS). -if [[ -z "$CIBW_ARCHS" ]]; then - echo "ERROR: Pillow builds require CIBW_ARCHS be defined." - exit 1 -fi -if [[ "$CIBW_ARCHS" == *" "* ]]; then - echo "ERROR: Pillow builds only support a single architecture in CIBW_ARCHS." - exit 1 -fi +echo "ENV CHECK: CIBW_ARCHS=$CIBW_ARCHS" +function check_cibw_archs { + if [[ -z "$CIBW_ARCHS" ]]; then + echo "ERROR: Pillow builds require CIBW_ARCHS be defined." + exit 1 + fi + if [[ "$CIBW_ARCHS" == *" "* ]]; then + echo "ERROR: Pillow builds only support a single architecture in CIBW_ARCHS." + exit 1 + fi +} # Setup that needs to be done before multibuild utils are invoked. Process # potential cross-build platforms before native platforms to ensure that we pick # up the cross environment. PROJECTDIR=$(pwd) if [[ "$CIBW_PLATFORM" == "ios" ]]; then + check_cibw_archs # On iOS, CIBW_ARCHS is actually a multi-arch - arm64_iphoneos, # arm64_iphonesimulator or x86_64_iphonesimulator. Split into the CPU # platform, and the iOS SDK. @@ -65,6 +69,7 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then HOST_MESON_FLAGS="--cross-file $WORKDIR/meson-cross.txt -Dprefer_static=true -Ddefault_library=static" elif [[ "$(uname -s)" == "Darwin" ]]; then + check_cibw_archs # Build macOS dependencies in `build/darwin` # Install them into `build/deps/darwin` PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}" diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index d1894ad862e..9a62016a745 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -96,20 +96,17 @@ jobs: - name: "iOS arm64 device" platform: ios os: macos-13 - cibw_arch: arm64 - build: "*iphoneos" + cibw_arch: arm64_iphoneos iphoneos_deployment_target: "13.0" - name: "iOS arm64 simulator" platform: ios os: macos-13 - cibw_arch: arm64 - build: "*iphonesimulator" + cibw_arch: arm64_iphonesimulator iphoneos_deployment_target: "13.0" - name: "iOS x86_64 simulator" platform: ios os: macos-latest - cibw_arch: x86_64 - build: "*iphonesimulator" + cibw_arch: x86_64_iphonesimulator iphoneos_deployment_target: "13.0" steps: - uses: actions/checkout@v4 From f42822239e0c7c8e38bce6f22fb924783c3052c9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 21 Jun 2025 17:58:28 +0800 Subject: [PATCH 04/34] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- checks/check_wheel.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/checks/check_wheel.py b/checks/check_wheel.py index 413dc747309..a23006c6b85 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -50,7 +50,7 @@ def test_wheel_features() -> None: expected_features.remove("zlib_ng") elif sys.platform == "ios": # Can't distribute raqm due to licensing, and there's no system version; - # fribid and harfbuzz won't be available if raqm isn't available. + # fribidi and harfbuzz won't be available if raqm isn't available. expected_features.remove("fribidi") expected_features.remove("raqm") expected_features.remove("harfbuzz") diff --git a/setup.py b/setup.py index 99097971da4..21ccbd76567 100644 --- a/setup.py +++ b/setup.py @@ -683,7 +683,7 @@ def build_extensions(self) -> None: _add_directory(include_dirs, os.path.join(best_path, "include")) elif sys.platform == "darwin": - # Alwasy include the macOS SDK path. + # Always include the macOS SDK path. sdk_path = self.get_macos_sdk_path() if sdk_path: _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) From 980a9da09387376b969cf4078fa412aed9dda515 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 21 Jun 2025 17:58:58 +0800 Subject: [PATCH 05/34] Correct regex handling. --- .github/workflows/wheels-dependencies.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 982ba7b104a..fd8126c4bd2 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -2,8 +2,8 @@ # Safety check - Pillow builds require that CIBW_ARCHS is set, and that it only # contains a single value (even though cibuildwheel allows multiple values in -# CIBW_ARCHS). -echo "ENV CHECK: CIBW_ARCHS=$CIBW_ARCHS" +# CIBW_ARCHS). This check doesn't work on Linux because of how the CIBW_ARCHS +# variable is exposed. function check_cibw_archs { if [[ -z "$CIBW_ARCHS" ]]; then echo "ERROR: Pillow builds require CIBW_ARCHS be defined." @@ -204,7 +204,7 @@ function build { build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist else - sed "s/\$\{pc_sysrootdir\}//" $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc + sed "s/\${pc_sysrootdir\}//" $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc fi build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib From 8c3b4bb38358877af52bedc6fc38e44033633f24 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 21 Jun 2025 19:16:58 +0800 Subject: [PATCH 06/34] Correct MANIFEST.in --- MANIFEST.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 48085b82ed0..564976637de 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include *.c include *.h include *.in include *.md +include *.patch include *.py include *.rst include *.sh @@ -13,6 +14,8 @@ include LICENSE include Makefile include tox.ini graft Tests +graft checks +graft patches graft src graft depends graft winbuild From cb62e6a0f03c11c64296b469bd6bffaee7c2f92d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 21 Jun 2025 19:20:07 +0800 Subject: [PATCH 07/34] Correct the OS image definitions for iOS CI runs. --- .github/workflows/wheels.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 9a62016a745..a0dc61a9ddb 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -95,17 +95,17 @@ jobs: manylinux: "manylinux_2_28" - name: "iOS arm64 device" platform: ios - os: macos-13 + os: macos-latest cibw_arch: arm64_iphoneos iphoneos_deployment_target: "13.0" - name: "iOS arm64 simulator" platform: ios - os: macos-13 + os: macos-latest cibw_arch: arm64_iphonesimulator iphoneos_deployment_target: "13.0" - name: "iOS x86_64 simulator" platform: ios - os: macos-latest + os: macos-13 cibw_arch: x86_64_iphonesimulator iphoneos_deployment_target: "13.0" steps: From 2cc0cd197c4a9a2f0e8149e4c15bced4263acf9b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 22 Jun 2025 10:27:48 +0800 Subject: [PATCH 08/34] Remove unnecessary manifest entry. --- MANIFEST.in | 1 - 1 file changed, 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 564976637de..95a6b1b9297 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,7 +2,6 @@ include *.c include *.h include *.in include *.md -include *.patch include *.py include *.rst include *.sh From fc8ed5ce661ac0ab8f432c9bd8be85698e02fcb2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 22 Jun 2025 13:46:13 +0800 Subject: [PATCH 09/34] Only include macOS/iOS SDKs when platform guessing is enabled. --- setup.py | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/setup.py b/setup.py index 21ccbd76567..346d43463b9 100644 --- a/setup.py +++ b/setup.py @@ -628,6 +628,26 @@ def build_extensions(self) -> None: _add_directory(library_dirs, "/usr/X11/lib") _add_directory(include_dirs, "/usr/X11/include") + # Add the macOS SDK path. + sdk_path = self.get_macos_sdk_path() + if sdk_path: + _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) + _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) + + for extension in self.extensions: + extension.extra_compile_args = ["-Wno-nullability-completeness"] + + elif sys.platform == "ios": + # Add the iOS SDK path. + sdk_path = self.get_ios_sdk_path() + + # Add the iOS SDK path. + _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) + _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) + + for extension in self.extensions: + extension.extra_compile_args = ["-Wno-nullability-completeness"] + elif sys.platform.startswith(("linux", "gnu", "freebsd")): for dirname in _find_library_dirs_ldconfig(): _add_directory(library_dirs, dirname) @@ -682,27 +702,6 @@ def build_extensions(self) -> None: _add_directory(library_dirs, os.path.join(best_path, "lib")) _add_directory(include_dirs, os.path.join(best_path, "include")) - elif sys.platform == "darwin": - # Always include the macOS SDK path. - sdk_path = self.get_macos_sdk_path() - if sdk_path: - _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) - _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) - - for extension in self.extensions: - extension.extra_compile_args = ["-Wno-nullability-completeness"] - - elif sys.platform == "ios": - # Always include the iOS SDK path. - sdk_path = self.get_ios_sdk_path() - - # Add the iOS SDK path. - _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) - _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) - - for extension in self.extensions: - extension.extra_compile_args = ["-Wno-nullability-completeness"] - # # insert new dirs *before* default libs, to avoid conflicts # between Python PYD stub libs and real libraries From 19e3b1a2968d95e2e55782d82490cb4c10befb95 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 22 Jun 2025 12:11:51 +1000 Subject: [PATCH 10/34] Removed unnecessary code --- .github/workflows/wheels-dependencies.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index fd8126c4bd2..538c851815d 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -163,8 +163,6 @@ function build_zlib_ng { function build_brotli { if [ -e brotli-stamp ]; then return; fi - local name=brotli - local version=$BROTLI_VERSION local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) (cd $out_dir \ && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \ @@ -174,10 +172,8 @@ function build_brotli { function build_harfbuzz { if [ -e harfbuzz-stamp ]; then return; fi - local name=harfbuzz - local version=$HARFBUZZ_VERSION - python3 -m pip install --disable-pip-version-check meson ninja + python3 -m pip install meson ninja local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) (cd $out_dir \ @@ -302,7 +298,7 @@ if [[ -n "$IS_MACOS" ]]; then # Ensure cmake is available, and that the default prefix used by CMake is # the build prefix - python3 -m pip install --disable-pip-version-check cmake + python3 -m pip install cmake export CMAKE_PREFIX_PATH=$BUILD_PREFIX if [[ -n "$IOS_SDK" ]]; then From ecadd4cade555905b102b17d6c9b6e67e9e3ea16 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 22 Jun 2025 22:20:13 +1000 Subject: [PATCH 11/34] Simplified code --- .github/workflows/wheels-dependencies.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 538c851815d..739a73a8d63 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -42,11 +42,10 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then fi IOS_SDK_PATH=$(xcrun --sdk $IOS_SDK --show-sdk-path) + CMAKE_SYSTEM_NAME=iOS if [[ "$IOS_SDK" == "iphonesimulator" ]]; then - CMAKE_SYSTEM_NAME=iOS IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET-simulator else - CMAKE_SYSTEM_NAME=iOS IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET fi From 7bd64c48dddbe9665f03088eb2dcf6c8660fc51b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 23 Jun 2025 07:09:12 +1000 Subject: [PATCH 12/34] Do not specify default iPhone deployment target --- .github/workflows/wheels.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index a0dc61a9ddb..64c52956edf 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -97,17 +97,14 @@ jobs: platform: ios os: macos-latest cibw_arch: arm64_iphoneos - iphoneos_deployment_target: "13.0" - name: "iOS arm64 simulator" platform: ios os: macos-latest cibw_arch: arm64_iphonesimulator - iphoneos_deployment_target: "13.0" - name: "iOS x86_64 simulator" platform: ios os: macos-13 cibw_arch: x86_64_iphonesimulator - iphoneos_deployment_target: "13.0" steps: - uses: actions/checkout@v4 with: @@ -135,7 +132,6 @@ jobs: CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} - IPHONEOS_DEPLOYMENT_TARGET: ${{ matrix.iphoneos_deployment_target }} - uses: actions/upload-artifact@v4 with: From 5cad00f06d8fbd7bd82cec5a882e3a58ab53c580 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 23 Jun 2025 11:14:29 +0800 Subject: [PATCH 13/34] Modify comment for consistency. Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 739a73a8d63..684b4b0e9b7 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -27,7 +27,7 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then PLAT=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\1/") IOS_SDK=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\2/") - # Build iOS builds in `build/iphoneos` or `build/iphonesimulator/` + # Build iOS builds in `build/iphoneos` or `build/iphonesimulator` # (depending on the build target). Install them into `build/deps/iphoneos` # or `build/deps/iphonesimulator` WORKDIR=$(pwd)/build/$IOS_SDK From a7afc503632ede2ce3afb482c09e8670b3d21bc8 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 26 Jun 2025 08:53:48 +0800 Subject: [PATCH 14/34] Add notes about removing patches. --- .github/workflows/wheels-dependencies.sh | 4 ++-- patches/iOS/brotli-1.1.0.tar.gz.patch | 5 ++++- patches/iOS/libwebp-1.5.0.tar.gz.patch | 3 +++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 684b4b0e9b7..83041dafb45 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -104,10 +104,10 @@ TIFF_VERSION=4.7.0 LCMS2_VERSION=2.17 ZLIB_VERSION=1.3.1 ZLIB_NG_VERSION=2.2.4 -LIBWEBP_VERSION=1.5.0 # Patched +LIBWEBP_VERSION=1.5.0 # Patched; next release won't need patching. See patch file. BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.17.0 -BROTLI_VERSION=1.1.0 # Patched +BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file. function build_pkg_config { if [ -e pkg-config-stamp ]; then return; fi diff --git a/patches/iOS/brotli-1.1.0.tar.gz.patch b/patches/iOS/brotli-1.1.0.tar.gz.patch index 9e79266bbdd..f165a9ac12f 100644 --- a/patches/iOS/brotli-1.1.0.tar.gz.patch +++ b/patches/iOS/brotli-1.1.0.tar.gz.patch @@ -1,4 +1,7 @@ -# Brotli doesn't have explicit support for iOS as a CMAKE_SYSTEM_NAME. +# Brotli 1.1.0 doesn't have explicit support for iOS as a CMAKE_SYSTEM_NAME. +# That release was from 2023; there have been subsequent changes that allow +# Brotli to build on iOS without any patches, as long as -DBROTLI_BUILD_TOOLS=NO +# is specified on the command line. # diff -ru brotli-1.1.0-orig/CMakeLists.txt brotli-1.1.0/CMakeLists.txt --- brotli-1.1.0-orig/CMakeLists.txt 2023-08-29 19:00:29 diff --git a/patches/iOS/libwebp-1.5.0.tar.gz.patch b/patches/iOS/libwebp-1.5.0.tar.gz.patch index 8fce7ce6404..2ea587554ef 100644 --- a/patches/iOS/libwebp-1.5.0.tar.gz.patch +++ b/patches/iOS/libwebp-1.5.0.tar.gz.patch @@ -6,6 +6,9 @@ # 7 separate invocations of make to avoid building the examples. Patching the makefile # to remove the examples is a simpler approach, and one that is more compatible with # the existing multibuild infrastructure. +# +# In the next release, it should be possible to pass --disable-libwebpexamples +# instead of applying this patch. # diff -ur libwebp-1.5.0-orig/Makefile.am libwebp-1.5.0/Makefile.am --- libwebp-1.5.0-orig/Makefile.am 2024-12-20 09:17:50 From 6ab8e77950ac5c5e7362ea8332c96db95717aea8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 25 Jun 2025 19:47:48 +1000 Subject: [PATCH 15/34] Removed Android --- Tests/oss-fuzz/test_fuzzers.py | 7 ++----- Tests/test_main.py | 5 +---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 9e357f30df8..37d11e0ba4c 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -10,11 +10,8 @@ from PIL import Image, features from Tests.helper import skip_unless_feature -if sys.platform.startswith("win32") or sys.platform in {"ios", "android"}: - pytest.skip( - "Fuzzer doesn't run on Windows or mobile", - allow_module_level=True, - ) +if sys.platform.startswith("win32") or sys.platform == "ios": + pytest.skip("Fuzzer doesn't run on Windows or iOS", allow_module_level=True) libjpeg_turbo_version = features.version("libjpeg_turbo") if libjpeg_turbo_version is not None: diff --git a/Tests/test_main.py b/Tests/test_main.py index a7886592c68..b98c8554348 100644 --- a/Tests/test_main.py +++ b/Tests/test_main.py @@ -7,10 +7,7 @@ import pytest -@pytest.mark.skipif( - sys.platform in {"ios", "android"}, - reason="Not required on mobile", -) +@pytest.mark.skipif(sys.platform == "ios", reason="Not required on mobile") @pytest.mark.parametrize( "args, report", ((["PIL"], False), (["PIL", "--report"], True), (["PIL.report"], True)), From 3524b4e72917dc5d6a417b23dfcfca97f287ea19 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 24 Jun 2025 20:42:59 +1000 Subject: [PATCH 16/34] Updated reason --- Tests/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_main.py b/Tests/test_main.py index b98c8554348..65e7a44d8d0 100644 --- a/Tests/test_main.py +++ b/Tests/test_main.py @@ -7,7 +7,7 @@ import pytest -@pytest.mark.skipif(sys.platform == "ios", reason="Not required on mobile") +@pytest.mark.skipif(sys.platform == "ios", reason="Processes not supported on iOS") @pytest.mark.parametrize( "args, report", ((["PIL"], False), (["PIL", "--report"], True), (["PIL.report"], True)), From e5676027194ffee83103a4aea4ec311d0c0b130f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 24 Jun 2025 20:55:03 +1000 Subject: [PATCH 17/34] Updated capitalisation --- .github/workflows/wheels-dependencies.sh | 8 ++++---- pyproject.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 83041dafb45..9f6a85d9722 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -60,11 +60,11 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then # Cmake has native support for iOS. However, most of that support is based # on using the Xcode builder, which isn't very helpful for most of Pillow's # dependencies. Therefore, we lean on the OSX configurations, plus CC/CFLAGS - # etc to ensure the right sysroot is selected. + # etc. to ensure the right sysroot is selected. 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" # Meson needs to be pointed at a cross-platform configuration file - # This will be generated once CC etc have been evaluated. + # This will be generated once CC etc. have been evaluated. HOST_MESON_FLAGS="--cross-file $WORKDIR/meson-cross.txt -Dprefer_static=true -Ddefault_library=static" elif [[ "$(uname -s)" == "Darwin" ]]; then @@ -291,7 +291,7 @@ if [[ -n "$IS_MACOS" ]]; then mkdir -p "$BUILD_PREFIX/lib" # Ensure pkg-config is available. This is done *before* setting CC, CFLAGS - # etc to ensure that the build is *always* a macOS build, even when building + # etc. to ensure that the build is *always* a macOS build, even when building # for iOS. build_pkg_config @@ -317,7 +317,7 @@ if [[ -n "$IS_MACOS" ]]; then # behavior into clang. unset IPHONEOS_DEPLOYMENT_TARGET - # Now that we know CC etc, we can create a meson cross-configuration file + # Now that we know CC etc., we can create a meson cross-configuration file create_meson_cross_config fi fi diff --git a/pyproject.toml b/pyproject.toml index 709c141b1a9..9590e69c663 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,11 +112,11 @@ test-requires = [ xbuild-tools = [ ] [tool.cibuildwheel.macos] -# Disable platform guessing on macOS to avoid picking up homebrew etc +# Disable platform guessing on macOS to avoid picking up Homebrew etc. config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable platform-guessing=disable" [tool.cibuildwheel.macos.environment] -# Isolate macOS build environment from homebrew etc +# Isolate macOS build environment from Homebrew etc. PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" [tool.cibuildwheel.ios] From 1c2ec9fff9560b4ffa9ddafcc41c4b0260a77616 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 24 Jun 2025 20:57:59 +1000 Subject: [PATCH 18/34] Removed duplicate lines --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index e373f5a738e..3033c2ea7ae 100644 --- a/.gitignore +++ b/.gitignore @@ -80,10 +80,6 @@ docs/_build/ # JetBrains .idea -# Files downloaded as part of the build process -pillow-depends-main.zip -pillow-test-images.zip - # Extra test images installed from python-pillow/test-images Tests/images/README.md Tests/images/crash_1.tif From d393d0937e2ffcd6c9ee7e09eb46200d54e6ec5a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 25 Jun 2025 20:07:37 +1000 Subject: [PATCH 19/34] Simplified code --- .github/workflows/wheels-dependencies.sh | 6 ++---- checks/check_wheel.py | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 9f6a85d9722..d3998236d01 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -43,10 +43,9 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then IOS_SDK_PATH=$(xcrun --sdk $IOS_SDK --show-sdk-path) CMAKE_SYSTEM_NAME=iOS + IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET if [[ "$IOS_SDK" == "iphonesimulator" ]]; then - IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET-simulator - else - IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET + IOS_HOST_TRIPLE=$IOS_HOST_TRIPLE-simulator fi # GNU Autotools doesn't recognize the existence of arm64-apple-ios-simulator @@ -171,7 +170,6 @@ function build_brotli { function build_harfbuzz { if [ -e harfbuzz-stamp ]; then return; fi - python3 -m pip install meson ninja local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) diff --git a/checks/check_wheel.py b/checks/check_wheel.py index a23006c6b85..025ab85904b 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -51,8 +51,6 @@ def test_wheel_features() -> None: elif sys.platform == "ios": # Can't distribute raqm due to licensing, and there's no system version; # fribidi and harfbuzz won't be available if raqm isn't available. - expected_features.remove("fribidi") - expected_features.remove("raqm") - expected_features.remove("harfbuzz") + expected_features -= {"raqm", "fribidi", "harfbuzz"} assert set(features.get_supported_features()) == expected_features From 527d18c2d71af1f6682883c3ac066dcc9a54f3a8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 26 Jun 2025 17:00:03 +1000 Subject: [PATCH 20/34] Run selftest directly --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9590e69c663..188a8542a63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,7 +131,10 @@ test-sources = [ "pyproject.toml", "selftest.py", ] -test-command = "python -m pytest -vv selftest.py checks/check_wheel.py Tests" +test-command = [ + "python -m selftest", + "python -m pytest checks/check_wheel.py Tests", +] # There's no numpy wheel for iOS (yet...) test-requires = [ ] From ab9dd5ed0558a6022748f9c4ea5299538349cf09 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 27 Jun 2025 15:41:01 +0800 Subject: [PATCH 21/34] Disable libavif on iOS (for now). --- .github/workflows/wheels-dependencies.sh | 5 ++++- checks/check_wheel.py | 10 +++++++--- pyproject.toml | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 3969bff3796..a4d45fc8e15 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -268,7 +268,10 @@ function build { build_tiff fi - build_libavif + if [[ -z "$IOS_SDK" ]]; then + # Short term workaround; buillib + build_libavif + fi build_libpng build_lcms2 build_openjpeg diff --git a/checks/check_wheel.py b/checks/check_wheel.py index 6102ff3c394..e29ade58025 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -18,14 +18,18 @@ def test_wheel_modules() -> None: assert tkinter except ImportError: expected_modules.remove("tkinter") - elif sys.platform == "ios": - # tkinter is not available on iOS - expected_modules.remove("tkinter") # libavif is not available on Windows for ARM64 architectures if platform.machine() == "ARM64": expected_modules.remove("avif") + elif sys.platform == "ios": + # tkinter is not available on iOS + expected_modules.remove("tkinter") + + # libavif is not available on iOS (for now) + expected_modules.remove("avif") + assert set(features.get_supported_modules()) == expected_modules diff --git a/pyproject.toml b/pyproject.toml index 188a8542a63..0e347b161fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ test-sources = [ ] test-command = [ "python -m selftest", - "python -m pytest checks/check_wheel.py Tests", + "python -m pytest -vv checks/check_wheel.py Tests", ] # There's no numpy wheel for iOS (yet...) From 4a069e0267c20969bbe34371da752b546b0b1423 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 27 Jun 2025 15:44:17 +0800 Subject: [PATCH 22/34] Save the *whole* file... --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index a4d45fc8e15..ca93f1cf51b 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -269,7 +269,7 @@ function build { fi if [[ -z "$IOS_SDK" ]]; then - # Short term workaround; buillib + # Short term workaround; don't build libavif on iOS build_libavif fi build_libpng From d7cab29d5d855b14b2ba355200ec55e86b0236d0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 27 Jun 2025 15:53:48 +0800 Subject: [PATCH 23/34] Add release note about iOS support. --- docs/releasenotes/11.3.0.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/releasenotes/11.3.0.rst b/docs/releasenotes/11.3.0.rst index 654a7e6b6f1..531926d6a62 100644 --- a/docs/releasenotes/11.3.0.rst +++ b/docs/releasenotes/11.3.0.rst @@ -80,11 +80,17 @@ Pillow only supports libavif 1.0.0 or later. In order to prevent errors when bui from source, if a user happens to have an earlier libavif on their system, Pillow will now ignore it. +iOS wheels +^^^^^^^^^^ + +Pillow now provides wheels that can be used on iOS ARM64 devices, and on the iOS +simulator on ARM64 and x86_64. + AVIF support in wheels ^^^^^^^^^^^^^^^^^^^^^^ Support for reading and writing AVIF images is now included in Pillow's wheels, except -for Windows ARM64. libaom is available as an encoder and dav1d as a decoder. +for Windows ARM64 and iOS. libaom is available as an encoder and dav1d as a decoder. Python 3.14 beta ^^^^^^^^^^^^^^^^ From 14b761aed5667d9f9562594d587268fc964b35cf Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 27 Jun 2025 20:47:44 +0800 Subject: [PATCH 24/34] Tweaks to test flags and module exclusions. --- checks/check_wheel.py | 4 +--- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/checks/check_wheel.py b/checks/check_wheel.py index e29ade58025..c89d32ed7aa 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -25,10 +25,8 @@ def test_wheel_modules() -> None: elif sys.platform == "ios": # tkinter is not available on iOS - expected_modules.remove("tkinter") - # libavif is not available on iOS (for now) - expected_modules.remove("avif") + expected_modules -= {"tkinter", "avif"} assert set(features.get_supported_modules()) == expected_modules diff --git a/pyproject.toml b/pyproject.toml index 0e347b161fd..752d49327fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ test-sources = [ ] test-command = [ "python -m selftest", - "python -m pytest -vv checks/check_wheel.py Tests", + "python -m pytest -vv -x -W always checks/check_wheel.py Tests", ] # There's no numpy wheel for iOS (yet...) From ff185091c57834646fc9e3cebb423f38e4bd8d25 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 27 Jun 2025 23:29:59 +1000 Subject: [PATCH 25/34] Moved new iOS wheels to be next to new 3.14 wheels --- docs/releasenotes/11.3.0.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/releasenotes/11.3.0.rst b/docs/releasenotes/11.3.0.rst index 531926d6a62..7949ef0a6c3 100644 --- a/docs/releasenotes/11.3.0.rst +++ b/docs/releasenotes/11.3.0.rst @@ -80,18 +80,18 @@ Pillow only supports libavif 1.0.0 or later. In order to prevent errors when bui from source, if a user happens to have an earlier libavif on their system, Pillow will now ignore it. -iOS wheels -^^^^^^^^^^ - -Pillow now provides wheels that can be used on iOS ARM64 devices, and on the iOS -simulator on ARM64 and x86_64. - AVIF support in wheels ^^^^^^^^^^^^^^^^^^^^^^ Support for reading and writing AVIF images is now included in Pillow's wheels, except for Windows ARM64 and iOS. libaom is available as an encoder and dav1d as a decoder. +iOS +^^^ + +Pillow now provides wheels that can be used on iOS ARM64 devices, and on the iOS +simulator on ARM64 and x86_64. + Python 3.14 beta ^^^^^^^^^^^^^^^^ From 8d8a2c0e5aebc943dd5c48f8015e63cfe82b6830 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 27 Jun 2025 23:28:45 +1000 Subject: [PATCH 26/34] Only Python 3.13 iOS wheels are available --- docs/releasenotes/11.3.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/11.3.0.rst b/docs/releasenotes/11.3.0.rst index 7949ef0a6c3..279aa4ef5bb 100644 --- a/docs/releasenotes/11.3.0.rst +++ b/docs/releasenotes/11.3.0.rst @@ -90,7 +90,7 @@ iOS ^^^ Pillow now provides wheels that can be used on iOS ARM64 devices, and on the iOS -simulator on ARM64 and x86_64. +simulator on ARM64 and x86_64. Currently, only Python 3.13 wheels are available. Python 3.14 beta ^^^^^^^^^^^^^^^^ From a4de9dbbfa381a24ea56ffc71bc8c478c0990b94 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 27 Jun 2025 23:39:50 +1000 Subject: [PATCH 27/34] Use [[ ]] --- .github/workflows/wheels-dependencies.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index ca93f1cf51b..b1bd63ce458 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -84,7 +84,7 @@ fi # Define custom utilities source wheels/multibuild/common_utils.sh source wheels/multibuild/library_builders.sh -if [ -z "$IS_MACOS" ]; then +if [[ -z "$IS_MACOS" ]]; then source wheels/multibuild/manylinux_utils.sh fi @@ -149,7 +149,7 @@ function build_zlib_ng { HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS - if [ -n "$IS_MACOS" ] && [ -z "$IOS_SDK" ]; then + if [[ -n "$IS_MACOS" ]] && [[ -z "$IOS_SDK" ]]; then # Ensure that on macOS, the library name is an absolute path, not an # @rpath, so that delocate picks up the right library (and doesn't need # DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an From 9e07493c7e6687e4781d4fc86bb7c0fbd4a66dc7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 27 Jun 2025 23:40:17 +1000 Subject: [PATCH 28/34] Removed extra space --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index b1bd63ce458..7c01deeaca5 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -164,7 +164,7 @@ function build_brotli { if [ -e brotli-stamp ]; then return; fi local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) (cd $out_dir \ - && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \ + && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \ && make install) touch brotli-stamp } From ee0ac6c59671c5161996b50b273ef412f08685be Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jun 2025 00:05:52 +1000 Subject: [PATCH 29/34] Removed pyproject.tml from test-sources --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 752d49327fb..5b7ce2d096a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,7 +128,6 @@ config-settings = "raqm=disable imagequant=disable platform-guessing=disable" test-sources = [ "checks", "Tests", - "pyproject.toml", "selftest.py", ] test-command = [ From 114ddd628107b91338b338c35667d3dc7ebfe886 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 27 Jun 2025 22:15:57 +0800 Subject: [PATCH 30/34] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/wheels-dependencies.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index ca93f1cf51b..702469760bd 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -149,7 +149,7 @@ function build_zlib_ng { HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS - if [ -n "$IS_MACOS" ] && [ -z "$IOS_SDK" ]; then + if [[ -n "$IS_MACOS" ]] && [[ -z "$IOS_SDK" ]]; then # Ensure that on macOS, the library name is an absolute path, not an # @rpath, so that delocate picks up the right library (and doesn't need # DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an @@ -164,7 +164,7 @@ function build_brotli { if [ -e brotli-stamp ]; then return; fi local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) (cd $out_dir \ - && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \ + && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \ && make install) touch brotli-stamp } From a8251c5a6a5963c645dcc19662c947b8607962bb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jun 2025 15:58:19 +1000 Subject: [PATCH 31/34] Do not install NumPy twice --- .github/workflows/wheels-test.ps1 | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/wheels-test.ps1 b/.github/workflows/wheels-test.ps1 index ee6ed6bec30..e6453d09118 100644 --- a/.github/workflows/wheels-test.ps1 +++ b/.github/workflows/wheels-test.ps1 @@ -15,9 +15,6 @@ if (Test-Path $venv\Scripts\pypy.exe) { $python = "python.exe" } & reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f -if ("$venv" -like "*\cibw-run-*-win_amd64\*") { - & $venv\Scripts\$python -m pip install numpy -} cd $pillow & $venv\Scripts\$python -VV if (!$?) { exit $LASTEXITCODE } From 3efb5883358eac954143312305ef4f638b08566c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Jun 2025 21:28:27 +1000 Subject: [PATCH 32/34] Do not install NumPy on Windows x86 --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 5b7ce2d096a..582d742b4d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -148,6 +148,10 @@ environment.PATH = "$(pwd)/build/deps/iphoneos/bin:$PATH" select = "*_iphonesimulator" environment.PATH = "$(pwd)/build/deps/iphonesimulator/bin:$PATH" +[[tool.cibuildwheel.overrides]] +select = "*-win32" +test-requires = [ ] + [tool.black] exclude = "wheels/multibuild" From 33d522d7bd1c8f40deb817a0814987e9ced45778 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 30 Jun 2025 19:32:15 +1000 Subject: [PATCH 33/34] Updated capitalisation --- .github/workflows/wheels-dependencies.sh | 6 +++--- patches/iOS/libwebp-1.5.0.tar.gz.patch | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 7c01deeaca5..b7a418f44be 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -51,14 +51,14 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then # GNU Autotools doesn't recognize the existence of arm64-apple-ios-simulator # as a valid host. However, the only difference between arm64-apple-ios and # arm64-apple-ios-simulator is the choice of sysroot, and that is - # coordinated by CC,CFLAGS etc. From the perspective of configure, the two + # coordinated by CC, CFLAGS etc. From the perspective of configure, the two # platforms are identical, so we can use arm64-apple-ios consistently. # This (mostly) avoids us needing to patch config.sub in dependency sources. HOST_CONFIGURE_FLAGS="--disable-shared --enable-static --host=$GNU_ARCH-apple-ios --build=$GNU_ARCH-apple-darwin" - # Cmake has native support for iOS. However, most of that support is based + # CMake has native support for iOS. However, most of that support is based # on using the Xcode builder, which isn't very helpful for most of Pillow's - # dependencies. Therefore, we lean on the OSX configurations, plus CC/CFLAGS + # dependencies. Therefore, we lean on the OSX configurations, plus CC, CFLAGS # etc. to ensure the right sysroot is selected. 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" diff --git a/patches/iOS/libwebp-1.5.0.tar.gz.patch b/patches/iOS/libwebp-1.5.0.tar.gz.patch index 2ea587554ef..fefb72b68d2 100644 --- a/patches/iOS/libwebp-1.5.0.tar.gz.patch +++ b/patches/iOS/libwebp-1.5.0.tar.gz.patch @@ -1,9 +1,9 @@ # libwebp example binaries require dependencies that aren't available for iOS builds. # There's also no easy way to invoke the build to *exclude* the example builds. -# Since we don't need the examples anyway, remove them from the makefile. +# Since we don't need the examples anyway, remove them from the Makefile. # -# As a point of reference, libwebp provides an XCframework build script that involves -# 7 separate invocations of make to avoid building the examples. Patching the makefile +# As a point of reference, libwebp provides an XCFramework build script that involves +# 7 separate invocations of make to avoid building the examples. Patching the Makefile # to remove the examples is a simpler approach, and one that is more compatible with # the existing multibuild infrastructure. # From a823a0a8a6e6e1b8ea1088b610b510da4254d876 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 30 Jun 2025 20:10:52 +1000 Subject: [PATCH 34/34] Simplified setting PLAT on macOS --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index b7a418f44be..d761d93b62c 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -70,7 +70,7 @@ elif [[ "$(uname -s)" == "Darwin" ]]; then check_cibw_archs # Build macOS dependencies in `build/darwin` # Install them into `build/deps/darwin` - PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}" + PLAT=$CIBW_ARCHS WORKDIR=$(pwd)/build/darwin BUILD_PREFIX=$(pwd)/build/deps/darwin else