Кросскомпиляция библиотек под 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_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
    

    Когда make будет работать над зависимостями, указанными в цели arch:, переменные окружения будут проинициализированы для той архитектуры, которая собирается в настоящий момент. Обратите внимание, что мы заполнили PREFIX и make install библиотек установит результат сборки в папки ./build/armv7, ./build/armv7s и т.д.

    Цель arch: указывает на цели, от которых она зависит. В нашем случае это библиотеки, которые мы собираем. При добавлении новых библиотек — их цели надо будет добавить в зависимости arch:, иначе они не соберутся.

    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
    

    В последней части осталось самое простое. Цели сборки библиотек, которые зависят от целей скачивания исходников. Именно тут можно указать кастомные ключи для ./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 <protobuf_arm64.patch
    	touch protobuf
    
    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.
    • +32
    • 9.9k
    • 4
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 4

      0
      Полезный пост, спасибо.
      Раз такое дело, у меня есть вопрос. Вам не приходилось собирать boost для iOS?
      Я сделал скрипт на основе других решений: github.com/mgrebenets/boost-xcode5-iosx.
      Тем не менее у людей возникают проблемы, когда им нужно собрать, например, locale библиотеку, которая имеет зависимость от icu или iconv.
      0
      Спасибо за статью.
      Вместо магических строк советую посмотреть в сторону `xcrun`:
      $ xcrun -sdk iphonesimulator7.1 -show-sdk-platform-path
      /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform
      $ xcrun -sdk iphonesimulator7.1 -show-sdk-path         
      /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk
      
        0
        Да, так удобней. Обновил Makefile.

      Only users with full accounts can post comments. Log in, please.