#!/bin/bash

# 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). 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."
        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.
    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

    # GNU tooling insists on using aarch64 rather than arm64
    if [[ $PLAT == "arm64" ]]; then
        GNU_ARCH=aarch64
    else
        GNU_ARCH=x86_64
    fi

    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=$IOS_HOST_TRIPLE-simulator
    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 -DENABLE_SHARED=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
    check_cibw_archs
    # Build macOS dependencies in `build/darwin`
    # Install them into `build/deps/darwin`
    PLAT=$CIBW_ARCHS
    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

# Define custom utilities
source wheels/multibuild/common_utils.sh
source wheels/multibuild/library_builders.sh
if [[ -z "$IS_MACOS" ]]; then
    source wheels/multibuild/manylinux_utils.sh
fi

ARCHIVE_SDIR=pillow-depends-main

# Package versions for fresh source builds.
FREETYPE_VERSION=2.14.3
HARFBUZZ_VERSION=13.2.1
LIBPNG_VERSION=1.6.56
JPEGTURBO_VERSION=3.1.4.1
OPENJPEG_VERSION=2.5.4
XZ_VERSION=5.8.3
ZSTD_VERSION=1.5.7
TIFF_VERSION=4.7.1
LCMS2_VERSION=2.18
ZLIB_NG_VERSION=2.3.3
LIBWEBP_VERSION=1.6.0
BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.2.0
LIBAVIF_VERSION=1.4.1

function build_pkg_config {
    if [ -e pkg-config-stamp ]; then return; fi
    # 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 --installnamedir=$BUILD_PREFIX/lib --zlib-compat

    HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
    touch zlib-stamp
}

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 -DCMAKE_MACOSX_BUNDLE=OFF $HOST_CMAKE_FLAGS . \
        && make -j4 install)
    touch brotli-stamp
}

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)
    (cd $out_dir \
        && 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
}

function build_libavif {
    if [ -e libavif-stamp ]; then return; fi

    python3 -m pip install meson ninja

    if ([[ "$PLAT" == "x86_64" ]] && [[ -z "$IOS_SDK" ]]) || [ -n "$SANITIZER" ]; then
        build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
    fi

    local build_type=MinSizeRel
    local build_shared=ON
    local lto=ON

    local libavif_cmake_flags

    if [[ -n "$IS_MACOS" ]]; then
        lto=OFF
        libavif_cmake_flags=(
            -DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
            -DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
            -DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \
        )
        if [[ -n "$IOS_SDK" ]]; then
            build_shared=OFF
        fi
    else
        if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "x86_64" ]]; then
            build_type=Release
        fi
        libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
    fi
    if [[ -n "$IOS_SDK" ]] && [[ "$PLAT" == "x86_64" ]]; then
        libavif_cmake_flags+=(-DAOM_TARGET_CPU=generic)
    else
        libavif_cmake_flags+=(
            -DAVIF_CODEC_AOM_DECODE=OFF \
            -DAVIF_CODEC_DAV1D=LOCAL
        )
    fi

    local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz)

    # CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject
    # of libavif) that disables support for encoding high bit depth images.
    (cd $out_dir \
        && cmake \
            -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \
            -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \
            -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \
            -DBUILD_SHARED_LIBS=$build_shared \
            -DAVIF_LIBSHARPYUV=LOCAL \
            -DAVIF_LIBYUV=LOCAL \
            -DAVIF_CODEC_AOM=LOCAL \
            -DCONFIG_AV1_HIGHBITDEPTH=0 \
            -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \
            -DCMAKE_C_VISIBILITY_PRESET=hidden \
            -DCMAKE_CXX_VISIBILITY_PRESET=hidden \
            -DCMAKE_BUILD_TYPE=$build_type \
            "${libavif_cmake_flags[@]}" \
            $HOST_CMAKE_FLAGS . )

    if [[ -n "$IOS_SDK" ]]; then
        # libavif's CMake configuration generates a meson cross file... but it
        # doesn't work for iOS cross-compilation. Copy in Pillow-generated
        # meson-cross config to replace the cmake-generated version.
        cp $WORKDIR/meson-cross.txt $out_dir/crossfile-apple.meson
    fi

    (cd $out_dir && make -j4 install)

    touch libavif-stamp
}

function build_zstd {
    if [ -e zstd-stamp ]; then return; fi
    local out_dir=$(fetch_unpack https://github.com/facebook/zstd/releases/download/v$ZSTD_VERSION/zstd-$ZSTD_VERSION.tar.gz)
    (cd $out_dir \
        && make -j4 install)
    touch zstd-stamp
}

function build {
    build_xz
    if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
        yum remove -y zlib-devel
    fi
    if [[ -n "$IS_MACOS" ]]; then
        CFLAGS="$CFLAGS -headerpad_max_install_names" build_zlib_ng
    else
        build_zlib_ng
    fi

    build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
    if [[ -n "$IS_MACOS" ]]; then
        build_simple xorgproto 2025.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
    fi
    build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib

    build_libjpeg_turbo
    if [[ -n "$IS_MACOS" ]]; then
        # Custom tiff build to include jpeg; by default, configure won't include
        # 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 \
            --with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \
            --disable-webp --disable-libdeflate --disable-zstd
    else
        build_zstd
        build_tiff
    fi

    build_libavif
    build_libpng
    build_lcms2
    build_openjpeg

    webp_cflags="-O3 -DNDEBUG"
    if [[ -n "$IS_MACOS" ]]; then
        webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names"
    fi
    webp_ldflags=""
    if [[ -n "$IOS_SDK" ]]; then
        webp_ldflags="$webp_ldflags -llzma -lz"
    fi
    CFLAGS="$CFLAGS $webp_cflags" LDFLAGS="$LDFLAGS $webp_ldflags" build_simple libwebp $LIBWEBP_VERSION \
        https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \
        --enable-libwebpmux --enable-libwebpdemux

    build_brotli

    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

    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.
mkdir -p $WORKDIR
pushd $WORKDIR > /dev/null

# Any stuff that you need to do before you start building the wheels
# Runs in the root directory of this repository.
if [[ ! -d $WORKDIR/pillow-depends-main ]]; then
  if [[ ! -f $PROJECTDIR/pillow-depends-main.zip ]]; then
    echo "Download pillow dependency sources..."
    curl -fSL -o $PROJECTDIR/pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
  fi
  echo "Unpacking pillow dependency sources..."
  untar $PROJECTDIR/pillow-depends-main.zip
fi

if [[ -n "$IS_MACOS" ]]; then
    # 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. 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, and that the default prefix used by CMake is
    # the build prefix
    python3 -m pip install 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

# A safety catch for iOS. iOS can't use dynamic libraries, but clang will prefer
# to link dynamic libraries to static libraries. The only way to reliably
# prevent this is to not have dynamic libraries available in the first place.
# The build process *shouldn't* generate any dylibs... but just in case, purge
# any dylibs that *have* been installed into the build prefix directory.
if [[ -n "$IOS_SDK" ]]; then
    find "$BUILD_PREFIX" -name "*.dylib" -exec rm -rf {} \;
fi

# Return to the project root to finish the build
popd > /dev/null

# Append licenses
for filename in wheels/dependency_licenses/*; do
  echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE
  cat $filename >> LICENSE
done
