4

I've been building GDAL 2.x for iOS for years but I will have a compelling need to upgrade to 3.6 when it is released (hopefully later this year). Therefore I now need to be able to build GDAL 3.x for iOS.

For GDAL 2.x, I have been using the build method documented at Building GDAL for iOS . That method will not work for 3.6 when it is released because 3.6 will require the 'cmake' build method and will no longer support autoconf.

I'm entirely unfamiliar with 'cmake' and so I'm learning as I go along, but I've not managed to get very far yet.

I will persist, and will post my method here if I do eventually get it working.

In the meantime, has anybody else managed to build GDAL 3.x for iOS using cmake?

2 Answers 2

6

After much research, trials and a few rounds of correspondence with GDAL developers, I have finally got GDAL 3.x to build for iOS using 'cmake'. The full procedure is documented below, including building for iPhone simulator (x86_64 and arm64) as well as iPhone device (arm64) and even macOS (Mac Catalyst, x86_64 and arm64). I've also included the commands required for building FAT binary libraries for device, simulator and catalyst and for building an XC-Framework (a single framework that can be used on all three platforms and on both architectures).

I occasionally refine the procedure documented here as I tweak my process.

This build may not suit all people or all purposes. It does not include all possible dependencies and therefore some optional features of GDAL will not be available. But it does include all that I need, and probably all that would be required for many other iOS uses.

PROCEDURE:

Depending on the platform (eg, arm64 device or x86_64 simulator), select only ONE of the platform-dependent set of environment variables for each run through the procedure. If desired, run the entire procedure again for the other platform.

Optionally, combine the outputs of both into a FAT library using the step at the end. There are also instructions to build Frameworks and an XC-Framework at the end.

Install Homebrew package manager:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Install cmake using Homebrew:

brew install cmakecmake --build . --target install

Install ios.toolchain.cmake:

Download from: https://github.com/leetal/ios-cmake

Install at, eg: $HOME/dev/3rdParty/ios-cmake-master/ios.toolchain.cmake

Environment Variables:

Notes:

  • Select from ONE of the platform options below for each build (then repeat the entire build for any of the other platform options, setting the platform specific environment variables at the beginning of each build)
  • Including the platform-specific $OS as part of the build directory name (ie, build$OS for each library, below) means that the procedure can be run simultaneously for multiple platforms, if desired. Eg, arm64 iOS device and x86_64 iOS simulator.
# for ALL platforms
export CMTOOLCHAIN=$HOME/dev/3rdParty/ios-cmake-master/ios.toolchain.cmake

# for iOS arm64 device
export PREFIX=$HOME/build/iphoneos_arm64
export SDKPATH=$(xcrun --sdk iphoneos --show-sdk-path)
export OS=OS64

# for iOS x86_64 simulator
export PREFIX=$HOME/build/iphonesimulator_x86_64
export SDKPATH=$(xcrun --sdk iphonesimulator --show-sdk-path)
export OS=SIMULATOR64

# for iOS arm64 simulator
export PREFIX=$HOME/build/iphonesimulator_arm64
export SDKPATH=$(xcrun --sdk iphonesimulator --show-sdk-path)
export OS=SIMULATORARM64

# for Mac Catalyst x86_64
export PREFIX=$HOME/build/macos_x86_64
export SDKPATH=$(xcrun --sdk macosx --show-sdk-path)
export OS=MAC_CATALYST

# for Mac Catalyst arm64
export PREFIX=$HOME/build/macos_arm64
export SDKPATH=$(xcrun --sdk macosx --show-sdk-path)
export OS=MAC_CATALYST_ARM64

NB: Other platform (OS) options are also available, including MAC for macOS x86_64 or MAC_ARM64 for macOS arm64 (non-catalyst).

Build SQLite:

Apple-bundled SQLite is missing RTREE extension, which is required to build GDAL.

Download CMAKE-compatible SQLite amalgamation from: https://github.com/azadkuh/sqlite-amalgamation

