Intégrer des systèmes de compilation C/C++ personnalisés à l'aide de Ninja (expérimental)

Si vous n'utilisez pas CMake ni ndk-build, mais que vous souhaitez une intégration complète du build C/C++ du plug-in Android Gradle et d'Android Studio, vous pouvez créer un système de compilation C/C++ personnalisé en créant un script shell qui écrit les informations de build dans le format de fichier de build Ninja.

La compatibilité expérimentale pour les systèmes de compilation C/C++ personnalisés a été ajoutée à Android Studio et à AGP. Cette fonctionnalité est disponible dans Android Studio Dolphin | 2021.3.1 Canary 4.

Présentation

L'un des modèles les plus courants pour les projets C/C++, en particulier ceux qui ciblent plusieurs plates-formes, consiste à générer des projets pour chacune de ces plates-formes à partir d'une représentation sous-jacente. CMake est un bon exemple de ce format. Il peut générer des projets pour Android, iOS et d'autres plates-formes à partir d'une seule représentation sous-jacente, enregistrée dans le fichier CMakeLists.txt.

CMake est directement compatible avec AGP, contrairement à d'autres générateurs de projet comme ceux ci-dessous :

Ces types de générateurs de projets peuvent soit prendre en charge Ninja en tant que représentation backend du build C/C++, soit être adaptés pour générer des fichiers Ninja en tant que représentations backend.

Lorsqu'il est configuré correctement, un projet AGP disposant d'un générateur de système de projet C/C++ intégré permet aux utilisateurs de :

  • compiler à partir de la ligne de commande et d'Android Studio ;

  • modifier les sources compatibles à l'aide de la fonctionnalité de service linguistique complète (par exemple, accès aux définitions) dans Android Studio ;

  • déboguer les processus natifs et mixtes à l'aide des débogueurs Android Studio.

Modifier votre build pour utiliser un script de configuration de compilation C/C++ personnalisé

Cette section explique comment utiliser un script de configuration de compilation C/C++ personnalisé à partir d'AGP.

Étape 1 : Modifiez le fichier build.gradle au niveau du module pour référencer un script de configuration

Pour activer la compatibilité avec Ninja dans AGP, configurez experimentalProperties dans le fichier build.gradle au niveau du module :

