Сборка исходников Android. Инструмент Androgenizer.
В данной статье рассматривается только адаптация сборки ПО с исходным кодом для Unix/Linux на Android.
На своей работе часто приходится принимать решения по переносу некоторых библиотек/приложений из Linux в Android. В интернете много англоязычных статей, описывающих основные принципы переноса и адаптации. Поэтому я решил собрать весь материал, касающийся сборки ПО, воедино и поделиться своим опытом. Излагаемый материал подразумевает, что у читателей есть опыт сборки AOSP и знание синтаксиса файла Android.mk.
Зачем нужен Androigenizer?
У Android есть своя собственная система сборки наподобие GNU make. Тем не менее, большинство разработчиков не очень-то радуются, когда им приходится добавлять поддержку новой системы сборки в open source проекты (в большенстве случаев используется autotools), за которыми, к тому же, необходимо постоянно следить (а вдруг что-то поломается в другой версии). Androgenizer был создан, чтобы избежать подобных ситуаций.
Вместо того, чтобы добавлять в каждый модуль проекта файл Android.mk, Вы можете добавить только один файл Android.mk на самом верхнем уровне для автоматической конфигурации для пред-сборочной стадии. Выполнение всех шагов в autoconf/automake/configure, а затем вызов make в соответствующих каталогах, для создания в них файлов Android.mk.
Каждый Makefile.am модуля, который Вы хотите скомпилировать под Android, должен содержать небольшой фрагмент для генерации Android.mk, с использованием androgenizer.
Иногда содержимое Android.mk файла зависит от того, как будет собираться проект: с помощью NDK или как часть AOSP. Androgenizer это тоже учитывает.
Androgenizer обнаруживает систему сборки на основе переменной окружения ANDROID_BUILD_TOP, если она не установлена или пуста, то предполагается сборка из-под NDK.
Рассмотрим пример сборки плагина mpeg2enc от gst-plugins-bad для gstreamer’а.
Введение
Вся сборка будет осуществляться с использованием AOSP:
- Проект Gstreamer и все плагины располагаются в папке «external/gstreamer-aggregate»
- Файл Android.mk для подготовки переменных окружения располагается в папке «external/gstreamer-aggregate/jni»
- Плагин gst-plugins-bad располагается в папке «external/gstreamer-aggregate/gst-plugins-bad»
- Файл Android.mk, для конфигурирования плагина gst-plugins-bad, располагается в папке «external/gstreamer-aggregate/gst-plugins-bad/Android.mk»
Шаг 1. Подготовка переменных окружения
Рассмотрим создание файла Android.mk, который будет располагаться по пути «external/gstreamer-aggregate/jni/Android.mk». Возьмем за основу файл Android.mk и рассмотрим подробнее:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS)
Задание локального пути и очистка значений некоторых переменных.
GSTREAMER_AGGREGATE_TOP := $(abspath $(LOCAL_PATH))/..
Задание вершины директории, где располагаются исходники Gstremer. В нашей случае переменная GSTREAMER_AGGREGATE_TOP будет указывать на “/path/to/AOSP/external/gstreamer-aggregate”.
ifneq ($(SYSROOT),) NDK_BUILD := true else NDK_BUILD := false endif
Если переменная SYSROOT задана, то сборка осуществляется с помощью ndk-build, иначе в исходниках AOSP.
ifeq ($(gstreamer_TOP),) gstreamer_TOP := $(GSTREAMER_AGGREGATE_TOP)/gstreamer endif ifeq ($(GST_PLUGINS_GOOD_TOP),) GST_PLUGINS_GOOD_TOP := $(GSTREAMER_AGGREGATE_TOP)/gst-plugins-bad endif ifeq ($(GST_PLUGINS_BAD_TOP),) GST_PLUGINS_BAD_TOP := $(GSTREAMER_AGGREGATE_TOP)/gst-plugins-bad endif
Задание глобальных переменных, где расположены библиотеки, приложения, плагины и т.п. Эти переменные нам понадобятся в дальнейшем для сборки и конфигурации.
CONFIGURE_CC := $(TARGET_CC) CONFIGURE_CXX := $(TARGET_CXX) CONFIGURE_INCLUDES := CONFIGURE_LDFLAGS := -lc -ldl # as ndk-build ifeq ($(NDK_BUILD),true) CONFIGURE_CFLAGS := \ -nostdlib -Bdynamic \ -Wl,-dynamic-linker,/system/bin/linker \ -Wl,--gc-sections \ -Wl,-z,nocopyreloc \ $(call host-path,\ $(TARGET_CRTBEGIN_DYNAMIC_O) \ $(PRIVATE_OBJECTS)) \ $(call link-whole-archives,$(PRIVATE_WHOLE_STATIC_LIBRARIES))\ $(call host-path,\ $(PRIVATE_STATIC_LIBRARIES) \ $(TARGET_LIBGCC) \ $(PRIVATE_SHARED_LIBRARIES)) \ $(PRIVATE_LDFLAGS) \ $(PRIVATE_LDLIBS) \ $(call host-path,\ $(TARGET_CRTEND_O)) \ $(CONFIGURE_INCLUDES) CONFIGURE_LDFLAGS += -L$(SYSROOT)/usr/lib -L$(TARGET_OUT) CONFIGURE_INCLUDES += -I$(SYSROOT)/usr/include \ -I$(GSTREAMER_AGGREGATE_TOP)/libid3tag \ -I$(GSTREAMER_AGGREGATE_TOP)/libmad \ -I$(GSTREAMER_AGGREGATE_TOP)/faad/include CONFIGURE_CPP := $(TOOLCHAIN_PREFIX)cpp CONFIGURE_CXX := $(TOOLCHAIN_PREFIX)c++ LIB := $(SYSROOT)/usr/lib # as AOSP build else LIB := $(TARGET_OUT_SHARED_LIBRARIES) CONFIGURE_CC := $(patsubst %,$(PWD)/%,$(TARGET_CC)) CONFIGURE_CXX := $(patsubst %,$(PWD)/%,$(TARGET_CXX)) CONFIGURE_LDFLAGS += -L$(PWD)/$(TARGET_OUT_INTERMEDIATE_LIBRARIES) CONFIGURE_CFLAGS := \ -nostdlib -Bdynamic \ -Wl,-dynamic-linker,/system/bin/linker \ -Wl,--gc-sections \ -Wl,-z,nocopyreloc CONFIGURE_LDFLAGS += \ $(PWD)/$(TARGET_CRTBEGIN_DYNAMIC_O) \ $(call link-whole-archives,$(PRIVATE_WHOLE_STATIC_LIBRARIES))\ $(PRIVATE_STATIC_LIBRARIES) \ $(PWD)/$(TARGET_LIBGCC) \ $(PRIVATE_SHARED_LIBRARIES) \ $(PWD)/$(TARGET_CRTEND_O) CONFIGURE_CPP := $(PWD)/$(TARGET_TOOLS_PREFIX)cpp CONFIGURE_INCLUDES += \ $(foreach incdir, $(realpath $(C_INCLUDES) $(TARGET_C_INCLUDES)), -I$(incdir)) \ -I$(abspath $(TOP)/external/zlib) \ -I$(GSTREAMER_AGGREGATE_TOP)/libid3tag \ -I$(GSTREAMER_AGGREGATE_TOP)/libmad \ -I$(GSTREAMER_AGGREGATE_TOP)/faad/include endif CONFIGURE_CPPFLAGS := \ $(CONFIGURE_INCLUDES) CONFIGURE_CXXFLAGS := \ $(CONFIGURE_INCLUDES)
Различные переменные окружения, необходимые для компиляции, линковки и т.д…
# configure as ./autogen.sh CONFIGURE := autogen.sh # or configure as ./configure CONFIGURE := configure
Исполняемый файл для запуска конфигурирования.
CONFIGURE_PKG_CONFIG_LIBDIR := $(GLIB_TOP):$(gstreamer_TOP)/pkgconfig:$(GST_PLUGINS_BASE_TOP)/pkgconfig:$(GST_PLUGINS_GOOD_TOP)/pkgconfig:$(GST_PLUGINS_BAD_TOP)/pkgconfig:$(GSTREAMER_AGGREGATE_TOP)/x264 PKG_CONFIG := PKG_CONFIG_LIBDIR=$(CONFIGURE_PKG_CONFIG_LIBDIR) PKG_CONFIG_TOP_BUILD_DIR="/" pkg-config
Переменные окружения для указания директорий, где расположены файлы конфигурация пакетов (pc-файлы). Не всегда работает, зачастую приходится вручную править или вообще убирать зависимости в файлах, таких как configure.ac, чтобы завершить успешно конфигурирование.
На основной системе должен быть установлен pkg-config
GST_CFLAGS := \ -DD_GNU_SOURCE \ -DGST_DISABLE_DEPRECATED \ -DHAVE_CONFIG_H \ -I$(gstreamer_TOP)
Специфичные глобальные опции для Gstreamer.
GST_CFLAGS += \ $(shell $(PKG_CONFIG) gstreamer --cflags)
Добавление специфичных глобальных опций для Gstreamer из пакета конфигураций. Для это требуется чтобы в основной системе был установлен пакет «gstreamer». Опция не критическая, но зачастую бывает полезной.
Шаг 2. Подготовка к конфигурации
Рассмотрим создание файла Android.mk, который будет располагаться по пути «external/gstreamer-aggregate/gst-plugins-bad/Android.mk», для конфигурирования и создания конечных файлов Android.mk для сборки бинарных библиотек/плагинов. Возьмем за основу файл Android.mk и рассмотрим подробнее:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS)
Задание локального пути и очистка значений некоторых переменных.
GST_PLUGINS_BAD_TOP := $(LOCAL_PATH)
Задание вершины директории, где располагаются исходники gst-plugins-bad. В нашей случае переменная GST_PLUGINS_BAD_TOP будет указывать на “/path/to/AOSP/external/gstreamer-aggregate/gst-plugins-bad”.
GST_PLUGINS_BAD_BUILT_SOURCES := \ pkgconfig/gstreamer-plugins-bad-1.0-uninstalled.pc \ pkgconfig/gstreamer-plugins-bad-1.0.pc \ gst-libs/gst/baseparse/Android.mk \ gst-libs/gst/basecamerabinsrc/Android.mk \ gst-libs/gst/codecparsers/Android.mk \ gst-libs/gst/interfaces/Android.mk \ gst/h264parse/Android.mk \ ...
Эта переменная содержит пути и цели для make. К примеру, нам нужно зайти в папку «gst/h264parse» и выполнить «make Android.mk», для этого выполним команду «cd gst/h264parse; make Android.mk», что эквивалентно команде «make -C ‘gst/h264parse’ Android.mk»
GST_PLUGINS_BAD_BUILT_SOURCES := $(patsubst %, $(abspath $(GST_PLUGINS_BAD_TOP))/%, $(GST_PLUGINS_BAD_BUILT_SOURCES))
Меняем относительные пути на абсолютные.
.PHONY: gst-plugins-bad-configure
Добавляем «ложную» цель на всякий случай.
gst-plugins-bad-configure: cd $(GST_PLUGINS_BAD_TOP) ; \ CC="$(CONFIGURE_CC)" \ CFLAGS="$(CONFIGURE_CFLAGS)" \ CXX="$(CONFIGURE_CXX)" \ CXXFLAGS="$(CONFIGURE_CXXFLAGS)" \ LD=$(TARGET_LD) \ LDFLAGS="$(CONFIGURE_LDFLAGS)" \ CPP=$(CONFIGURE_CPP) \ CPPFLAGS="$(CONFIGURE_CPPFLAGS)" \ PKG_CONFIG_LIBDIR="$(CONFIGURE_PKG_CONFIG_LIBDIR)" \ PKG_CONFIG_TOP_BUILD_DIR=/ \ $(abspath $(GST_PLUGINS_BAD_TOP))/$(CONFIGURE) \ --prefix=/system --host=arm-linux-androideabi \ --disable-gtk-doc \ --disable-valgrind && \ for file in $(GST_PLUGINS_BAD_BUILT_SOURCES); do \ rm -f $$file && \ make -C $$(dirname $$file) $$(basename $$file) ; \ done
Создаем цель gst-plugins-bad-configure.
При конфигурировании всегда указываем опцию “–prefix=/system”, так как по умолчанию в Android все системные библиотеки, приложения, настройки и т.п. располагаются в папке /system
Для ARM-систем обязательно указываем опцию “–host=arm-linux-androideabi”.
Остальные опции конфигурирования уже зависят от Ваших требований, желаний и возможностей.
По необходимости можно воспользоваться дополнительными командами для чистки исходников «make distclean» и/или «autoreconf -fiv» перед конфигурированием.
-include $(GST_PLUGINS_BAD_TOP)/gst-libs/gst/baseparse/Android.mk -include $(GST_PLUGINS_BAD_TOP)/gst-libs/gst/basecamerabinsrc/Android.mk -include $(GST_PLUGINS_BAD_TOP)/gst-libs/gst/codecparsers/Android.mk -include $(GST_PLUGINS_BAD_TOP)/gst-libs/gst/interfaces/Android.mk -include $(GST_PLUGINS_BAD_TOP)/gst/h264parse/Android.mk ...
И в самом конце располагаются пути к файлам Android.mk, которые будут собирать библиотеки, приложения, плагины и т.д.
Шаг 3. Добавление цели в Makefile
Рассмотрим добавление цели «Android.mk» в файл Makefile.am, который будет располагаться по пути «external/gstreamer-aggregate/gst-plugins-bad/ext/mpeg2enc/Makefile.am», для построения файла Android.mk для сборки бинарного плагина. Возьмем за основу файл Makefile.am и рассмотрим подробнее:
plugin_LTLIBRARIES = libgstmpeg2enc.la
Название плагина
libgstmpeg2enc_la_SOURCES = \ gstmpeg2enc.cc \ gstmpeg2encoptions.cc \ gstmpeg2encoder.cc \ gstmpeg2encstreamwriter.cc \ gstmpeg2encpicturereader.cc
Исходные файлы, необходимые для сборки
libgstmpeg2enc_la_CXXFLAGS = \ $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(GST_CXXFLAGS) $(MPEG2ENC_CFLAGS) libgstmpeg2enc_la_LIBADD = \ $(GST_PLUGINS_BASE_LIBS) -lgstvideo-@GST_API_VERSION@ \ $(GST_LIBS) $(MPEG2ENC_LIBS) libgstmpeg2enc_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstmpeg2enc_la_LIBTOOLFLAGS = --tag=disable-static
Флаги для компиляции и линковки
noinst_HEADERS = \ gstmpeg2enc.hh \ gstmpeg2encoder.hh \ gstmpeg2encoptions.hh \ gstmpeg2encstreamwriter.hh \ gstmpeg2encpicturereader.hh
Файлы заголовков.
У нас есть все необходимое для созднание цели «Android.mk»
Android.mk: Makefile.am $(BUILT_SOURCES) androgenizer \ -:PROJECT libgstmpeg2enc -:SHARED libgstmpeg2enc \ -:TAGS eng debug \ -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ -:SOURCES $(libgstmpeg2enc_la_SOURCES) \ -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstmpeg2enc_la_CXXFLAGS) \ -:LDFLAGS $(libgstmpeg2enc_la_LDFLAGS) \ $(libgstmpeg2enc_la_LIBADD) \ -ldl \ -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ LOCAL_CPP_EXTENSION:='.cc' \ > $@
Следует обратить внимание на LOCAL_CPP_EXTENSION, я задал принудительно “.сс”, так как по умолчанию Android использует расширение «cpp». Всегда возникают проблемы, если в проекте храняться сразу несколько файлов с различными расширениями «cxx», «cpp» и «cc».
Перевод опций androgenizer, о которых можно прочитать в README.txt.
- -:PROJECT Название проекта. Параметр должен вызываться первым и только один раз.
- -:SUBDIR добавляет -include, кроме объявленной переменной ‹project›_TOP
- Путь замены для -I включений
- -:ABS_TOP задает абсолютный путь к папке исходных кодов
- -:REL_TOP задает относительный путь к папке исходных кодов
Всегда должно быть: -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir)
- Типы создаваемых модулей:
- -:STATIC создает статическую библиотеку для целевой системы (BUILD_STATIC_LIBRARY)
- -:SHARED создает динамическую библиотеку для целевой системы (BUILD_SHARED_LIBRARY)
- -:EXECUTABLE создает исполняемый файл для целевой системы (BUILD_EXECUTABLE)
- -:HOST_STATIC создает статическую библиотеку для хоста (BUILD_HOST_STATIC_LIBRARY)
- -:HOST_SHARED создает динамическую библиотеку для хоста (BUILD_HOST_SHARED_LIBRARY)
- -:HOST_EXECUTABLE создает исполняемый файл для хоста (BUILD_HOST_EXECUTABLE)
Несколько модулей могут быть созданы с помощью одной командной строки.
- Дополнительные ресурсы для модулей (вначале необходимо объявить модуль!):
- -:SOURCES список исходный файлов
-
- -:CFLAGS флаги для компилятора C
- -:CXXFLAGS флаги для компилятора C++
- -:CPPFLAGS флаги для препроцессора C
CPPFLAGS используется компиляторами C и C++
CFLAGS используется компиляторами C и C++
CXXFLAGS используется только компилятором C++
Android использует различные конвенции, поэтому не удивляйтесь, если CXXFLAGS окажется в конце LOCAL_CPPFLAGS в Android.mk
Не существует способ передать только флаги компилятора C в систему сборки Android.
Все -I флаги в любом из CFLAGS, CPPFLAGS или CXXFLAGS будут добавлены в LOCAL_C_INCLUDES без “-I”.
некоторые флаги удаляются без предупреждения: -Werror -pthread
- -:LDFLAGS список опций линковщика:
- -l‹foo› добавит как lib‹foo› в LOCAL_SHARED_LIBRARIES
- -L и -R будут удалены без предупреждения
- -pthread и -lpthread будут удалены без предупреждения
- -lrt будет удалена без предупреждения (rt встроенная библиотека в bionic)
- -no-undefined будет удалена без предупреждения
- -dlopen, -version-info, и слова следующие после них (опциональные агрументы) будут удалены без предупреждения
- Только файлы *.a и *.la сохраняться, остальные будут удалены без предупреждения.
- -:LIBFILTER_STATIC список библиотек (без префикса «lib» и расширения)Эти библиотеки будут добавлены в LOCAL_STATIC_LIBRARIES и исключены из LOCAL_SHARED_LIBRARIES.
- -:LIBFILTER_WHOLE список библиотек (без префикса «lib» и расширения)Эти библиотеки будут добавлены в LOCAL_WHOLE_STATIC_LIBRARIES.
- -:TAGS должен включать в себя любой из параметров: optional user eng tests
- -:HEADERS список файлов заголовков для LOCAL_COPY_HEADERS
- -:HEADER_TARGET устанавливает LOCAL_COPY_HEADERS_TO, может включать в себя несколько строк, но сохраниться только последняя
- -:PASSTHROUGH список строк, встраиваемых в текущую конфигурацию модуля. К примеру LOCAL_ARM_MODE:=arm
- -:END опционально… может быть удалено в будущем. Заканчивает описание текущего модуля, и начинает описание следующего.
Шаг 4. Сборка
Скачиваем androgenizer, собираем и кладем программу, где ее будет видно из переменной окружения PATH.
Теперь у нас все готово для сборки модуля. Для этого переходим в корень AOSP и выполняем команду:
make gst-plugins-bad-configure
Спустя какое-то время gst-plugins-bad сконфигурируется и на выходе получится готовый файл Android.mk:
external/gstreamer-aggregate/gst-plugins-bad/ext/mpeg2enc/Android.mk
LOCAL_PATH:=$(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE:=libgstmpeg2enc LOCAL_MODULE_TAGS:=eng debug LOCAL_SRC_FILES := \ gstmpeg2enc.cc \ gstmpeg2encoptions.cc \ gstmpeg2encoder.cc \ gstmpeg2encstreamwriter.cc \ gstmpeg2encpicturereader.cc LOCAL_SHARED_LIBRARIES:=\ libglib-2.0 \ libgobject-2.0 \ libgstreamer-0.10 \ libgstbase-0.10 \ libgstriff-0.10 \ libgsttag-0.10 \ libgstvideo-0.10 \ libdl LOCAL_LDFLAGS:=\ -module\ -avoid-version\ -export-symbols-regex\ -no-undefined\ -Wl,-Bsymbolic-functions LOCAL_CFLAGS := \ -DHAVE_CONFIG_H \ -I. \ -I/path/to/AOSP/external/gstreamer-aggregate/gst-plugins-bad \ -I/path/to/AOSP/external/gstreamer-aggregate/gst-plugins-bad/gst-libs \ -I/path/to/AOSP/external/gstreamer-aggregate/gst-plugins-base \ -I/path/to/AOSP/external/gstreamer-aggregate/gst-plugins-base/gst-libs \ -I/path/to/AOSP/external/gstreamer-aggregate/gstreamer \ -I/path/to/AOSP/external/gstreamer-aggregate/glib/glib \ -I/path/to/AOSP/external/gstreamer-aggregate/glib \ -I/path/to/AOSP/external/gstreamer-aggregate/glib/gmodule \ -I/path/to/AOSP/external/gstreamer-aggregate/gstreamer/libs \ -DG_THREADS_MANDATORY \ -DG_DISABLE_DEPRECATED \ -Wall \ -Wdeclaration-after-statement \ -Wvla \ -Wpointer-arith \ -Wmissing-declarations \ -Wmissing-prototypes \ -Wredundant-decls \ -Wundef \ -Wwrite-strings \ -Wformat-nonliteral \ -Wformat-security \ -Winit-self \ -Wmissing-include-dirs \ -Waddress \ -Waggregate-return \ -Wno-multichar \ -Wnested-externs \ -Wno-unused \ -g \ -DGST_DISABLE_DEPRECATED LOCAL_PRELINK_MODULE := false LOCAL_ARM_MODE:=arm LOCAL_MODULE_PATH:=$(TARGET_OUT)/lib/gstreamer-0.10 LOCAL_CPP_EXTENSION:=.cc include $(BUILD_SHARED_LIBRARY)
Итоги
Достоинства:
- Автоматическая генерация файлов Android.mk
Недостатки:
- Требует индивидуального составления и добавления цели Android.mk в каждый Makefile
- Требует работы напильником с каждым файлом Android.mk: очень часто не включены некоторые пути к заголовочным файлам, то опции лишние добавлены и т.п.
- Встраивание абсолютных путей в файл Android.mk, а хотелось бы на основе $(LOCAL_PATH)
Используемые статьи:
- Gstreamer Android Install Instructions
- From source code to ndk-build using autotools and androgenizer
- Androgenizer — porting libtoolized software to android, the easy way