cd sqlite-amalgamation-master
rm -r build_$OS; mkdir build_$OS; cd build_$OS
cmake -DCMAKE_TOOLCHAIN_FILE=$CMTOOLCHAIN \
    -DPLATFORM=$OS \
    -DENABLE_BITCODE=OFF \
    -DCMAKE_INSTALL_PREFIX=$PREFIX \
    -DBUILD_SHARED_LIBS=OFF \
    -DSQLITE_ENABLE_RTREE=ON \
    -DSQLITE_ENABLE_COLUMN_METADATA=ON \
    -DSQLITE_OMIT_DECLTYPE=OFF \
    ..
cmake --build .
cmake --build . --target install

Build Proj:

cd proj-{VERSION}
rm -r build_$OS; mkdir build_$OS; cd build_$OS
cmake -DCMAKE_TOOLCHAIN_FILE=$CMTOOLCHAIN \
    -DPLATFORM=$OS \
    -DENABLE_BITCODE=OFF \
    -DBUILD_SHARED_LIBS=OFF \
    -DCMAKE_INSTALL_PREFIX=$PREFIX \
    -DENABLE_TIFF=OFF -DENABLE_CURL=OFF \
    -DBUILD_PROJSYNC=OFF \
    -DSQLITE3_INCLUDE_DIR=$PREFIX/include \
    -DSQLITE3_LIBRARY=$PREFIX/lib/libsqlite3.a \
    ..
cmake --build .
cmake --build . --target install

Build GDAL:

Notes:

  • GDAL 3.5.2 or greater is required for this process (earlier versions required a patch to the CMAKE configuration which has now been removed from this answer, since the release of 3.5.2)
  • Specifying the path to PROJ should not be required(?) because cmake automatically adds CMAKE_INSTALL_PREFIX to CMAKE_SYSTEM_PREFIX_PATH however, in practice it fails at 98% without -DPROJ_ROOT=$PREFIX because it attempts to use /Library/proj.framework
  • cmake finds and uses iconv from libc which is missing some symbols required by GDAL (causes errors when building gdal_fuzzer), so it is necessary to explicitly force it to use libiconv instead (from the iOS SDK)
cd gdal-{VERSION}
rm -r build_$OS; mkdir build_$OS; cd build_$OS
cmake -DCMAKE_TOOLCHAIN_FILE=$CMTOOLCHAIN \
    -DPLATFORM=$OS \
    -DENABLE_BITCODE=OFF \
    -DCMAKE_INSTALL_PREFIX=$PREFIX \
    -DBUILD_SHARED_LIBS=OFF \
    -DBUILD_APPS=OFF \
    -DBUILD_PYTHON_BINDINGS=OFF \
    -DPROJ_ROOT=$PREFIX \
    -DSQLITE3_INCLUDE_DIR=$PREFIX/include \
    -DSQLITE3_LIBRARY=$PREFIX/lib/libsqlite3.a \
    -DIconv_INCLUDE_DIR=$SDKPATH/usr \
    -DIconv_LIBRARY=$SDKPATH/usr/lib/libiconv.tbd \
    -DCMAKE_BUILD_TYPE=Release \
    ..
cmake --build .
cmake --build . --target install

Additional Steps After Successful Build of GDAL

If you wish to use these libraries as part of an iOS project and distribute it on the App Store, then the following additional steps may also be useful.

Generate FAT Binary Libraries (arm64, x86_64) for each platform (device, simulator, mac-catalyst):

The exact library paths to use will depend on what were configured in the platform-specific environment variables, above. Optionally, exclude any architectures and/or platforms that are not required (eg, in most cases only ONE of the simulator architectures is likely to be used, and in some cases either iPhoneOS or Mac Catalyst may not be required).

Note that the cmake 'build' directory names used for each library, above, deliberately include a platform-specific component (using the $OS environment variable) so that they can be built simultaneously for multiple platforms, if desired.

The three platform directories created (if necessary) in the first line below are explicitly named to match XCode’s '$(PLATFORM_NAME)’ environment variable values so that it can be easily included in XCode’s ‘Library Search Paths’ build setting.

mkdir -p $HOME/build/iphoneos/lib $HOME/build/iphonesimulator/lib $HOME/build/macos/lib

