Кросскомпиляция библиотек под iOS, делаем это правильно

Во время разработки большого проекта наступает такой момент, когда надо встроить в приложение библиотеку из мира open source с подходящей лицензией. Например, вам захотелось ускорить декодирование картинок, или понадобился sqlite3 с fts4, или нужны какие-то плюшки из libicu, которых нету в системной libicucore.Для этого библиотеку, которая понадобилась, нужно будет собрать для 5 архитектур: armv7, armv7s, arm64, i386, x86_64. С кросскомпиляцией есть много подводных камней, на которые не хотелось бы наткнуться, когда есть уже проверенные решения. В этом коротком посте я расскажу об автоматизации сборки библиотек на примере protobuf и sqlite3.Прежде чем что-то делать, нам надо определиться с тем, что же нам надо на выходе.

Автоматизация процесса сборки нескольких библиотек Удобство добавления новых библиотек Распространение решения внутри репозитория Сохранение заголовочных файлов для всех архитектур Исходя из этих требований получилось следующее решение. Makefile, который управляет скачиванием, распаковкой, патчингом и сборкой библиотек. Код его не большой, так что можно привести тут и обсудить. (Или скачать c github и запустить, а потом читать дальше.)Определяем путь к самому новому SDK в системе и в build_arches: запускаем этот же Makefile до arch: с заполнеными переменными ARCH и IOS_PLATFORM. После того как build_arches: отработает, запустится скрипт, который соберет для каждой из библиотек жирную fat версию.

XCODE_DEVELOPER = $(shell xcode-select --print-path) IOS_PLATFORM?= iPhoneOS

# Pick latest SDK in the directory IOS_PLATFORM_DEVELOPER = ${XCODE_DEVELOPER}/Platforms/${IOS_PLATFORM}.platform/Developer IOS_SDK = ${IOS_PLATFORM_DEVELOPER}/SDKs/$(shell ls ${IOS_PLATFORM_DEVELOPER}/SDKs | sort -r | head -n1)

all: build_arches mkdir -p lib

# Make fat libraries for all architectures for file in build/armv7/lib/*.a; \ do name=`basename $$file .a`; \ ${IOS_PLATFORM_DEVELOPER}/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 Когда make будет работать над зависимостями, указанными в цели arch:, переменные окружения будут проинициализированы для той архитектуры, которая собирается в настоящий момент. Обратите внимание, что мы заполнили PREFIX и make install библиотек установит результат сборки в папки ./build/armv7, ./build/armv7s и т.д. Цель arch: указывает на цели, от которых она зависит. В нашем случае это библиотеки, которые мы собираем. При добавлении новых библиотек — их цели надо будет добавить в зависимости arch:, иначе они не соберутся.

PREFIX = ${CURDIR}/build/${ARCH} LIBDIR = ${PREFIX}/lib INCLUDEDIR = ${PREFIX}/include

CXX = ${XCODE_DEVELOPER}/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ CC = ${XCODE_DEVELOPER}/Toolchains/XcodeDefault.xctoolchain/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 В последней части осталось самое простое. Цели сборки библиотек, которые зависят от целей скачивания исходников. Именно тут можно указать кастомные ключи для ./configure или добавить поддержку arm64 в 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

clean: rm -rf build lib sqlite3 protobuf На выходе: в папке /lib лежат fat версии библиотек, а в build/{$ARCH}/include заголовочные файлы, которые могут пригодиться в работе.Заголовочные файлы для каждой архитектуры раздельно нужны не всегда, но встречаются библиотеки, которые на этапе ./configure в явном виде сохраняют размеры системных типов в заголовочный файл, например в config.h. Когда мы используем такой файл для arm64 и armv7 одновременно, есть риск, что что-то пойдет не так на каком-то этапе работы. И именно чтобы не гадать поломается что-то в логике работы библиотеки или нет и не включать в проект дополнительное тестирование библиотеки на всех архитектурах в поисках проблем совместимости, я для всех fat библиотек использую раздельные версии заголовочных файлов. Сделать это просто, в Header Search Path нужно добавить »$(SRCROOT)/…/…/libs/build/$(arch)/include». Где /…/…/libs/build/ путь к папке build относительно файла xcodeproj.

Этот способ сборки я подсмотрел в github репозитории сборки растрового рендера mapnik, там же можно посмотреть более сложный вариант Makefile, когда одна библиотека зависит от нескольких других.

Файлы этого поста можно скачать с github и полюбоваться на бегущие строчки кросскомпиляции. Достаточно набрать make.

© Habrahabr.ru