CMake

El NDK de Android admite el uso de CMake para compilar código C y C++ para tu app. En esta página, se analiza cómo usar CMake con el NDK a través de ExternalNativeBuild del complemento de Gradle para Android o cuando se invoca directamente a CMake.

El archivo de la cadena de herramientas de CMake

El NDK admite CMake a través de un archivo de cadena de herramientas. Los archivos de cadena de herramientas son archivos de CMake que personalizan el comportamiento de la cadena de herramientas para la compilación de forma cruzada. El archivo de cadena de herramientas que se usa para el NDK se encuentra dentro del NDK, en <NDK>/build/cmake/android.toolchain.cmake.

Los parámetros de compilación, como ABI, minSdkVersion, etc., se muestran en la línea de comandos cuando se invoca a cmake. Para obtener una lista de argumentos compatibles, consulta la sección Argumentos de la cadena de herramientas.

La "nueva" archivo de la cadena de herramientas

Los NDK anteriores experimentaron con una nueva implementación del archivo de la cadena de herramientas que reduciría las diferencias de comportamiento entre el uso del archivo de la cadena de herramientas del NDK y gracias a la compatibilidad integrada con CMake. Esto terminó requiriendo una cantidad significativa de trabajo (que no se completó), pero que en realidad no mejoró el comportamiento, por lo que no seguiremos trabajando en esto.

La "nueva" de la cadena de herramientas tiene regresiones de comportamiento en comparación con la versión de la cadena de herramientas. El comportamiento predeterminado es el flujo de trabajo recomendado. Si estás usando -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF, te recomendamos que quites esa marca de tu compilación. El nuevo archivo de la cadena de herramientas nunca alcanzó la paridad con el heredado. de la cadena de herramientas, por lo que probablemente existan regresiones de comportamiento.

Si bien recomendamos no usar el nuevo archivo de la cadena de herramientas, actualmente no hay planea quitarlo del NDK. Si lo hicieras, se dañarían las compilaciones que se basan en el diferencias de comportamiento entre los archivos nuevos y heredados de la cadena de herramientas lamentablemente, cambiaremos el nombre de la opción para que quede claro en realidad recomendado también interrumpiría a los usuarios de esa opción. Si estás feliz de usar el nuevo archivo de la cadena de herramientas que no necesitas migrar, pero ten en cuenta que cualquier error que se haya informado con el nuevo comportamiento del archivo de la cadena de herramientas. En su lugar, que deberás migrar.

Uso

Gradle

El uso del archivo de la cadena de herramientas de CMake es automático cuando se usa externalNativeBuild. Consulta la guía Cómo agregar código C y C++ a tu proyecto de Android Studio para obtener más información.

Línea de comandos

Cuando compiles con CMake fuera de Gradle, se deben pasar a CMake el archivo de la cadena de herramientas y sus argumentos. Por ejemplo:

$ cmake \
    -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI=$ABI \
    -DANDROID_PLATFORM=android-$MINSDKVERSION \
    $OTHER_ARGS

Argumentos de la cadena de herramientas

Se pueden pasar los siguientes argumentos al archivo de la cadena de herramientas de CMake. Si compilas con Gradle, agrega argumentos a android.defaultConfig.externalNativeBuild.cmake.arguments como se describe en los documentos de ExternalNativeBuild. Si compilas desde la línea de comandos, pasa argumentos a CMake con -D. Por ejemplo, para forzar armeabi-v7a a que no compile con Neon. compatibilidad, pasa -DANDROID_ARM_NEON=FALSE.

ANDROID_ABI

La ABI de destino. Para obtener información sobre las ABI compatibles, consulta ABI de Android.

Gradle

Gradle proporciona automáticamente este argumento. No lo establezcas manera explícita en tu archivo build.gradle. Para controlar las ABI de destino de Gradle, usa abiFilters como se describe en ABI de Android.

Línea de comandos

CMake compila para un solo destino por compilación. Para establecer más de una ABI de Android como destino, debes compilar una vez por cada ABI. Se recomienda usar diferentes directorios de compilación para cada ABI a fin de evitar colisiones entre compilaciones.