Set environment variable to each of these settings in turn, running the following set of ‘lipo’ comands, after each (ie, for each library)

export LIBNAME="libsqlite3"
export LIBNAME="libproj"
export LIBNAME="libgdal"

Generate multi-architecture binaries (run for each of the ‘export’ commands above, in turn.

lipo -create -output $HOME/build/iphoneos/lib/${LIBNAME}.a $HOME/build/iphoneos_arm64/lib/${LIBNAME}.a
lipo -create -output $HOME/build/iphonesimulator/lib/${LIBNAME}.a $HOME/build/iphonesimulator_arm64/lib/${LIBNAME}.a $HOME/build/iphonesimulator_x86_64/lib/${LIBNAME}.a
lipo -create -output $HOME/build/macos/lib/${LIBNAME}.a $HOME/build/macos_arm64/lib/${LIBNAME}.a $HOME/build/macos_x86_64/lib/${LIBNAME}.a

Repeat for each library, using the applicable ‘export’ command above.

Generate Single-Platform, Multi-Architecture Framework for each Library:

Separate out the various header files into library-specific directories

mkdir -p $HOME/build/include_sqlite3 $HOME/build/include_proj $HOME/build/include_gdal
cp $HOME/build/iphoneos_arm64/include/* $HOME/build/include_gdal/
mv $HOME/build/include_gdal/sqlite3* $HOME/build/include_sqlite3/
mv $HOME/build/include_gdal/proj* $HOME/build/include_proj/

Set environment variable to each of these settings in turn, running the following set commands (to create the framework), after each (ie, for each library/platform combination)

# SQLite3 device
export FRAMENAME=sqlite3
export LIBNAME=libsqlite3
export PLATFORM=iphoneos

# SQLite3 simulator
export FRAMENAME=sqlite3
export LIBNAME=libsqlite3
export PLATFORM=iphonesimulator

# SQLite3 macOS (Mac Catalyst)
export FRAMENAME=sqlite3
export LIBNAME=libsqlite3
export PLATFORM=macos

# Proj device
export FRAMENAME=proj
export LIBNAME=libproj
export PLATFORM=iphoneos

# Proj simulator
export FRAMENAME=proj
export LIBNAME=libproj
export PLATFORM=iphonesimulator

# Proj macOS (Mac Catalyst)
export FRAMENAME=proj
export LIBNAME=libproj
export PLATFORM=macos

# GDAL device
export FRAMENAME=gdal
export LIBNAME=libgdal
export PLATFORM=iphoneos

# GDAL simulator
export FRAMENAME=gdal
export LIBNAME=libgdal
export PLATFORM=iphonesimulator

# GDAL macOS (Mac Catalyst)
export FRAMENAME=gdal
export LIBNAME=libgdal
export PLATFORM=macos

Generate Framework (repeat for each set of ‘export’ commands, above):

export FRAMEPATH="$HOME/build/${PLATFORM}/${FRAMENAME}.framework"

mkdir -p "${FRAMEPATH}/Versions/A/Headers"
mkdir -p "${FRAMEPATH}/Versions/A/Headers"
ln -sfh "A" "${FRAMEPATH}/Versions/Current"
ln -shf "Versions/Current/Headers" "${FRAMEPATH}/Headers"
ln -sfh "Versions/Current/${FRAMENAME}" "${FRAMEPATH}/${FRAMENAME}"
cp -a "${HOME}/build/include_${FRAMENAME}/" "${FRAMEPATH}/Versions/A/Headers"
cp -a "${HOME}/build/${PLATFORM}/lib/${LIBNAME}.a" "${FRAMEPATH}/Versions/A/${FRAMENAME}"

Generate Cross-Platform, Multi-Architecture XCFramework for Each Library:

mkdir "${HOME}/build/xcf"

Set environment variable to each of these settings in turn, running the following ‘xcodebuild’ comand, after each (ie, for each library)

export FRAMENAME=sqlite3
export FRAMENAME=proj
export FRAMENAME=gdal

Generate XCFramework (repeat for each ‘export’ command, above):

xcodebuild -create-xcframework \
-framework "${HOME}/build/iphoneos/${FRAMENAME}.framework" \
-framework "${HOME}/build/iphonesimulator/${FRAMENAME}.framework" \
-framework "${HOME}/build/macos/${FRAMENAME}.framework" \
-output "${HOME}/build/xcf/${FRAMENAME}.xcframework"

To Use in an XCode Project:

  • Add the XC-Frameworks to the project (link, but do not embed)
  • Copy $HOME/build/lib/iphoneos_arm64/share/proj into project
  • In the app’s code set PROJ_LIB environment variable so that proj can find proj.db (and other contents it may require?) setenv("PROJ_LIB", projLibBundleURL.cString(using: .ascii), 1)
  • In Build Settings, add the value, “—deep”, to ‘Other Code Signing Flags’ (if somebody can figure out how to correctly code-sign these XC-Frameworks in such a way that XCode will accept it, I'd like to know)
  • Create module.map file to generate Swift wrappers of C functions (details of this are beyond the scope of this post)
0
0

GDAL now creates dynamic libraries with binding: You can convert them to framework by following above process: you just have to create info plist file also: Here is the minimal plist file for iOS Device:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>DTPlatformVersion</key>
    <string>17.2</string>
    <key>DTCompiler</key>
    <string>com.apple.compilers.llvm.clang.1_0</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleIdentifier</key>
    <string>com.microsoft.gdalwrap</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>DTXcodeBuild</key>
    <string>15C500b</string>
    <key>CFBundleExecutable</key>
    <string>gdal_wrap</string>
    <key>DTPlatformBuild</key>
    <string>21C52</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>MinimumOSVersion</key>
    <string>15.0</string>
    <key>CFBundleSupportedPlatforms</key>
    <array>
        <string>iPhoneOS</string>
    </array>
    <key>CFBundlePackageType</key>
    <string>FMWK</string>
    <key>BuildMachineOSBuild</key>
    <string>23D60</string>
    <key>DTPlatformName</key>
    <string>iPhoneOS</string>
    <key>DTSDKBuild</key>
    <string>21C52</string>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>UIDeviceFamily</key>
    <array>
        <integer>1</integer>
        <integer>2</integer>
    </array>
    <key>CFBundleName</key>
    <string>gdal_wrap</string>
</dict>
</plist>

You don't need any header or other files:

export CUR_DIR=$(pwd)
export LIB_NAME="gdal"
export SOURCE_INFO_PLIST="${CUR_DIR}/info.plist"
export DYLIB="libgdal.dylib"

# set CUR_DIR and LIB_NAME
FW_PATH="${CUR_DIR}/$LIB_NAME.framework"
INFO_PLIST="$FW_PATH/Info.plist"
OUT_DYLIB="$FW_PATH/$LIB_NAME"

# set the DYLIB, SOURCE_INFO_PLIST and HEADER_PATH for the library
mkdir -p "$FW_PATH"
cp "${SOURCE_INFO_PLIST}" "$INFO_PLIST"
lipo "${CUR_DIR}/lib/${DYLIB}" -output "$OUT_DYLIB" -create

install_name_tool -id @rpath/$LIB_NAME.framework/$LIB_NAME "$OUT_DYLIB"
chmod +x ${OUT_DYLIB}

Do this for all of your dylibs and then you have the frameworks. I have some dependent libraries of GDAL also, I followed same approach for those too but one more thing I had to do is changing the dependent path:

install_name_tool -change "old/path/of/libgdal.dylib" "@rpath/gdal.framework/gdal" ${OUT_DYLIB}

If you want for multiple architectures you can create xcFramework afterwards with simple xcode command:

export CUR_DIR=$(pwd)
export FRAMENAME=gdal

xcodebuild -create-xcframework \
-framework "${CUR_DIR}/arm64/${FRAMENAME}.framework" \
-framework "${CUR_DIR}/x86_64/${FRAMENAME}.framework" \
-output "${CUR_DIR}/${FRAMENAME}.xcframework"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.