android {
  defaultConfig {
    externalNativeBuild {
      experimentalProperties["ninja.abiFilters"] = [ "x86", "arm64-v8a" ]
      experimentalProperties["ninja.path"] = "source-file-list.txt"
      experimentalProperties["ninja.configure"] = "configure-ninja"
      experimentalProperties["ninja.arguments"] = [
            "\${ndk.moduleMakeFile}",
            "--variant=\${ndk.variantName}",
            "--abi=Android-\${ndk.abi}",
            "--configuration-dir=\${ndk.configurationDir}",
            "--ndk-version=\${ndk.moduleNdkVersion}",
            "--min-sdk-version=\${ndk.minSdkVersion}"
       ]
     }
   }

Les propriétés sont interprétées par AGP comme suit :

  • ninja.abiFilters est une liste d'ABI à compiler. Les valeurs valides sont x86, x86-64, armeabi-v7a et arm64-v8a.

  • ninja.path est un chemin d'accès à un fichier de projet C/C++. Vous pouvez utiliser le format de votre choix. Les modifications apportées à ce fichier déclencheront une invite de synchronisation Gradle dans Android Studio.

  • ninja.configure est un chemin d'accès à un fichier de script qui sera exécuté par Gradle si nécessaire pour configurer le projet C/C++. Un projet est configuré lors de la première compilation, lors d'une synchronisation Gradle dans Android Studio ou lorsque l'une des entrées de script de configuration est modifiée.

  • ninja.arguments est une liste d'arguments qui sera transmise au script défini par ninja.configure. Les éléments de cette liste peuvent faire référence à un ensemble de macros dont les valeurs dépendent du contexte de configuration actuel dans AGP :

    • ${ndk.moduleMakeFile} est le chemin d'accès complet au fichier ninja.configure. Dans l'exemple, il s'agit de C:\path\to\configure-ninja.bat.

    • ${ndk.variantName} est le nom de la variante AGP actuelle en cours de compilation. Par exemple, variante de débogage ou de release.

    • ${ndk.abi} est le nom de l'ABI d'AGP actuelle en cours de compilation. Par exemple, x86 ou arm64-v8a.

    • ${ndk.buildRoot} est le nom d'un dossier, généré par AGP, dans lequel le script écrit sa sortie. Retrouvez tous les détails à l'étape 2 ("Créez le script de configuration").

    • ${ndk.ndkVersion} est la version de NDK à utiliser. Il s'agit généralement de la valeur transmise à android.ndkVersion dans le fichier build.gradle ou d'une valeur par défaut.

    • ${ndk.minPlatform} est la plate-forme Android cible minimale demandée par AGP.

  • ninja.targets est une liste des cibles Ninja spécifiques à compiler.

Étape 2 : Créez le script de configuration

La responsabilité minimale du script de configuration (configure-ninja.bat dans l'exemple précédent) consiste à générer un fichier build.ninja qui, une fois compilé avec Ninja, compilera et liera toutes les sorties natives du projet. Il s'agit généralement de fichiers .o pour "Object" (Objet), .a pour "Archive" et .so pour "Shared Object" (Objet partagé).

Le script de configuration peut écrire le fichier build.ninja à deux endroits différents selon vos besoins.

  • Si AGP peut choisir un emplacement, le script de configuration écrit build.ninja à l'emplacement défini dans la macro ${ndk.buildRoot}.

  • Si le script de configuration doit choisir l'emplacement du fichier build.ninja, il écrit également un fichier nommé build.ninja.txt à l'emplacement défini dans la macro ${ndk.buildRoot}. Ce fichier contient le chemin d'accès complet au fichier build.ninja écrit par le script de configuration.

Structure du fichier build.ninja

En général, la plupart des structures qui représentent précisément un build C/C++ Android fonctionnent. Les principaux éléments requis par AGP et Android Studio sont les suivants :

  • Liste des fichiers sources C/C++ avec les indicateurs nécessaires à Clang pour les compiler.

  • Liste des bibliothèques de sorties. Il s'agit généralement de fichiers .so (objet partagé), mais il peut également s'agir de fichiers .a (archive) ou exécutables (sans extension).

Pour des exemples de génération de fichier build.ninja, consultez la sortie de CMake lorsque le générateur build.ninja est utilisé.

Voici un exemple de modèle build.ninja minimal.

rule COMPILE
   command = /path/to/ndk/clang -c $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

build source.o : COMPILE source.cpp
build lib.so : LINK source.o

Bonnes pratiques

Outre les exigences (liste des fichiers sources et des bibliothèques de sortie), voici quelques bonnes pratiques recommandées.

Choisissez des sorties nommées avec des règles phony

Il est recommandé d'utiliser dès que possible des règles phony dans la structure build.ninja pour attribuer des noms lisibles aux résultats de build. Par exemple, si vous avez une sortie nommée c:/path/to/lib.so, vous pouvez lui attribuer un nom lisible comme :

build curl: phony /path/to/lib.so

L'avantage de cette méthode est que vous pouvez ensuite spécifier ce nom comme cible de build dans le fichier build.gradle. Par exemple :

android {
  defaultConfig {
    externalNativeBuild {
      ...
      experimentalProperties["ninja.targets"] = [ "curl" ]

Définissez une cible "all"

La cible all que vous définissez sera désignée par défaut pour les bibliothèques compilées par AGP si aucune cible n'est spécifiée explicitement dans le fichier build.gradle.

rule COMPILE
   command = /path/to/ndk/clang $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

build foo.o : COMPILE foo.cpp
build bar.o : COMPILE bar.cpp
build libfoo.so : LINK foo.o
build libbar.so : LINK bar.o
build all: phony libfoo.so libbar.so

Définissez une autre méthode de compilation (facultatif)

Pour une utilisation plus avancée, vous pouvez encapsuler un système de compilation existant qui n'est pas basé sur Ninja. Dans ce cas, vous devez toujours représenter toutes les sources avec leurs indicateurs, ainsi que les bibliothèques de sortie, afin qu'Android Studio puisse présenter les fonctionnalités de service linguistique adéquates, comme la saisie semi-automatique et l'accès aux définitions. Vous aurez toutefois besoin qu'AGP s'applique au système de compilation sous-jacent pendant la compilation.

Pour ce faire, vous pouvez utiliser une sortie de build Ninja avec une extension spécifique .passthrough.

Plus concrètement, supposons que vous souhaitiez encapsuler un MSBuild. Votre script de configuration génère normalement le fichier build.ninja, mais ajoute également une cible passthrough qui définit la manière dont AGP appelle MSBuild.

rule COMPILE
   command = /path/to/ndk/clang $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

rule MBSUILD_CURL
  command = /path/to/msbuild {flags to build curl with MSBuild}

build source.o : COMPILE source.cpp
build lib.so : LINK source.o
build curl : phony lib.so
build curl.passthrough : MBSUILD_CURL

Envoyer un commentaire

Cette fonctionnalité est encore en test, vos commentaires sont donc les bienvenus. Faites-nous part de vos commentaires via les canaux suivants :

  • Pour un retour global, ajoutez un commentaire à ce bug.

  • Pour signaler un bug, ouvrez Android Studio, puis cliquez sur Help > Submit Feedback (Aide > Envoyer des commentaires). Veillez à mentionner "Systèmes de compilation C/C++ personnalisés" pour faciliter la prise en charge du bug.

  • Si vous n'avez pas installé Android Studio, signalez le bug grâce à ce formulaire.