Valor Notas
armeabi-v7a
armeabi-v7a with NEON Es igual que armeabi-v7a.
arm64-v8a
x86
x86_64

ANDROID_ARM_MODE

Especifica si se deben generar instrucciones arm o thumb para armeabi-v7a. No tiene ningún efecto en otras ABI. Para obtener más información, consulta la documentación de ABI de Android.

Valor Notas
arm
thumb Es el comportamiento predeterminado.

ANDROID_NATIVE_API_LEVEL

Es el alias para ANDROID_PLATFORM.

ANDROID_PLATFORM

Especifica el nivel mínimo de API compatible con la app o biblioteca. Este valor corresponde al objeto minSdkVersion de la app.

Gradle

Cuando se usa el complemento de Gradle para Android, este valor se establece automáticamente para que coincida con el objeto minSdkVersion de la app y no se debe configurar de forma manual.

Línea de comandos

Cuando se invoca directamente a CMake, este valor se establece de forma predeterminada en el nivel de API más bajo compatible con el NDK en uso. Por ejemplo, con NDK r20, este valor se establece de forma predeterminada en la API nivel 16.

Se aceptan múltiples formatos para este parámetro:

  • android-$API_LEVEL
  • $API_LEVEL
  • android-$API_LETTER

El formato $API_LETTER te permite especificar android-N sin la necesidad de determinar el número asociado con esa actualización. Ten en cuenta que en algunas versiones se aumentó el nivel de API, pero no el de letra. Se pueden especificar estas API agregando el sufijo -MR1. Por ejemplo, el nivel de API 25 es android-N-MR1.

ANDROID_STL

Especifica qué STL usar para esta app. Para obtener más información, consulta Compatibilidad de la biblioteca C++. De forma predeterminada, se usará c++_static.

Valor Notas
c++_shared Es la variante de biblioteca compartida de libc++.
c++_static Es la variante de biblioteca estática de libc++.
ninguna No se admite la biblioteca C++ estándar.
sistema Es la STL del sistema.

Cómo administrar marcas del compilador

Si necesitas pasar indicadores específicos al compilador o vinculador para tu compilación, consulta la documentación de CMake para set_target_compile_options y la de la familia de opciones relacionada. La palabra "ver también" al final de la página. algunas pistas útiles.

En general, la práctica recomendada es aplicar marcas de compilador como las más específicas. el alcance disponible. Marcas que desea aplicar a todas sus orientaciones (como -Werror) no es conveniente repetir por módulo, pero aun así deberían ser se aplican de forma global (CMAKE_CXX_FLAGS), ya que pueden tener efectos no deseados en las dependencias de terceros en tu proyecto. En estos casos, las marcas se pueden se aplica en el alcance del directorio (add_compile_options).

En el caso de un subconjunto reducido de marcas de compilador, también se pueden configurar en tu archivo build.gradle. con cppFlags o propiedades similares. No debes hacer esto. Marcas pasados a CMake desde Gradle tendrá comportamientos de precedencia sorprendentes, en algunos que anulan las marcas que la implementación pasó implícitamente necesaria para compilar código de Android. Siempre priorizar el control del comportamiento de CMake directamente en CMake. Si necesitas controlar las marcas del compilador según buildType de AGP, consulta Cómo trabajar con tipos de compilación de AGP en CMake.

Cómo trabajar con tipos de compilación de AGP en CMake

Si necesitas adaptar el comportamiento de CMake a un buildType de Gradle personalizado, usa ese de compilación para pasar una marca adicional de CMake (no una marca de compilador) que tu Las secuencias de comandos de compilación de CMake pueden leer. Por ejemplo, si tienes "gratis" y "premium" variantes de compilación controladas por tu build.gradle.kts y debes pasar esos datos a CMake:

android {
    buildTypes {
        free {
            externalNativeBuild {
                cmake {
                    arguments.add("-DPRODUCT_VARIANT_PREMIUM=OFF")
                }
            }
        }
        premium {
            externalNativeBuild {
                cmake {
                    arguments.add("-DPRODUCT_VARIANT_PREMIUM=ON")
                }
            }
        }
    }
}

