CMake

Android NDK prend en charge l'utilisation de CMake pour compiler du code C et C++ pour votre application. Cette page explique comment utiliser CMake avec le NDK via le ExternalNativeBuild du plug-in Android Gradle ou lorsque vous appelez directement CMake.

Fichier de chaîne d'outils CMake

Le NDK est compatible avec CMake grâce à un fichier de chaîne d'outils. Les fichiers de chaîne d'outils sont des fichiers CMake qui personnalisent le comportement de la chaîne d'outils pour la compilation croisée. Le fichier de chaîne d'outils utilisé pour le NDK se trouve dans celui-ci, à l'emplacement <NDK>/build/cmake/android.toolchain.cmake.

Les paramètres de compilation comme ABI, minSdkVersion, etc. sont fournis dans la ligne de commande lors de l'appel de cmake. Pour obtenir la liste des arguments acceptés, consultez la section Arguments de la chaîne d'outils.

Le nouveau fichier de chaîne d'outils

Les NDK précédents ont expérimenté une nouvelle implémentation du fichier de chaîne d'outils qui réduisait les différences de comportement entre l'utilisation du fichier de chaîne d'outils du NDK et l'utilisation de la compatibilité CMake intégrée. Cela a nécessité un travail important (qui n'a pas été terminé), mais n'a pas réellement amélioré le comportement, nous avons donc arrêté d'aller plus loin.

Le "nouveau" fichier de chaîne d'outils présente des régressions de comportement par rapport à l'"ancienne" fichier de chaîne d'outils. Le comportement par défaut est le workflow recommandé. Si vous utilisez -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF, nous vous recommandons de supprimer cet indicateur de votre build. Le nouveau fichier de chaîne d'outils n'a jamais atteint la parité avec l'ancien fichier de chaîne d'outils. Il y a donc probablement des régressions de comportement.

Bien que nous vous recommandions de ne pas utiliser le nouveau fichier de chaîne d'outils, il n'est actuellement pas prévu de le supprimer du NDK. Cela endommagerait les builds qui dépendent des différences de comportement entre les nouveaux fichiers de chaîne d'outils et les anciens fichiers de chaîne d'outils. Malheureusement, si vous renommiez l'option pour indiquer clairement que l'ancien fichier est en fait recommandé, les utilisateurs ne bénéficieraient pas de cette option. Si vous utilisez le nouveau fichier de chaîne d'outils, vous n'avez pas besoin de le migrer. Sachez toutefois que les bugs signalés concernant le comportement du nouveau fichier de chaîne d'outils ne seront probablement pas corrigés. Vous devrez plutôt le migrer.

Utilisation

Gradle

L'utilisation du fichier de chaîne d'outils CMake est automatique lorsque vous utilisez externalNativeBuild. Pour en savoir plus, consultez le guide Ajouter du code C et C++ à votre projet d'Android Studio.

Ligne de commande

Lorsque vous compilez votre projet avec CMake en dehors de Gradle, le fichier de chaîne d'outils et ses arguments doivent être transmis à CMake. Exemple :

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

Arguments de la chaîne d'outils

Les arguments suivants peuvent être transmis au fichier de chaîne d'outils CMake. Si vous compilez votre projet avec Gradle, ajoutez des arguments à android.defaultConfig.externalNativeBuild.cmake.arguments, comme décrit dans la documentation sur ExternalNativeBuild. Si vous compilez votre projet à partir de la ligne de commande, transmettez les arguments à CMake avec -D. Par exemple, pour forcer armeabi-v7a à ne pas compiler avec Neon, transmettez -DANDROID_ARM_NEON=FALSE.

ANDROID_ABI

ABI cible. Pour en savoir plus sur les ABI compatibles, consultez ABI Android.

Gradle

Gradle fournit automatiquement cet argument. Ne le définissez pas de manière explicite dans votre fichier build.gradle. Pour contrôler les ABI ciblées par Gradle, utilisez abiFilters comme décrit dans ABI Android.

Ligne de commande

La compilation CMake prévoit une seule cible par build. Pour cibler plusieurs ABI Android, vous devez effectuer une compilation pour chaque ABI. Il est recommandé d'utiliser des répertoires de compilation différents pour chaque ABI afin d'éviter les conflits entre les builds.

Valeur Remarques
armeabi-v7a
armeabi-v7a with NEON Équivalent à armeabi-v7a.
arm64-v8a
x86
x86_64

ANDROID_ARM_MODE

Indique s'il faut générer des instructions arm ou thumb pour armeabi-v7a. N'a aucun effet pour les autres ABI. Pour en savoir plus, consultez la documentation sur les ABI Android.

Valeur Remarques
arm
thumb Comportement par défaut

ANDROID_NATIVE_API_LEVEL

Alias pour ANDROID_PLATFORM

ANDROID_PLATFORM

Spécifie le niveau d'API minimal accepté par l'application ou la bibliothèque. Cette valeur correspond à la minSdkVersion de l'application.

Gradle

Lorsque vous utilisez le plug-in Android Gradle, cette valeur est définie automatiquement pour correspondre à la minSdkVersion de l'application et ne doit pas être définie manuellement.

Ligne de commande

Lorsque vous appelez directement CMake, cette valeur est définie par défaut sur le niveau d'API le plus bas accepté par le NDK utilisé. Par exemple, avec NDK r20, cette valeur est définie par défaut sur le niveau d'API 16.

Ce paramètre accepte plusieurs formats :

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

Le format $API_LETTER vous permet de spécifier android-N sans avoir à déterminer le numéro associé à cette version. Notez que certaines versions passent à un niveau d'API supérieur sans incrémentation de la lettre. Vous pouvez spécifier ces API en ajoutant le suffixe -MR1. Par exemple, le niveau d'API 25 correspond à android-N-MR1.

ANDROID_STL

Spécifie la STL à utiliser pour cette application. Pour en savoir plus, consultez Compatibilité avec les bibliothèques C++. La bibliothèque utilisée par défaut est c++_static.

Valeur Remarques
c++_shared Variante de bibliothèque partagée de libc++
c++_static Variante de bibliothèque statique de libc++
aucune Incompatible avec les bibliothèques standards C++.
système STL système

Gérer les options du compilateur

Si vous devez transmettre des indicateurs spécifiques au compilateur ou à l'éditeur de liens pour votre build, consultez la documentation CMake sur set_target_compile_options et la famille d'options associée. La section "Voir aussi" en bas de cette page contient des indices utiles.

En règle générale, il est recommandé d'appliquer les options du compilateur comme champ d'application le plus restreint disponible. Il n'est pas pratique de répéter les indicateurs que vous souhaitez appliquer à toutes vos cibles (par exemple, -Werror) par module. Toutefois, ils doivent rarement être appliqués à l'échelle mondiale (CMAKE_CXX_FLAGS), car ils peuvent avoir des effets indésirables sur les dépendances tierces de votre projet. Dans ce cas, les indicateurs peuvent être appliqués au niveau du répertoire (add_compile_options).

Pour un sous-ensemble restreint d'indicateurs de compilation, vous pouvez également les définir dans votre fichier build.gradle à l'aide de cppFlags ou de propriétés similaires. Ne le faites pas. Les indicateurs transmis à CMake à partir de Gradle présenteront des comportements de priorité surprenants, dans certains cas en ignorant les indicateurs transmis implicitement par l'implémentation, qui sont nécessaires pour compiler le code Android. Préférez toujours gérer le comportement CMake directement dans CMake. Si vous devez contrôler les indicateurs du compilateur par buildType AGP, consultez Utiliser les types de compilation AGP dans CMake.

Utiliser des types de compilation AGP dans CMake

Si vous devez adapter le comportement de CMake à un buildType Gradle personnalisé, utilisez ce type de compilation pour transmettre un indicateur CMake supplémentaire (et non un indicateur de compilateur) que vos scripts de compilation CMake peuvent lire. Par exemple, si vous disposez de variantes de compilation "free" et "premium" contrôlées par votre build.gradle.kts et que vous devez transmettre ces données à CMake:

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

Ensuite, dans votre fichier CMakeLists.txt:

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

Le nom de la variable vous appartient, mais veillez à éviter tout élément avec un préfixe ANDROID_, APP_ ou CMAKE_ pour éviter tout conflit ou confusion avec les indicateurs existants.

Consultez l'exemple de NDK Sanitizers pour voir un exemple.

Comprendre la commande de compilation CMake

Lors du débogage des problèmes de compilation CMake, il est utile de connaître les arguments de compilation spécifiques utilisés par Gradle lors de la compilation croisée pour Android.

Le plug-in Android Gradle enregistre les arguments utilisés pour exécuter une compilation CMake pour chaque association entre une ABI et un type de compilation dans le fichier build_command.txt. Ces fichiers se trouvent dans le répertoire suivant :

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

L'extrait de code suivant illustre l'utilisation d'arguments CMake pour créer une version débogable de l'exemple hello-jni ciblant l'architecture 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

Utiliser des bibliothèques prédéfinies

Si la bibliothèque prédéfinie que vous devez importer est distribuée en tant que fichiers AAR, suivez la documentation sur les dépendances de Studio pour les importer et les utiliser. Si vous n'utilisez pas AGP, vous pouvez suivre les instructions de la page https://google.github.io/prefab/example-workflow.html, mais il sera probablement beaucoup plus facile de migrer vers AGP.

Pour les bibliothèques qui ne sont pas distribuées en tant que fichiers AAR, consultez les instructions sur l'utilisation de bibliothèques prédéfinies avec CMake dans la documentation add_library sur les cibles IMPORTED dans le manuel CMake.

Développer un code tiers

Il existe plusieurs façons de créer du code tiers dans le cadre de votre projet CMake. L'option la plus adaptée dépend de votre situation. Souvent, la meilleure solution est de ne pas procéder ainsi. À la place, créez un fichier AAR pour la bibliothèque et utilisez-le dans votre application. Vous n'avez pas nécessairement besoin de publier ce fichier AAR. Il peut être interne à votre projet Gradle.

Si ce n'est pas possible :

  • Procurez-vous (c'est-à-dire copiez) la source tierce dans votre dépôt et utilisez add_subdirectory pour le créer. Cette méthode ne fonctionne que si l'autre bibliothèque est également créée avec CMake.
  • Définissez un ExternalProject.
  • Créez la bibliothèque séparément de votre projet et suivez la section Utiliser des bibliothèques prédéfinies pour l'importer en tant que telle.

Compatibilité avec YASM dans CMake

Le NDK assure la compatibilité de CMake avec la création de code assembleur écrit en YASM pour l'exécution sur des architectures x86 et x86-64. YASM est un assembleur Open Source destiné aux architectures x86 et x86-64. Il est basé sur l'assembleur NASM.

Pour créer du code assembleur avec CMake, apportez les modifications suivantes au fichier CMakeLists.txt de votre projet :

  1. Appelez enable_language en définissant la valeur sur ASM_NASM.
  2. Selon que vous créez une bibliothèque partagée ou un binaire exécutable, appelez add_library ou add_executable. Dans les arguments, transmettez la liste des fichiers sources comprenant les fichiers .asm pour l'assembleur en YASM, et les fichiers .c pour les bibliothèques ou fonctions C associées.

L'extrait de code suivant montre comment configurer CMakeLists.txt pour créer un programme YASM en tant que bibliothèque partagée.

cmake_minimum_required(VERSION 3.6.0)

enable_language(ASM_NASM)

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

Pour obtenir un exemple de création d'un programme YASM en tant qu'exécutable, consultez le test yasm dans le dépôt Git NDK.

Signaler des problèmes

Si vous rencontrez des problèmes avec le NDK ou son fichier de chaîne d'outils CMake, signalez-les via l'outil de suivi des problèmes android-ndk/ndk sur GitHub. Pour les problèmes liés à Gradle ou au plug-in Android Gradle, signalez un bug Studio.