Настройка CMake

Скрипт сборки CMake – это обычный текстовый файл, который должен называться CMakeLists.txt и содержать команды, используемые CMake для сборки библиотек C/C++. Если у ваших нативных источников ещё нет скрипта сборки CMake, вам нужно создать его самостоятельно и включить соответствующие команды CMake. Подробнее о том, как установить и настроить NDK и CMake…

В этом разделе рассматриваются некоторые основные команды, которые вы должны включить в свой скрипт сборки, чтобы сообщить CMake, какие источники использовать при создании вашей нативной библиотеки. Дополнительную информацию можно найти в официальной документации по командам CMake.

После настройки нового скрипта сборки CMake необходимо настроить Gradle, чтобы включить проект CMake в качестве зависимости сборки. Это позволит Gradle собрать и упаковать нативную библиотеку вместе с APK-файлом приложения.

Примечание. Если в вашем проекте используется ndk-build, создавать скрипт сборки CMake не нужно. Вам нужно лишь настроить Gradle, чтобы включить существующий проект нативной библиотеки, указав путь к файлу Android.mk.

Как создать скрипт сборки CMake

Чтобы создать обычный текстовый файл, который можно использовать в качестве скрипта сборки CMake, выполните следующие действия:

  1. Откройте панель Project (Проект) в левой части IDE и выберите представление Project (Проект) в раскрывающемся меню.
  2. Нажмите правой кнопкой мыши на корневой каталог your-module и выберите New (Создать) > File (Файл).

    Примечание. Скрипт сборки можно создать в любом месте. Однако при настройке скрипта сборки пути к исходным файлам и библиотекам указываются относительно местоположения скрипта сборки.

  3. Введите CMakeLists.txt в качестве названия файла и нажмите ОК.

Теперь вы можете настроить скрипт сборки, добавив команды CMake. Чтобы указать CMake, что нужно создать нативную библиотеку из нативного исходного кода, добавьте в скрипт сборки команды cmake_minimum_required() и add_library().

# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.

cmake_minimum_required(VERSION 3.4.1)

# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add_library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.

add_library( # Specifies the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

Совет. С помощью команды add_executable() можно указать CMake, что вместо нативной библиотеки нужно создать исполняемый файл. Однако создавать исполняемые файлы из нативных источников необязательно. Для большинства проектов достаточно создать нативные библиотеки и упаковать их в APK.

Если вы добавите исходный файл или библиотеку в скрипт сборки CMake с помощью add_library(), то после синхронизации проекта в представлении Проект также появятся связанные заголовочные файлы. Однако, чтобы CMake мог найти заголовочные файлы во время компиляции, необходимо добавить в скрипт сборки CMake команду include_directories() и указать путь к заголовочным файлам:

add_library(...)

# Specifies a path to native header files.
include_directories(src/main/cpp/include/)

CMake использует следующее соглашение о наименовании файлов библиотек:

liblibrary-name.so

Например, если в скрипте сборки вы укажете native-lib в качестве названия общей библиотеки, CMake создаст файл libnative-lib.so. Однако при загрузке этой библиотеки в коде Java или Kotlin используйте название, указанное в скрипте сборки CMake:

Kotlin

companion object {
    init {
        System.loadLibrary("native-lib");
    }
}

Java

static {
    System.loadLibrary("native-lib");
}

Примечание. Если вы переименуете или удалите библиотеку в скрипте сборки CMake, вам нужно будет очистить проект, прежде чем Gradle применит изменения или удалит старую версию библиотеки из APK. Чтобы очистить проект, в строке меню выберите Build (Сборка) > Clean Project (Очистить проект).

Android Studio автоматически добавит исходные файлы и заголовки в группу cpp на панели Project (Проект). Используя несколько команд add_library(), вы можете определить дополнительные библиотеки для CMake, которые будут созданы из других исходных файлов.

Добавьте API NDK

Android NDK предоставляет набор нативных API и библиотек, которые могут быть вам полезны. Чтобы использовать любой из этих API, добавьте библиотеки NDK в файл скрипта CMakeLists.txt вашего проекта.

Готовые библиотеки NDK уже есть на платформе Android, поэтому вам не нужно создавать их или упаковывать в APK. Поскольку библиотеки NDK уже включены в путь поиска CMake, вам не нужно указывать расположение библиотеки в локальной установке NDK. Достаточно предоставить CMake название библиотеки, которую вы хотите использовать, и связать ее с собственной нативной библиотекой.

Добавьте команду find_library() в скрипт сборки CMake, чтобы найти библиотеку NDK и сохранить путь к ней в виде переменной. Эта переменная используется для ссылки на библиотеку NDK в других частях скрипта сборки. В следующем примере кода выполняется поиск библиотеки поддержки журналов для Android и сохраняется путь к ней в log-lib:

find_library( # Defines the name of the path variable that stores the
              # location of the NDK library.
              log-lib

              # Specifies the name of the NDK library that
              # CMake needs to locate.
              log )

Чтобы нативная библиотека могла вызывать функции из библиотеки log, свяжите их с помощью команды target_link_libraries() в скрипте сборки CMake:

find_library(...)

# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the log library to the target library.
                       ${log-lib} )

NDK также включает некоторые библиотеки в виде исходного кода, который необходимо собрать и связать с нативной библиотекой. Вы можете скомпилировать исходный код в нативную библиотеку с помощью команды add_library() в скрипте сборки CMake. Чтобы указать путь к локальной библиотеке NDK, можно использовать переменную пути ANDROID_NDK, которую Android Studio определяет автоматически.

