Cross-compiling libraries for iOS, doing it right
During the development of a large project, there comes a moment when you need to embed a library from the open source world with a suitable license into the application. For example, you wanted to speed up the decoding of pictures, or needed sqlite3 with fts4, or some kind of buns from libicu that are not in the system libicucore are needed.
For this, the library that was needed will need to be assembled for 5 architectures: armv7, armv7s, arm64, i386, x86_64. With cross-compilation, there are many pitfalls that I would not want to stumble upon when there are already proven solutions. In this short post I will talk about library assembly automation using protobuf and sqlite3 as an example.
Before doing anything, we need to decide what we need at the exit.
Based on these requirements, the following solution was obtained. A makefile that controls the downloading, unpacking, patching, and building of libraries. Its code is not large, so you can bring it here and discuss. (Or download from github and run, and then read on. )
We determine the path to the newest SDK in the system andfatty fat version.
When make will work on the dependencies specified in the target
A goal
The last part is the simplest. Library assembly goals, which depend on the source download goals. This is where you can specify custom keys for ./configure or add support for arm64 in protobuf.
On the output: in the / lib folder there are fat versions of libraries, and in build / {$ ARCH} / include header files that may be useful in work.
Header files for each architecture are not always needed separately, but there are libraries that, at the ./configure stage, explicitly store the sizes of system types in a header file, for example, in config.h. When we use such a file for arm64 and armv7 at the same time, there is a risk that something will go wrong at some stage of work. And in order not to guess, something in the logic of the library will break or not and not include additional testing of the library on all architectures in the project in search of compatibility problems, I use separate versions of header files for all fat libraries. Make it simple, in Header Search Path you need to add
I spotted this build method in github repositories of the mapnik raster render assembly repositories , where you can also see a more complex version of the Makefile when one library depends on several others.
Files of this post can be downloaded from github and admire the running lines of cross-compilation. Just dial
For this, the library that was needed will need to be assembled for 5 architectures: armv7, armv7s, arm64, i386, x86_64. With cross-compilation, there are many pitfalls that I would not want to stumble upon when there are already proven solutions. In this short post I will talk about library assembly automation using protobuf and sqlite3 as an example.
Before doing anything, we need to decide what we need at the exit.
- Automation of the assembly process of several libraries
- Ease of adding new libraries
- Distributing the solution inside the repository
- Saving header files for all architectures
Based on these requirements, the following solution was obtained. A makefile that controls the downloading, unpacking, patching, and building of libraries. Its code is not large, so you can bring it here and discuss. (Or download from github and run, and then read on. )
We determine the path to the newest SDK in the system and
build_arches:
run the same Makefile before arch:
with filled variables ARCH and IOS_PLATFORM. Once build_arches:
is done, run a script that will gather for each library XCODE_TOOLCHAIN = $(shell xcode-select --print-path)/Toolchains/XcodeDefault.xctoolchain
IOS_PLATFORM ?= iphoneos
# Pick latest SDK in the directory#IOS_PLATFORM_DEVELOPER = $(shell xcrun -sdk ${IOS_PLATFORM} -show-sdk-platform-path)
IOS_SDK = $(shell xcrun -sdk ${IOS_PLATFORM} -show-sdk-path)
all: build_arches
mkdir -p lib
# Make fat libraries for all architectures
for file in build/armv7/lib/*.a; \
do name=`basename $$file .a`; \
${XCODE_TOOLCHAIN}/usr/bin/lipo -create \
-arch armv7 build/armv7/lib/$$name.a \
-arch armv7s build/armv7s/lib/$$name.a \
-arch arm64 build/arm64/lib/$$name.a \
-arch i386 build/i386/lib/$$name.a \
-arch x86_64 build/x86_64/lib/$$name.a \
-output lib/$$name.a \
; \
done;
echo "Making fat libs"# Build separate architectures
build_arches:
${MAKE} arch ARCH=armv7 IOS_PLATFORM=iphoneos
${MAKE} arch ARCH=armv7s IOS_PLATFORM=iphoneos
${MAKE} arch ARCH=arm64 IOS_PLATFORM=iphoneos
${MAKE} arch ARCH=i386 IOS_PLATFORM=iphonesimulator
${MAKE} arch ARCH=x86_64 IOS_PLATFORM=iphonesimulator
When make will work on the dependencies specified in the target
arch:
, environment variables will be initialized for the architecture that is currently being built. Please note that we filled PREFIX and make install libraries will install the build result in the folders ./build/armv7, ./build/armv7s, etc. A goal
arch:
indicates the goals on which it depends. In our case, these are the libraries that we collect. When adding new libraries - their goals will need to be added depending arch:
, otherwise they will not be assembled.PREFIX = ${CURDIR}/build/${ARCH}
LIBDIR = ${PREFIX}/lib
INCLUDEDIR = ${PREFIX}/include
CXX = ${XCODE_TOOLCHAIN}/usr/bin/clang++
CC = ${XCODE_TOOLCHAIN}/usr/bin/clang
CFLAGS = -isysroot ${IOS_SDK} -I${IOS_SDK}/usr/include -arch ${ARCH} -miphoneos-version-min=5.0
CXXFLAGS = -stdlib=libc++ -isysroot ${IOS_SDK} -I${IOS_SDK}/usr/include -arch ${ARCH} -miphoneos-version-min=5.0
LDFLAGS = -stdlib=libc++ -isysroot ${IOS_SDK} -L${LIBDIR} -L${IOS_SDK}/usr/lib -arch ${ARCH} -miphoneos-version-min=5.0
LIBTOOLFLAGS = -arch_only ${ARCH}
arch: ${LIBDIR}/libsqlite3.a ${LIBDIR}/libprotobuf.a
The last part is the simplest. Library assembly goals, which depend on the source download goals. This is where you can specify custom keys for ./configure or add support for arm64 in protobuf.
${LIBDIR}/libsqlite3.a: ${CURDIR}/sqlite3
cd sqlite3 && env CXX=${CXX} CC=${CC} CFLAGS="${CFLAGS}" \
CXXFLAGS="${CXXFLAGS} -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS4_UNICODE61" \
LDFLAGS="${LDFLAGS}" ./configure --host=arm-apple-darwin --disable-shared --prefix=${PREFIX} && ${MAKE} clean install${LIBDIR}/libprotobuf.a: ${CURDIR}/protobuf
cd protobuf && env CXX=${CXX} CC=${CC} CFLAGS="${CFLAGS}" CXXFLAGS="${CXXFLAGS}" LDFLAGS="${LDFLAGS}" \
./configure --host=arm-apple-darwin --disable-shared --with-protoc=/usr/local/bin/protoc --prefix=${PREFIX} && ${MAKE} clean install${CURDIR}/sqlite3:
curl https://www.sqlite.org/2014/sqlite-autoconf-3080403.tar.gz > sqlite3.tar.gz
tar xzvf sqlite3.tar.gz
rm sqlite3.tar.gz
mv sqlite-autoconf-3080403 sqlite3
touch sqlite3
${CURDIR}/protobuf:
curl https://protobuf.googlecode.com/files/protobuf-2.5.0.tar.gz > protobuf.tar.gz
tar xzvf protobuf.tar.gz
rm protobuf.tar.gz
mv protobuf-2.5.0 protobuf
# add arm64 support https://code.google.com/p/protobuf/issues/detail?id=575
patch -p0 <protobuf_arm64.patch
touch protobuf
clean:
rm -rf build lib sqlite3 protobuf
On the output: in the / lib folder there are fat versions of libraries, and in build / {$ ARCH} / include header files that may be useful in work.
Header files for each architecture are not always needed separately, but there are libraries that, at the ./configure stage, explicitly store the sizes of system types in a header file, for example, in config.h. When we use such a file for arm64 and armv7 at the same time, there is a risk that something will go wrong at some stage of work. And in order not to guess, something in the logic of the library will break or not and not include additional testing of the library on all architectures in the project in search of compatibility problems, I use separate versions of header files for all fat libraries. Make it simple, in Header Search Path you need to add
"$(SRCROOT)/../../libs/build/$(arch)/include"
. Where is the /../../libs/build/
path to the build folder relative to the xcodeproj file. I spotted this build method in github repositories of the mapnik raster render assembly repositories , where you can also see a more complex version of the Makefile when one library depends on several others.
Files of this post can be downloaded from github and admire the running lines of cross-compilation. Just dial
make
.