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

Wenn Sie CMake oder ndk-build nicht verwenden, aber eine vollständige Integration des C/C++-Builds von Android-Gradle-Plug-ins (AGP) und Android Studio wünschen, können Sie ein benutzerdefiniertes C/C++-Build-System erstellen. Erstellen Sie dazu ein Shell-Skript, das Build-Informationen im Ninja-Build-Dateiformat 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 | verfügbar 2021.3.1 Canary 4.

Übersicht

Bei C/C++-Projekten, insbesondere solchen, die auf mehrere Plattformen abzielen, besteht ein gängiges Muster darin, für jede dieser Plattformen Projekte 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 einzelnen zugrunde liegenden Darstellung generieren, die in der Datei CMakeLists.txt gespeichert ist.

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

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

Bei korrekter Konfiguration ermöglicht ein AGP-Projekt mit einem integrierten C/C++-Projektsystemgenerator den Nutzern Folgendes:

  • Sie können Apps über die Befehlszeile und Android Studio erstellen.

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

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

Build ändern, um ein benutzerdefiniertes Build-Konfigurationsskript in C/C++ zu verwenden

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

Schritt 1: Datei build.gradle auf Modulebene so ändern, dass sie auf ein Konfigurationsskript verweist

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

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 Properties werden von AGP so interpretiert:

  • ninja.abiFilters ist eine Liste der zu erstellenden ABIs. Gültige Werte sind: x86, x86-64, armeabi-v7a und arm64-v8a.

  • ninja.path ist ein Pfad zu einer C/C++-Projektdatei. Das Format dieser Datei kann beliebig sein. Bei Änderungen an dieser Datei wird in Android Studio eine Aufforderung zur Gradle-Synchronisierung ausgelöst.

  • 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 im ersten Build, bei einer Gradle-Synchronisierung in Android Studio oder wenn sich eine der Konfigurationsskripts ändert.

  • ninja.arguments ist eine Liste der Argumente, 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. Im Beispiel wäre das C:\path\to\configure-ninja.bat.

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

    • ${ndk.abi} ist der Name des aktuellen AGP ABI, das 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. Einzelheiten dazu finden Sie in Schritt 2: Konfigurationsskript erstellen.

    • ${ndk.ndkVersion} ist die Version des zu verwendenden NDK. Dies ist normalerweise der Wert, der an android.ndkVersion in der Datei build.gradle ü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 bei der Erstellung mit Ninja alle nativen Ausgaben des Projekts kompiliert und verknüpft. In der Regel sind dies .o-Dateien (Objekt), .a-Dateien (Archiv) und .so-freigegebene Objekte.

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

  • Wenn AGP zur Auswahl eines Standorts berechtigt ist, schreibt das Konfigurationsskript build.ninja an dem im ${ndk.buildRoot}-Makro festgelegten Standort.

  • Wenn das Konfigurationsskript den Speicherort der Datei build.ninja auswählen muss, schreibt es auch eine Datei mit dem Namen build.ninja.txt an dem 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 abbilden. Die wichtigsten Elemente, die von AGP und Android Studio benötigt werden:

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

  • Die Liste der Ausgabebibliotheken. Dies sind in der Regel .so-Dateien (freigegebene Objekte), es kann sich aber auch um .a-Dateien (archivierte Dateien) 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, für die build.ninja-Struktur phony-Regeln zu verwenden, damit Build-Ausgaben visuell lesbar sind. Wenn Sie beispielsweise eine Ausgabe namens c:/path/to/lib.so haben, können Sie ihr einen visuell lesbaren Namen geben.

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

Dies hat den Vorteil, dass Sie diesen Namen dann als Build-Ziel in der Datei build.gradle angeben können. Beispiel:

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

Legen Sie „alle“ fest. Ziel

Wenn Sie ein all-Ziel angeben, wird dies der Standardsatz von Bibliotheken, die von AGP erstellt werden, wenn in der Datei build.gradle 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 Verpacken eines bestehenden Build-Systems, das nicht auf Ninja-Basis 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 die automatische Vervollständigung und die Go-to-Definition bereitstellen kann. Allerdings soll AGP während des eigentlichen Builds auf das zugrunde liegende Build-System zurückgreifen.

Dazu können Sie eine Ninja-Build-Ausgabe mit der Erweiterung .passthrough verwenden.

Ein konkreteres Beispiel nehmen wir an, Sie möchten einen MSBuild verpacken. Ihr Konfigurationsskript würde die build.ninja wie gewohnt generieren, aber es würde auch ein Passthrough-Ziel hinzufügen, 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. Wir würden uns daher über Ihr Feedback freuen. Sie können über die folgenden Kanäle Feedback geben:

  • Wenn du allgemeines Feedback benötigst, kannst du diesen Fehler kommentieren.

  • Wenn du einen Fehler melden möchtest, öffne Android Studio und klicke auf Hilfe > Feedback geben. Denken Sie daran: „Benutzerdefinierte C/C++ Build-Systeme“. um den Fehler zu beheben.

  • Wenn Sie einen Fehler melden möchten, wenn Sie Android Studio nicht installiert haben, können Sie ihn mithilfe dieser Vorlage melden.