Следующая команда указывает CMake, что нужно собрать android_native_app_glue.c, который управляет событиями жизненного цикла NativeActivity и сенсорным вводом, в статическую библиотеку и связать ее с native-lib:

add_library( app-glue
             STATIC
             ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )

# You need to link static libraries against your shared native library.
target_link_libraries( native-lib app-glue ${log-lib} )

Как добавить другие готовые библиотеки

Добавление предварительно созданной библиотеки похоже на указание другой нативной библиотеки для сборки CMake. Однако, поскольку библиотека уже создана, вам нужно использовать флаг IMPORTED, чтобы сообщить CMake, что вы хотите только импортировать библиотеку в свой проект:

add_library( imported-lib
             SHARED
             IMPORTED )

Затем укажите путь к библиотеке с помощью команды set_target_properties(), как показано ниже.

Некоторые библиотеки предоставляют отдельные пакеты для определенных архитектур ЦП или двоичных интерфейсов приложений (ABI) и упорядочивают их по отдельным каталогам. Такой подход позволяет библиотекам использовать преимущества определенных архитектур ЦП, а вам – только нужные версии библиотеки. Чтобы добавить в скрипт сборки CMake несколько версий библиотеки для разных ABI, не создавая отдельные команды для каждой версии, можно использовать переменную пути ANDROID_ABI. Эта переменная использует список ABI, поддерживаемых NDK, или отфильтрованный список ABI, которые вы вручную настроили в Gradle. Пример:

add_library(...)
set_target_properties( # Specifies the target library.
                       imported-lib

                       # Specifies the parameter you want to define.
                       PROPERTIES IMPORTED_LOCATION

                       # Provides the path to the library you want to import.
                       imported-lib/src/${ANDROID_ABI}/libimported-lib.so )

Чтобы CMake мог найти заголовочные файлы во время компиляции, используйте команду include_directories() и укажите путь к заголовочным файлам:

include_directories( imported-lib/include/ )

Примечание. Если вы хотите упаковать предварительно созданную библиотеку, которая не является зависимостью времени сборки (например, при добавлении предварительно созданной библиотеки, которая является зависимостью imported-lib), вам не нужно выполнять следующие инструкции, чтобы связать библиотеку.

Чтобы связать предварительно созданную библиотеку с собственной нативной библиотекой, добавьте ее в команду target_link_libraries() в скрипте сборки CMake:

target_link_libraries( native-lib imported-lib app-glue ${log-lib} )

Чтобы упаковать предварительно созданную библиотеку в APK, необходимо вручную настроить Gradle с помощью блока sourceSets, чтобы включить путь к файлу .so. После сборки APK-файла вы можете проверить, какие библиотеки Gradle упаковывает в него, с помощью APK Analyzer.

Как включить другие проекты CMake

Если вы хотите создать несколько проектов CMake и включить их выходные данные в проект для Android, вы можете использовать один файл CMakeLists.txt в качестве скрипта сборки CMake верхнего уровня (который вы связываете с Gradle) и добавить дополнительные проекты CMake в качестве зависимостей этого скрипта сборки. В следующем скрипте сборки CMake верхнего уровня используется команда add_subdirectory(), которая указывает другой файл CMakeLists.txt в качестве зависимости сборки, а затем связывает его с выходными данными, как и в случае с любой другой предварительно созданной библиотекой.

# Sets lib_src_DIR to the path of the target CMake project.
set( lib_src_DIR ../gmath )

# Sets lib_build_DIR to the path of the desired output directory.
set( lib_build_DIR ../gmath/outputs )
file(MAKE_DIRECTORY ${lib_build_DIR})

# Adds the CMakeLists.txt file located in the specified directory
# as a build dependency.
add_subdirectory( # Specifies the directory of the CMakeLists.txt file.
                  ${lib_src_DIR}

                  # Specifies the directory for the build outputs.
                  ${lib_build_DIR} )

# Adds the output of the additional CMake build as a prebuilt static
# library and names it lib_gmath.
add_library( lib_gmath STATIC IMPORTED )
set_target_properties( lib_gmath PROPERTIES IMPORTED_LOCATION
                       ${lib_build_DIR}/${ANDROID_ABI}/lib_gmath.a )
include_directories( ${lib_src_DIR}/include )

# Links the top-level CMake build output against lib_gmath.
target_link_libraries( native-lib ... lib_gmath )

Вызов CMake из командной строки

Чтобы вызвать CMake для создания проекта Ninja за пределами Android Studio, используйте следующую команду:

cmake
-Hpath/to/cmakelists/folder
-Bpath/to/generated/ninja/project/debug/ABI
-DANDROID_ABI=ABI                               // For example, arm64-v8a
-DANDROID_PLATFORM=platform-version-string      // For example, android-16
-DANDROID_NDK=android-sdk/ndk/ndk-version
-DCMAKE_TOOLCHAIN_FILE=android-sdk/ndk/ndk-version/build/cmake/android.toolchain.cmake
-G Ninja

Эта команда создаст проект Ninja, который можно выполнить для создания исполняемых библиотек Android (файлы .so). Для поддержки CMake в NDK требуется CMAKE_TOOLCHAIN_FILE. Для CMake 3.21 и более поздних версий можно использовать встроенную поддержку NDK, но потребуется другая группа переменных, как описано в документации CMake Cross Compiling for Android (Кросс-компиляция для Android).