Cómo configurar CMake

Una secuencia de comandos de compilación de CMake es un archivo de texto sin formato al que debes asignarle el nombre CMakeLists.txt. Además, incluye comandos que CMake usa para compilar las bibliotecas C/C++. Si las fuentes nativas aún no tienen una secuencia de comandos de compilación de CMake, debes crear una e incluirla en los comandos correspondientes. Si quieres saber más sobre la instalación de CMake, consulta Cómo instalar y configurar el NDK y CMake.

En esta sección, se abordan algunos comandos básicos que debes incluir en la secuencia de comandos de compilación para indicar a CMake las fuentes que debe usar cuando creas una biblioteca nativa. Para obtener más información, consulta la documentación oficial sobre comandos de CMake.

Después de configurar una secuencia de comandos de compilación de CMake, debes configurar Gradle para incluir el proyecto de CMake como dependencia de compilación, de modo que Gradle compile y empaquete la biblioteca nativa con el APK de la app.

Nota: Si el proyecto usa ndk-build, no es necesario crear una secuencia de comandos de compilación de CMake. Puedes configurar Gradle para que incluya el proyecto de la biblioteca nativa existente. Para ello, proporciona una ruta de acceso al archivo Android.mk.

Cómo crear una secuencia de comandos de compilación de CMake

Para crear un archivo de texto sin formato que puedas usar como secuencia de comandos de compilación de CMake, haz lo siguiente:

  1. Abre el panel Project del lateral izquierdo del IDE y selecciona la vista Project del menú desplegable.
  2. Haz clic con el botón derecho en el directorio raíz de your-module y selecciona New > File.

    Nota: Puedes crear la secuencia de comandos de compilación en cualquier ubicación que desees. Sin embargo, cuando configuras la secuencia de comandos de compilación, las rutas de acceso a los archivos fuente nativos y a las bibliotecas varían según la ubicación de la secuencia de comandos de compilación.

  3. Ingresa "CMakeLists.txt" como nombre del archivo y haz clic en OK.

Ahora podrás configurar tu secuencia de comandos de compilación agregando los comandos de CMake. Para indicar a CMake que cree una biblioteca nativa del código fuente nativo, agrega los comandos cmake_minimum_required() y add_library() a la secuencia de comandos de compilación:

# 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 )

Sugerencia: De manera similar a como le indicas a CMake que cree una biblioteca nativa desde los archivos de origen, puedes usar el comando add_executable() para indicarle que, en su lugar, cree un ejecutable desde esos archivos. Sin embargo, la creación de ejecutables desde fuentes nativas es opcional, y la compilación de bibliotecas nativas para empaquetar en el APK satisface la mayoría de los requisitos de los proyectos.

Cuando agregas un archivo de origen o una biblioteca a la secuencia de comandos de compilación de CMake con add_library(), Android Studio también muestra los archivos de encabezado asociados en la vista Project una vez que sincronizas el proyecto. Sin embargo, para que CMake ubique los archivos de encabezado durante el tiempo de compilación, debes agregar el comando include_directories() a la secuencia de compilación de CMake y especificar la ruta de acceso de los encabezados:

add_library(...)

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

La convención que usa CMake para asignar nombres a los archivos de la biblioteca es la siguiente:

liblibrary-name.so

Por ejemplo, si especificas "native-lib" como nombre de la biblioteca compartida en la secuencia de comandos de compilación, CMake creará un archivo denominado libnative-lib.so. Sin embargo, si cargas esta biblioteca en el código Java o Kotlin, usa el nombre que hayas especificado en la secuencia de comandos de compilación de CMake:

Kotlin

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

Java

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

Nota: Si cambias el nombre o quitas una biblioteca de la secuencia de comandos de compilación de CMake, deberás borrar el proyecto antes de que Gradle aplique los cambios o quite la versión anterior de la biblioteca del APK. Para borrar el proyecto, selecciona Build > Clean Project en la barra de menú.

Android Studio agrega automáticamente los archivos de origen y los encabezados al grupo cpp del panel Project. Con el uso de varios comandos add_library(), puedes definir bibliotecas adicionales para que CMake realice compilaciones desde otros archivos de origen.

Cómo agregar APIs de NDK

El NDK de Android proporciona un conjunto de APIs y bibliotecas nativas que podrían serte útiles. Puedes usar cualquiera de estas APIs incluyendo las bibliotecas del NDK en el archivo de secuencia de comandos CMakeLists.txt del proyecto.

Existen bibliotecas de NDK ya compiladas en la plataforma de Android, de modo que no es necesario compilarlas ni empaquetarlas en el APK. Dado que las bibliotecas de NDK ya son parte de la ruta de búsqueda de CMake, no es necesario especificar su ubicación en la instalación local de NDK. Solo debes proporcionar a CMake el nombre de la biblioteca que quieras usar y vincularla a la nativa.

