2

I am interested in building GDAL for iOS. I have managed to build successfully for macOS by building from sources with:

./configure --with-python=python3 --with-proj=/usr/local/Cellar/proj/7.0.1 --with-macosx-framework
make
make install

Like that, I get a libgdal.a static library that I can import successfully into a macOS app. Now, I would like to be able to do the same in an iOS project.

How can I achieve that?

0

1 Answer 1

2

I got this working for GDAL 2.2.2 and PROJ 4.9.3 several years ago. I'm now converting that project from Objective-C to Swift and I don't have time to figure out how to deal with the build issues I get for newer versions of GDAL and PROJ. but I now have the process working for a Swift project with these older versions.

I use a set of FOUR scripts to manage the GDAL build process, largely based on the old "pseudogreen" script that everybody seems to have used for building GDAL on iOS. I will post them here with just minimal explanation, but feel free to ask questions, and I'll answer them as best I can. Note that some of the items in scripts (particularly some of the environment variable values, such as CFLAGS values) are a bit beyond my understanding.

Note that in all of these scripts, my default prefix is "${HOME}/build", but other people may prefer to use "/usr/local" (or something else).

The first two scripts, one for PROJ and one for GDAL, will automatically download the sources from the web and unpack them if it cannot find what looks like the unpacked directory, already. In the case of GDAL, it also includes automatically fixing a bug in one of the config sources immediately after downloading and unpacking. Without this fix, it will not build for iOS (I had an email list conversation with the GDAL developers about this, but I don't know if it got fixed in later versions or not).

These first two scripts essentially pass the all the "configure" parameters required to a generic script that will build any configure/make package for iOS into a UNIVERSAL binary, including arm64 (for device) and x86_64 (for simulator on mac). It uses "lipo" to combine several architecture-dependent binaries into a single universal binary.

This Universal script runs a loop so that for each architecture, it calls the fourth script which builds for a single architecture.

PROJ SCRIPT - "build_proj_universal.sh":

#!/bin/bash

PREFIX="${HOME}/build"

proj_ver=4.9.3

# download gdal if necesary
if [ ! -e proj-$proj_ver ]
then
    echo proj4 missing, downloading
    curl -O http://download.osgeo.org/proj/proj-$proj_ver.tar.gz
    tar -xzf proj-$proj_ver.tar.gz
fi

startdir="$PWD"

cd proj-$proj_ver

universal_build_for_ios -p "$PREFIX" libproj.a \
| tee "$startdir/$0.log" 

GDAL SCRIPT - "build_gdal_universal.sh":


PREFIX="${HOME}/build"

gdal_ver=2.2.2

# download gdal if necesary
if [ ! -e gdal-$gdal_ver ]
then
    curl -O http://download.osgeo.org/gdal/$gdal_ver/gdal-$gdal_ver.tar.gz
    tar -xzf gdal-$gdal_ver.tar.gz
    sed 's/#undef HAVE_LONG_LONG/\/\* #undef HAVE_LONG_LONG \*\/\'$'\n#define HAVE_LONG_LONG 1/' gdal-$gdal_ver/port/cpl_config.h.in > gdal-$gdal_ver/port/cpl_config.h.in.NEW
    mv gdal-$gdal_ver/port/cpl_config.h.in gdal-$gdal_ver/port/cpl_config.h.in.ORIG
    mv gdal-$gdal_ver/port/cpl_config.h.in.NEW gdal-$gdal_ver/port/cpl_config.h.in
fi

startdir="$PWD"

cd gdal-$gdal_ver

universal_build_for_ios -p "$PREFIX" libgdal.a \
    --with-hide-internal-symbols \
    --with-unix-stdio-64=no \
    --with-geos=no \
    --without-pg \
    --without-grass \
    --without-libgrass \
    --without-cfitsio \
    --without-pcraster \
    --without-netcdf \
    --without-ogdi \
    --without-fme \
    --without-hdf4 \
    --without-hdf5 \
    --without-jasper \
    --without-kakadu \
    --without-grib \
    --without-mysql \
    --without-ingres \
    --without-xerces \
    --without-odbc \
    --without-idb \
    --without-sde \
    --with-sse=no \
    --with-avx=no \
    --with-expat="${PREFIX}" \
    --with-static-proj4="${PREFIX}" \
    --with-sqlite3="${PREFIX}" \
| tee "$startdir/$0.log"

(Note that the exact options you use here will depend on your requirements. If you use the --with-expat option like I've done here, you'll also need to download and build libexpat similarly to how we do with proj. I need Expat to be able to read GPX and KML. GDAL can write these without Expat, but cannot read them without Expat.)

Script to Build Universal Binary for iOS - "universal_build_for_ios":


default_prefix="${HOME}/build"

while getopts ":p:" opt; do
        case $opt in
        p  ) PREFIX="$OPTARG" ;;
        esac
done

shift $(( $OPTIND - 1 ))

