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.
El archivo "nuevo" de la cadena de herramientas
Los NDKs anteriores experimentaron con una nueva implementación del archivo de cadena de herramientas que reduciría las diferencias de comportamiento entre el uso del archivo de cadena de herramientas del NDK y el uso de la compatibilidad integrada de CMake. Esto terminó requiriendo una cantidad significativa de trabajo (que aún no se completó), pero en realidad no mejoró el comportamiento, por lo que ya no estamos trabajando en esto.
El archivo de cadena de herramientas "nuevo" tiene regresiones de comportamiento en comparación con el archivo de cadena de herramientas "heredado". El comportamiento predeterminado es el flujo de trabajo recomendado. Si usas -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF
, te recomendamos que quites esa marca de tu compilación. El nuevo archivo de cadena de herramientas nunca alcanzó la paridad con el archivo de cadena de herramientas heredado, por lo que es probable que haya regresiones de comportamiento.
Si bien no recomendamos usar el nuevo archivo de cadena de herramientas, actualmente no hay planes para quitarlo del NDK. De lo contrario, se interrumpirían las compilaciones que dependen de las diferencias de comportamiento entre los archivos de la cadena de herramientas nueva y heredada. Lamentablemente, cambiar el nombre de la opción para dejar en claro que se recomienda usar "heredado" también interrumpiría a los usuarios de esa opción. Si usas el archivo de cadena de herramientas nuevo, no es necesario que realices la migración, pero ten en cuenta que es probable que no se corrijan los errores relacionados con el comportamiento del archivo de cadena de herramientas nuevo y, en su lugar, deberás realizar la migración.
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 hacer que armeabi-v7a no compile con compatibilidad con Neon, 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 marcas específicas al compilador o vinculador para tu compilación, consulta la documentación de CMake para set_target_compile_options y la familia de opciones relacionada. La sección "ver también" en la parte inferior de esa página tiene algunas pistas útiles.
En general, se recomienda aplicar marcas del compilador como el ámbito más estrecho disponible. Las marcas que deseas aplicar a todos tus destinos (como -Werror
) son incómodas de repetir por módulo, pero rara vez deben aplicarse de forma global (CMAKE_CXX_FLAGS
), ya que pueden tener efectos no deseados en las dependencias de terceros de tu proyecto. En esos casos, las marcas se pueden aplicar a nivel del directorio (add_compile_options
).
Para un subconjunto reducido de marcas del compilador, también se pueden configurar en el archivo build.gradle con cppFlags
o propiedades similares. No deberías hacerlo. Las marcas que se pasan a CMake desde Gradle tendrán comportamientos de prioridad sorprendentes, en algunos casos, anulando las marcas que pasa implícitamente la implementación y que son necesarias para compilar el código de Android. Siempre prefiere controlar el comportamiento de CMake directamente en CMake. Si necesitas controlar las marcas del compilador por 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 tipo de compilación para pasar un indicador CMake adicional (no un indicador del compilador) que tus secuencias de comandos de compilación de CMake puedan leer. Por ejemplo, si tienes variantes de compilación "gratis" y "premium" controladas por build.gradle.kts y necesitas pasar esos datos a CMake, ejecuta el siguiente comando:
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, haz lo siguiente:
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 elemento con un prefijo ANDROID_
, APP_
o CMAKE_
para evitar colisiones o confusiones con 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:
- Llama a
enable_language
con el valor establecido enASM_NASM
. - Si estás compilando una biblioteca compartida, llama a
add_library
, pero si estás compilando un objeto binario ejecutable, llama aadd_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.