Agrega el comando find_library() a la secuencia de comandos de compilación de CMake para ubicar una biblioteca de NDK y almacenar la ruta de acceso como una variable, que se usará para hacer referencia a la biblioteca de NDK en otras partes de la secuencia de comandos de compilación. En el siguiente ejemplo, se ubica la biblioteca de compatibilidad de registro específica de Android y se almacena la ruta de acceso en 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 )

Para que la biblioteca nativa llame a funciones de la biblioteca log, debes vincular ambas con el comando target_link_libraries() en la secuencia de compilación de 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} )

El NDK también incluye algunas bibliotecas como código fuente que debes compilar y vincular a la biblioteca nativa. Puedes hacerlo en una biblioteca nativa usando el comando add_library() en la secuencia de comandos de compilación de CMake. Para proporcionar una ruta de acceso a la biblioteca del NDK local, puedes usar la variable de ruta ANDROID_NDK, que Android Studio definirá automáticamente.

El siguiente comando le indica a CMake que compile android_native_app_glue.c, que administra eventos de ciclo de vida y entradas táctiles de NativeActivity en una biblioteca estática y la vincula a 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} )

Cómo agregar otras bibliotecas ya compiladas

El proceso de agregar una biblioteca ya compilada es similar al de especificar otra biblioteca nativa para que CMake la compile. Sin embargo, debido a que la biblioteca ya está compilada, debes usar la marca IMPORTED para indicar a CMake que solo deseas importar la biblioteca al proyecto:

add_library( imported-lib
             SHARED
             IMPORTED )

Luego, debes especificar la ruta de acceso a la biblioteca con el comando set_target_properties(), como se muestra a continuación.

Algunas bibliotecas proporcionan paquetes separados para arquitecturas de CPU específicas o interfaces binarias de la aplicación (ABI), y las organizan en directorios separados. Este enfoque permite que las bibliotecas aprovechen determinadas arquitecturas de CPU y, al mismo tiempo, permite usar solo las versiones de bibliotecas que deseas. Para agregar varias versiones de ABI a una biblioteca de tu secuencia de comandos de compilación de CMake sin necesidad de escribir varios comandos para cada versión de la biblioteca, usa la variable de ruta de acceso ANDROID_ABI. Esta variable usa una lista de ABI predeterminadas compatibles con el NDK o una lista filtrada de ABI que debes configurar de forma manual para que las use Gradle. Por ejemplo:

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 )

Para que CMake ubique los archivos de encabezado durante el tiempo de compilación, debes usar el comando include_directories() e incluir la ruta de acceso a los archivos de encabezado:

include_directories( imported-lib/include/ )

Nota: Si deseas empaquetar una biblioteca ya compilada que no depende del tiempo de compilación (por ejemplo, cuando agregas una biblioteca ya compilada que depende de imported-lib), no es necesario que sigas las instrucciones para vincular la biblioteca.

Para vincular la biblioteca ya compilada a la nativa, agrégala al comando target_link_libraries() de la secuencia de comandos de compilación de CMake:

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

Para empaquetar la biblioteca ya compilada en el APK, debes configurar Gradle de forma manual con el bloque sourceSets para incluir la ruta de acceso al archivo .so. Después de compilar el APK, podrás verificar qué bibliotecas empaqueta Gradle en el APK a través del Analizador de APK.

Cómo incluir otros proyectos de CMake

Si deseas compilar varios proyectos de CMake e incluir los resultados en el proyecto de Android, puedes usar un archivo CMakeLists.txt como secuencia de comandos de compilación de CMake de nivel superior (que es el que vinculas a Gradle) y agregar proyectos de CMake adicionales como dependencias de esa secuencia de comandos de compilación. La siguiente secuencia de comandos de compilación de CMake usa el comando add_subdirectory() para especificar otro archivo CMakeLists.txt como dependencia de compilación y, luego, lo vincula con su resultado como lo haría con otra biblioteca ya compilada.

# 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 )

Cómo llamar a CMake desde la línea de comandos

Usa el siguiente comando para llamar a CMake y generar un proyecto de Ninja fuera de 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

Con este comando, se generará el proyecto de Ninja que se puede ejecutar para crear bibliotecas ejecutables de Android (archivos .so). Se requiere CMAKE_TOOLCHAIN_FILE para usar la compatibilidad de CMake con el NDK. Para CMake 3.21 o versiones posteriores, se puede usar la compatibilidad integrada con el NDK de CMake, pero se debe usar un grupo diferente de variables, como se describe en la documentación sobre la compilación cruzada para Android de CMake.