Android NDK поддерживает использование CMake для компиляции кода C и C++ в приложении. В этой статье рассказывается, как использовать CMake с NDK через ExternalNativeBuild плагина Android Gradle или при непосредственном вызове CMake.
Файл набора инструментов CMake
NDK поддерживает CMake через файл набора инструментов. Файлы тулчейна – это файлы CMake,
которые настраивают поведение тулчейна для кросс-компиляции. Файл тулчейна, используемый для NDK, находится в NDK по адресу <NDK>/build/cmake/android.toolchain.cmake.
Параметры сборки, такие как ABI, minSdkVersion и т. д., указываются в командной строке при вызове cmake. Список поддерживаемых аргументов приведен в разделе Аргументы набора инструментов.
Новый файл набора инструментов
В более ранних версиях NDK мы экспериментировали с новой реализацией файла набора инструментов, которая должна была уменьшить различия в поведении при использовании файла набора инструментов NDK и встроенной поддержки CMake. Это потребовало значительного объема работы (которая не была завершена), но фактически не улучшило поведение, поэтому мы больше не занимаемся этим.
В новом файле набора инструментов есть регрессии поведения по сравнению со старым. Рекомендуется использовать режим работы по умолчанию. Если вы используете -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF, рекомендуем удалить этот флаг из сборки. Новый файл набора инструментов так и не достиг паритета со старым, поэтому вероятны регрессии в поведении.
Хотя мы не рекомендуем использовать новый файл тулчейна, в настоящее время мы не планируем удалять его из NDK. Это нарушило бы сборки, которые зависят от различий в поведении между новым и устаревшим файлами набора инструментов, и, к сожалению, переименование параметра, чтобы было ясно, что на самом деле рекомендуется "устаревший", также нарушило бы работу пользователей этого параметра. Если вы используете новый файл набора инструментов, вам не нужно выполнять миграцию. Однако имейте в виду, что любые ошибки, связанные с поведением нового файла набора инструментов, скорее всего, не будут исправлены, и вам придется выполнить миграцию.
Использование
Gradle
Файл тулчейна CMake используется автоматически при работе с externalNativeBuild. Подробную информацию вы найдете в руководстве по Android Studio Добавление кода C и C++ в проект.
Командная строка
При сборке с помощью CMake вне Gradle файл набора инструментов и его аргументы должны быть переданы в CMake. Пример:
$ cmake \
-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=$ABI \
-DANDROID_PLATFORM=android-$MINSDKVERSION \
$OTHER_ARGS
Аргументы набора инструментов
Файлу набора инструментов CMake можно передать следующие аргументы: Если вы используете Gradle, добавьте аргументы в android.defaultConfig.externalNativeBuild.cmake.arguments, как описано в документации по ExternalNativeBuild. Если вы выполняете сборку из командной строки, передайте аргументы в CMake с помощью -D. Например, чтобы принудительно запретить сборку armeabi-v7a с поддержкой Neon, передайте -DANDROID_ARM_NEON=FALSE.
ANDROID_ABI
Целевой ABI. Подробнее о поддерживаемых ABI Android…
Gradle
Gradle предоставляет этот аргумент автоматически. Не задавайте этот аргумент в файле build.gradle. Чтобы указать, для каких ABI предназначена сборка Gradle, используйте abiFilters, как описано в статье Android ABI.
Командная строка
CMake builds for a single target per build. Чтобы настроить таргетинг на несколько ABI Android, необходимо выполнить сборку для каждого из них. Рекомендуется использовать разные каталоги сборки для каждого ABI, чтобы избежать конфликтов между сборками.
| Значение | Примечания |
|---|---|
armeabi-v7a |
|
armeabi-v7a with NEON |
Как в armeabi-v7a. |
arm64-v8a |
|
x86 |
|
x86_64 |
ANDROID_ARM_MODE
Указывает, нужно ли генерировать инструкции arm или thumb для armeabi-v7a. Для других ABI этот параметр не действует. Подробную информацию можно найти в документации по двоичным интерфейсам приложений Android.
| Значение | Примечания |
|---|---|
| рука | |
| большой палец | Режим работы по умолчанию. |
ANDROID_NATIVE_API_LEVEL
Псевдоним для ANDROID_PLATFORM.
ANDROID_PLATFORM
Указывает минимальный уровень API, поддерживаемый приложением или библиотекой. Это значение соответствует minSdkVersion приложения.
Gradle
При использовании плагина Android Gradle это значение автоматически устанавливается в соответствии с minSdkVersion приложения и не должно задаваться вручную.
Командная строка
При непосредственном вызове CMake по умолчанию используется самый низкий уровень API, поддерживаемый используемым NDK. Например, в NDK r20 по умолчанию используется уровень API 16.
Для этого параметра поддерживается несколько форматов:
android-$API_LEVEL$API_LEVELandroid-$API_LETTER
Формат $API_LETTER позволяет указать android-N без необходимости определять номер, связанный с этим выпуском. Обратите внимание, что в некоторых выпусках
уровень API повышался без изменения буквы. Эти API можно указать, добавив суффикс -MR1. Например, уровень API 25 – это android-N-MR1.
ANDROID_STL
Указывает, какую STL использовать для этого приложения. Подробнее о поддержке библиотек C++… По умолчанию будет использоваться c++_static.
| Значение | Примечания |
|---|---|
| c++_shared | Вариант общей библиотеки libc++. |
| c++_static | Вариант статической библиотеки libc++. |
| нет | Стандартная библиотека C++ не поддерживается. |
| система | Системная библиотека STL |
Управление флагами компилятора
Если вам нужно передать компилятору или компоновщику определенные флаги, ознакомьтесь с документацией по CMake для set_target_compile_options и связанных параметров. В разделе "См. также" внизу страницы есть полезные подсказки.
В целом рекомендуется применять флаги компилятора в максимально узкой области видимости. Флаги, которые вы хотите применить ко всем своим целям (например, -Werror), неудобно повторять для каждого модуля, но их все же редко следует применять глобально (CMAKE_CXX_FLAGS), поскольку это может иметь нежелательные последствия для сторонних зависимостей в вашем проекте. В таких случаях флаги можно применять на уровне каталога (add_compile_options).
Для узкого подмножества флагов компилятора их также можно задать в файле build.gradle с помощью cppFlags или аналогичных свойств. Не делайте этого. Флаги, переданные в CMake из Gradle, будут иметь неожиданное поведение приоритета, в некоторых случаях переопределяя флаги, неявно переданные реализацией, которые необходимы для сборки кода Android. Всегда предпочитайте обрабатывать поведение CMake непосредственно в CMake. Если вам нужно управлять флагами компилятора для каждого AGP buildType, ознакомьтесь с разделом Работа с типами сборки AGP в CMake.
Как работать с типами сборки AGP в CMake
Если вам нужно настроить поведение CMake для пользовательского типа сборки Gradle buildType, используйте этот тип сборки, чтобы передать дополнительный флаг CMake (не флаг компилятора), который могут считывать ваши скрипты сборки CMake. Например, если у вас есть варианты сборки free и premium, которые контролируются файлом build.gradle.kts, и вам нужно передать эти данные в CMake, сделайте следующее:
android {
buildTypes {
free {
externalNativeBuild {
cmake {
arguments.add("-DPRODUCT_VARIANT_PREMIUM=OFF")
}
}
}
premium {
externalNativeBuild {
cmake {
arguments.add("-DPRODUCT_VARIANT_PREMIUM=ON")
}
}
}
}
}
Затем в файле CMakeLists.txt:
if (DPRODUCT_VARIANT_PREMIUM)
# Do stuff for the premium build.
else()
# Do stuff for the free build.
endif()
Название переменной может быть любым, но не должно начинаться с префиксов ANDROID_, APP_ или CMAKE_, чтобы не возникало конфликтов с существующими флагами.
Пример можно найти в образце NDK для Sanitizers.
Команда сборки CMake
При отладке проблем со сборкой CMake полезно знать конкретные аргументы сборки, которые Gradle использует при кросс-компиляции для Android.
Плагин Android Gradle сохраняет аргументы сборки, которые он использует для выполнения сборки CMake для каждой пары ABI и типа сборки, в build_command.txt. Эти файлы находятся в следующем каталоге:
<project-root>/<module-root>/.cxx/cmake/<build-type>/<ABI>/
В следующем фрагменте кода показан пример аргументов CMake для сборки отладочной версии примера hello-jni для архитектуры armeabi-v7a.
Executable : ${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/cmake
arguments :
-H${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/src/main/cpp
-DCMAKE_FIND_ROOT_PATH=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/prefab/armeabi-v7a/prefab
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_TOOLCHAIN_FILE=${HOME}/Android/Sdk/ndk/22.1.7171670/build/cmake/android.toolchain.cmake
-DANDROID_ABI=armeabi-v7a
-DANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DANDROID_PLATFORM=android-23
-DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a
-DCMAKE_ANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_MAKE_PROGRAM=${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/ninja
-DCMAKE_SYSTEM_NAME=Android
-DCMAKE_SYSTEM_VERSION=23
-B${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/armeabi-v7a
-GNinja
jvmArgs :
Build command args: []
Version: 1
Используйте готовые библиотеки
Если нужная вам готовая библиотека распространяется в виде AAR-файла, импортируйте и используйте ее, следуя документации по зависимостям Android Studio. Если вы не используете AGP, следуйте инструкциям на странице https://google.github.io/prefab/example-workflow.html. Однако, скорее всего, вам будет проще перейти на AGP.
Если библиотека не распространяется в виде AAR, инструкции по использованию предварительно созданных библиотек с CMake можно найти в add_library документации по целям IMPORTED в руководстве по CMake.
Создание стороннего кода
Существует несколько способов сборки стороннего кода в рамках проекта CMake. Выбор оптимального варианта зависит от конкретной ситуации. Часто лучше вообще этого не делать. Вместо этого создайте AAR-файл для библиотеки и используйте его в приложении. Публиковать этот AAR необязательно. Он может быть внутренним для вашего проекта Gradle.
Если это невозможно:
- Скопируйте сторонний источник в свой репозиторий и используйте add_subdirectory, чтобы создать его. Это работает, только если другая библиотека также создана с помощью CMake.
- Определите ExternalProject.
- Соберите библиотеку отдельно от проекта и импортируйте ее как готовую, следуя инструкциям.
Поддержка YASM в CMake
NDK поддерживает CMake для сборки кода на ассемблере, написанного на YASM, для запуска на архитектурах x86 и x86-64. YASM – это ассемблер с открытым исходным кодом для архитектур x86 и x86-64, основанный на ассемблере NASM.
Чтобы создать код сборки с помощью CMake, внесите следующие изменения в файл CMakeLists.txt проекта:
- Вызовите
enable_languageсо значениемASM_NASM. - В зависимости от того, что вы создаете – общую библиотеку или исполняемый двоичный файл, вызовите
add_libraryилиadd_executable. В аргументах передайте список исходных файлов, состоящий из файлов.asmдля программы сборки в YASM и файлов.cдля связанных библиотек или функций C.
В следующем фрагменте показано, как настроить файл CMakeLists.txt, чтобы создать программу YASM в виде общей библиотеки.
cmake_minimum_required(VERSION 3.6.0)
enable_language(ASM_NASM)
add_library(test-yasm SHARED jni/test-yasm.c jni/print_hello.asm)
Пример создания исполняемой программы YASM можно найти в тесте yasm в репозитории Git NDK.
Как сообщить о проблеме
Если у вас возникнут проблемы с NDK или его файлом тулчейна CMake, сообщите о них в системе отслеживания ошибок android-ndk/ndk на GitHub. Если у вас возникли проблемы с Gradle или плагином Android Gradle, сообщите об ошибке в Android Studio.