Luego, en tu CMakeLists.txt:

if (DPRODUCT_VARIANT_PREMIUM)
  # Do stuff for the premium build.
else()
  # Do stuff for the free build.
endif()

El nombre de la variable depende de ti, pero asegúrate de evitar cualquier cosa que tenga una los prefijos ANDROID_, APP_ o CMAKE_ para evitar colisión o confusión marcas existentes.

Consulta el ejemplo del NDK de Sanitizers para ver un ejemplo.

Cómo comprender el comando de compilación CMake

Al depurar los problemas de compilación de CMake, es útil conocer los argumentos de compilación específicos que usa Gradle cuando realiza compilaciones de forma cruzada para Android.

El complemento de Gradle para Android guarda los argumentos de compilación que utiliza a fin de ejecutar una compilación de CMake para cada par de ABI y tipo de compilación en el build_command.txt. Estos archivos están en el siguiente directorio:

<project-root>/<module-root>/.cxx/cmake/<build-type>/<ABI>/

En el siguiente fragmento, se muestra un ejemplo de los argumentos de CMake para compilar una versión depurable de la muestra de hello-jni que se orienta a la arquitectura 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

Cómo usar bibliotecas compiladas previamente

Si la biblioteca compilada previamente que necesitas importar está distribuida como una AAR, sigue los documentos de dependencia de Studio para importarlos y usarlos. Si no usas AGP, puedes seguir https://google.github.io/prefab/example-workflow.html, pero seguro resulte mucho más fácil migrar a AGP.

En el caso de las bibliotecas que no se distribuyen como AAR, puedes obtener instrucciones para usar bibliotecas compiladas previamente con CMake en la documentación de add_library sobre destinos de IMPORTED en el manual de CMake.

Cómo compilar código de terceros

Hay varias maneras de compilar código de terceros como parte de tu proyecto CMake, y la opción que mejor funcione dependerá de tu situación. A menudo, la mejor opción será no hacer eso. En su lugar, debes compilar una AAR para la biblioteca y consumirlo en tu aplicación. No es necesario que publiques esa AAR. Puede ser algo interno de tu proyecto de Gradle.

Si no es posible, haz lo siguiente:

  • Utiliza (es decir, copia) la fuente de terceros en tu repositorio y usa add_subdirectory a fin de compilarla. Esto solo funciona si la otra biblioteca también se compila con CMake.
  • Define un ExternalProject.
  • Compila la biblioteca independientemente del proyecto y sigue los pasos de la sección Cómo usar bibliotecas compiladas previamente para importarlas como compilaciones previas.

Compatibilidad con YASM en CMake

Con el NDK, CMake es compatible con la compilación de código ensamblado escrito en YASM para ejecutarse en arquitecturas x86 y x86-64. YASM es un ensamblador de código abierto para las arquitecturas x86 y x86-64 que se basa en el ensamblador NASM.

Para compilar el código ensamblado con CMake, realiza los siguientes cambios en el objeto CMakeLists.txt de tu proyecto:

  1. Llama a enable_language con el valor establecido en ASM_NASM.
  2. Si estás compilando una biblioteca compartida, llama a add_library, pero si estás compilando un objeto binario ejecutable, llama a add_executable. En los argumentos, pasa una lista de archivos de origen que conste de archivos .asm para el programa de ensamblaje en YASM y archivos .c para las funciones o bibliotecas C asociadas.

En el siguiente fragmento, se muestra cómo puedes configurar el objeto CMakeLists.txt para compilar un programa YASM como una biblioteca compartida.

cmake_minimum_required(VERSION 3.6.0)

enable_language(ASM_NASM)

add_library(test-yasm SHARED jni/test-yasm.c jni/print_hello.asm)

Para ver un ejemplo de cómo compilar un programa YASM como ejecutable, consulta la prueba de yasm en el repositorio git del NDK.

Cómo informar problemas

Si tienes problemas con el NDK o su archivo de cadena de herramientas de CMake, infórmalo por medio de la Herramienta de seguimiento de errores android-ndk/ndk en GitHub. Si tienes problemas con el complemento de Android para Gradle o Gradle, informa un error de Studio.