Integration benutzerdefinierter C/C++-Build-Systeme mit Ninja (experimentell)

Wenn Sie CMake oder ndk-build nicht verwenden, aber den C/C++-Build des Android-Gradle-Plug-ins (AGP) und Android Studio vollständig integrieren möchten, können Sie ein benutzerdefiniertes C/C++-Build-System erstellen. Erstellen Sie dazu ein Shell-Script, das Build-Informationen im Build-Dateiformat Ninja schreibt.

Android Studio und AGP wurden experimentelle Unterstützung für benutzerdefinierte C/C++-Build-Systeme hinzugefügt. Diese Funktion ist ab Android Studio Dolphin | 2021.3.1 Canary 4 verfügbar.

Übersicht

Ein gängiges Muster für C/C++-Projekte, insbesondere solche, die auf mehrere Plattformen abzielen, besteht darin, Projekte für jede dieser Plattformen aus einer zugrunde liegenden Darstellung zu generieren. Ein auffälliges Beispiel für dieses Muster ist CMake. CMake kann Projekte für Android, iOS und andere Plattformen aus einer einzigen zugrunde liegenden Darstellung generieren, die in der Datei CMakeLists.txt gespeichert ist.

CMake wird zwar direkt von AGP unterstützt, es gibt jedoch andere Projektgeneratoren, die nicht direkt unterstützt werden:

Diese Arten von Projektgeneratoren unterstützen entweder Ninja als Back-End-Darstellung des C/C++-Builds oder können so angepasst werden, dass Ninja als Back-End-Darstellung generiert wird.

Bei korrekter Konfiguration bietet ein AGP-Projekt mit einem integrierten C/C++-Projektsystemgenerator folgende Möglichkeiten:

  • Mit der Befehlszeile und Android Studio entwickeln

  • Sie können Quellen mit vollständiger Sprachunterstützung in Android Studio bearbeiten, z. B. Go-to-Definition.

  • Verwenden Sie Android Studio-Debugger, um Fehler in nativen und gemischten Prozessen zu beheben.

Build für die Verwendung eines benutzerdefinierten C/C++-Build-Konfigurationsskripts ändern

In diesem Abschnitt werden die Schritte zur Verwendung eines benutzerdefinierten C/C++-Build-Konfigurationsskripts von AGP beschrieben.

Schritt 1: Datei build.gradle auf Modulebene ändern, um auf ein Konfigurationsskript zu verweisen

Um die Ninja-Unterstützung in AGP zu aktivieren, konfigurieren Sie experimentalProperties in der Datei build.gradle auf Modulebene:

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}"
       ]
     }
   }

Die Attribute werden von AGP so interpretiert:

  • ninja.abiFilters ist eine Liste von ABIs, die erstellt werden sollen. Gültige Werte sind: x86, x86-64, armeabi-v7a und arm64-v8a.

  • ninja.path ist ein Pfad zu einer C/C++-Projektdatei. Sie haben ein beliebiges Format für diese Datei. Änderungen an dieser Datei lösen eine Eingabeaufforderung zur Gradle-Synchronisierung in Android Studio aus.

  • ninja.configure ist ein Pfad zu einer Skriptdatei, die von Gradle ausgeführt wird, wenn das C/C++-Projekt konfiguriert werden muss. Ein Projekt wird beim ersten Build, während einer Gradle-Synchronisierung in Android Studio oder wenn sich eine der Konfigurationsskripteingaben ändert.

  • ninja.arguments ist eine Liste von Argumenten, die an das von „ninja.configure“ definierte Skript übergeben werden. Elemente in dieser Liste können auf eine Reihe von Makros verweisen, deren Werte vom aktuellen Konfigurationskontext in AGP abhängen:

    • ${ndk.moduleMakeFile} ist der vollständige Pfad zur Datei ninja.configure. In dem Beispiel wäre das also C:\path\to\configure-ninja.bat.

    • ${ndk.variantName} ist der Name der aktuellen AGP-Variante, die gerade erstellt wird. Beispiel: „Debug“ oder „Release“.

    • ${ndk.abi} ist der Name der aktuellen AGP-ABIs, die gerade erstellt wird. Zum Beispiel: x86 oder arm64-v8a.

    • ${ndk.buildRoot} ist der Name eines von AGP generierten Ordners, in den das Skript seine Ausgabe schreibt. Weitere Informationen dazu finden Sie in Schritt 2: Konfigurationsskript erstellen.

    • ${ndk.ndkVersion} ist die Version des zu verwendenden NDK. Das ist in der Regel der Wert, der in der Datei build.gradle an android.ndkVersion übergeben wird, oder ein Standardwert, wenn keiner vorhanden ist.

    • ${ndk.minPlatform} ist die von AGP angeforderte Mindestzielplattform für Android.

  • ninja.targets ist eine Liste der spezifischen Ninja-Ziele, die erstellt werden sollen.