if [ $# -lt 1 ]; then
    echo "Usage:  $0 [-p prefix] <target_library> [configure-options]"
    exit 2
fi

library=$1
shift
configopts="${@:1}"
PREFIX="${PREFIX:-$default_prefix}"

libs=""

for i in arm64 x86_64; do
    buildcmd="build_for_ios $i ${@:3}"
        echo "\n$buildcmd\n"

    $buildcmd || exit -2
        libs="$libs $PREFIX/$i/lib/$library"
done

mkdir -p "$PREFIX/lib/$library"
lipocmd="lipo -create -output $PREFIX/lib/$library $libs"

echo "\n$lipocmd"
$lipocmd || exit -1

echo "\nUNIVERSAL LIBRARY BUILT AT:\n\n$PREFIX/lib/$library\n"

Script to Build Single Architecture for iOS (device or simulator) - "build_for_ios":

#!/bin/bash

#default_iphoneos_version=14.4
default_iphoneos_version=`xcrun --sdk iphoneos --show-sdk-platform-version`
default_prefix="${HOME}/build"


export IPHONEOS_DEPLOYMENT_TARGET="${IPHONEOS_DEPLOYMENT_TARGET:-$default_iphoneos_version}"


usage ()
{
    cat >&2 << EOF
Usage: ${0##*/} [-ht] [-p prefix] target [configure_args]
    -h  Print help message
    -p  Installation prefix (default: \$HOME)
    -t  Use 16-bit Thumb instruction set (instead of 32-bit ARM)

The target must be "device" or "simulator".  Any additional arguments
are passed to configure.

The following environment variables affect the build process:

    IPHONEOS_DEPLOYMENT_TARGET  (default: $default_iphoneos_version)

EOF
}


while getopts ":hp:t" opt; do
    case $opt in
    h  ) usage ; exit 0 ;;
    p  ) prefix="$OPTARG" ;;
    t  ) thumb_opt=thumb ;;
    \? ) usage ; exit 2 ;;
    esac
done
shift $(( $OPTIND - 1 ))

if (( $# < 1 )); then
    usage
    exit 2
fi

target=$1
shift
arch=$target
host="${arch}-apple-darwin"

case $target in

    arm64 )
    platform=iPhoneOS
    extra_cflags="-m${thumb_opt:-no-thumb}"
    host="arm-apple-darwin"
    ;;

    x86_64 )
    platform=iPhoneSimulator
    extra_cflags="-D__IPHONE_OS_VERSION_MIN_REQUIRED=${IPHONEOS_DEPLOYMENT_TARGET%%.*}0000"
    ;;

    * )
    usage
    exit 2

esac


xcode="/Applications/Xcode.app"
platform_dir="${xcode}/Contents/Developer/Platforms/${platform}.platform/Developer"
platform_sdk_dir="${platform_dir}/SDKs/${platform}${IPHONEOS_DEPLOYMENT_TARGET}.sdk"
prefix="${prefix:-$default_prefix}"
platform_bin_dir="${platform_dir}/usr/bin"

export CC="${xcode}/Contents/Developer/usr/bin/gcc"
export CFLAGS="-arch ${arch} -pipe -Os -gdwarf-2 -isysroot ${platform_sdk_dir} -Wno-implicit-function-declaration -fembed-bitcode ${extra_cflags}"
export LDFLAGS="${LDFLAGS} -arch ${arch} -isysroot ${platform_sdk_dir}"
export CXX="${xcode}/Contents/Developer/usr/bin/g++"
export CXXFLAGS="${CFLAGS}"

libdir="${prefix}/${arch}/lib"
mkdir -p "$libdir"

echo
echo platform_sdk_dir:  $platform_sdk_dir
echo
echo prefix:  $prefix
echo
echo

make distclean

echo
echo

./configure \
    --prefix="${prefix}" \
    --libdir="$libdir" \
    --host="${host}" \
    --disable-shared \
    --enable-static \
    "$@" || exit

echo
echo

make install || exit

cat >&2 << EOF

    Build succeeded.
    Files were installed in:
    $prefix

EOF

Incorporate into XCode Project:

Build PROJ and then GDAL using the build_proj_universal.sh and build_gdal_universal.sh scripts (above).

Then in XCode (assuming you built to ${HOME}/build/):

  • For the target's "Build Settings"
    • "Header Search Paths" add: $(HOME)/build/include
    • "Library Search Paths" add: $(HOME)/build/lib
  • For the target's "Build Phases" -> "Link Binary With Libraries" add:
    • libc++.tdb
    • libz.tdb
    • libiconv.tdb
    • libsqlite3.tdb
    • libproj.a
    • libgdal.a

(these last two will have to be manually selected from the 'lib' directory to which you build them, the rest should be a quick search and select using the search field)

  • Then in your code you will need to import the following headers (this may vary, depending on which parts of GDAL you use):
    • gdal.h
    • ogr_api.h
    • ogr_srs_api.h

In Swift, I did this using a "module.map" file as follows:

module CGDAL [system] {
    header "/Users/nsands/build/include/gdal.h"
    header "/Users/nsands/build/include/ogr_api.h"
    header "/Users/nsands/build/include/ogr_srs_api.h"
    export *
}

(If you use this module.map method, then you need to also set the target's "Build Settings" -> "Swift Compiler - Search Paths" -> "Import Paths" to include the path to the directory that contains your "module.map" file.)

Now in my swift code, I can just: import CGDAL

and get access to all the GDAL functions that I need.

2
  • great , but I need support form armv7 too (I'm implementing gdal in an old project), how can I achieve this ?
    – atrebbi
    Commented Aug 11, 2020 at 14:45
  • I used to have armv7 in there, but removed it when I no longer required it. Add it to the list in this line: for i in arm64 x86_64; do so that it becomes: for i in armv7 arm64 x86_64; do. Then you'll also need to add a case stanza for it under the line case $target in - identical to the arm64 stanza. Commented Aug 12, 2020 at 0:03

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.