Schritt 2: Konfigurationsskript erstellen

Die Mindestverantwortung des Konfigurationsskripts (im vorherigen Beispiel configure-ninja.bat) besteht darin, eine build.ninja-Datei zu generieren, die, wenn sie mit Ninja erstellt wird, alle nativen Ausgaben des Projekts kompiliert und verknüpft. In der Regel sind dies .o (Objekt), .a (Archiv) und .so (Freigegebene Objekte).

Das Konfigurationsskript kann die build.ninja-Datei je nach Ihren Anforderungen an zwei verschiedene Stellen schreiben.

  • Wenn AGP einen Speicherort auswählen darf, schreibt das Konfigurationsskript build.ninja an den im ${ndk.buildRoot}-Makro festgelegten Speicherort.

  • Wenn das Konfigurationsskript den Speicherort der Datei build.ninja auswählen muss, schreibt es auch eine Datei namens build.ninja.txt an den im ${ndk.buildRoot}-Makro festgelegten Speicherort. Diese Datei enthält den vollständigen Pfad zur Datei build.ninja, die vom Konfigurationsskript geschrieben wurde.

Struktur der Datei build.ninja

Im Allgemeinen funktionieren die meisten Strukturen, die einen Android-C/C++-Build korrekt darstellen. Die wichtigsten Elemente, die für AGP und Android Studio benötigt werden, sind:

  • Die Liste der C/C++-Quelldateien zusammen mit den Flags, die Clang zum Kompilieren benötigt.

  • Die Liste der Ausgabebibliotheken. Dabei handelt es sich in der Regel um .so-Dateien (freigegebene Objekte), es kann sich aber auch um .a-Dateien (Archive) oder ausführbare Dateien (ohne Erweiterung) handeln.

Beispiele zum Generieren einer build.ninja-Datei finden Sie in der Ausgabe von CMake, wenn der build.ninja-Generator verwendet wird.

Hier ist ein Beispiel für eine minimale build.ninja-Vorlage.

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

Best Practices

Zusätzlich zu den Anforderungen (Liste der Quelldateien und Ausgabebibliotheken) finden Sie hier einige empfohlene Best Practices.

Benannte Ausgaben mit phony-Regeln deklarieren

Wenn möglich, empfiehlt es sich, in der build.ninja-Struktur phony-Regeln zu verwenden, um Build-Ausgaben in menschenlesbaren Namen zu benennen. Wenn Sie beispielsweise eine Ausgabe mit dem Namen c:/path/to/lib.so haben, können Sie ihr einen visuell lesbaren Namen geben:

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

Der Vorteil dabei ist, dass Sie diesen Namen als Build-Ziel in der Datei build.gradle angeben können. Beispiel:

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

Ziel "Alle" angeben

Wenn Sie ein all-Ziel angeben, werden dies der Standardsatz von Bibliotheken, die von AGP erstellt werden, wenn in der build.gradle-Datei keine Ziele explizit angegeben sind.

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

Alternative Build-Methode angeben (optional)

Ein komplexerer Anwendungsfall ist das Einbinden eines vorhandenen Build-Systems, das nicht auf Ninja basiert. In diesem Fall müssen Sie trotzdem alle Quellen mit ihren Flags zusammen mit den Ausgabebibliotheken darstellen, damit Android Studio die richtigen Sprachdienstfunktionen wie automatische Vervollständigung und Go-to-Definition anzeigen kann. Sie möchten jedoch, dass AGP während des eigentlichen Builds auf das zugrunde liegende Build-System zurückgreift.

Um dies zu erreichen, können Sie eine Ninja-Build-Ausgabe mit der spezifischen Erweiterung .passthrough verwenden.

Ein konkreteres Beispiel: Nehmen wir an, Sie möchten einen MSBuild verpacken. Ihr Konfigurationsskript würde wie gewohnt den build.ninja generieren, fügt jedoch auch ein Passthrough-Ziel hinzu, das definiert, wie AGP MSBuild aufruft.

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

Feedback geben

Diese Funktion befindet sich noch in der Testphase. Daher würden wir uns über Feedback freuen. Sie können über die folgenden Kanäle Feedback geben: