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 des Android-Gradle-Plug-ins (AGP) und von Android Studio wünschen, können Sie ein benutzerdefiniertes C/C++-Build-System erstellen. Dazu erstellen Sie ein Shell-Script, das Build-Informationen im Ninja-Build-Dateiformat schreibt.

Android Studio und AGP bieten jetzt experimentelle Unterstützung für benutzerdefinierte C/C++-Build-Systeme. 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 für solche, die auf mehrere Plattformen abzielen, besteht darin, Projekte für jede dieser Plattformen aus einer zugrunde liegenden Darstellung zu generieren. Ein bekanntes 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 vom AGP unterstützt, es gibt jedoch auch andere Projektgeneratoren, die nicht direkt unterstützt werden:

Diese Arten von 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 Nutzern Folgendes:

  • Build über die Befehlszeile und Android Studio erstellen

  • Quellen mit vollständiger Unterstützung für Sprachdienstfunktionen (z. B. „Gehe zu Definition“) in Android Studio bearbeiten

  • Verwenden Sie Android Studio-Debugger, um native und gemischte Prozesse zu debuggen.

Build so ändern, dass ein benutzerdefiniertes C/C++-Build-Konfigurationsskript verwendet wird

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

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

Wenn Sie die Ninja-Unterstützung im AGP aktivieren möchten, 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 Eigenschaften werden vom 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 der Pfad zu einer C/C++-Projektdatei. Das Format dieser Datei kann beliebig sein. Änderungen an dieser Datei lösen in Android Studio eine Aufforderung zur Gradle-Synchronisierung 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 Eingaben des Konfigurationsskripts ändert, konfiguriert.

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

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

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

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

    • ${ndk.buildRoot} ist der Name eines vom AGP generierten Ordners, in den das Script seine Ausgabe schreibt. Details dazu werden in Schritt 2: Konfigurationsskript erstellen erläutert.

    • ${ndk.ndkVersion} ist die zu verwendende NDK-Version. Dies ist in der Regel der Wert, der in der Datei build.gradle an android.ndkVersion übergeben wird, oder ein Standardwert, falls kein Wert vorhanden ist.

    • ${ndk.minPlatform} ist die von AGP angeforderte Mindest-Android-Zielplattform.

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

Schritt 2: Konfigurationsskript erstellen

Die Mindestaufgabe des Konfigurationsskripts (configure-ninja.bat im vorherigen Beispiel) besteht darin, eine build.ninja-Datei zu generieren, die beim Erstellen mit Ninja alle nativen Ausgaben des Projekts kompiliert und verknüpft. In der Regel sind dies .o- (Objekt), .a- (Archiv) und .so-Dateien (freigegebenes Objekt).

Das Konfigurationsskript kann die Datei build.ninja je nach Bedarf an zwei verschiedenen Orten schreiben.

  • Wenn es in Ordnung ist, dass das AGP einen Speicherort auswählt, schreibt das Konfigurationsskript build.ninja an den im Makro ${ndk.buildRoot} festgelegten Speicherort.

  • Wenn das Konfigurationsskript den Speicherort der Datei build.ninja auswählen muss, wird auch eine Datei mit dem Namen build.ninja.txt an den im Makro ${ndk.buildRoot} festgelegten Speicherort geschrieben. 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 das AGP und Android Studio erforderlich sind, sind:

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

  • Die Liste der Ausgabebibliotheken. In der Regel sind dies .so-Dateien (Shared Object), es können aber auch .a-Dateien (Archiv) oder ausführbare Dateien (ohne Erweiterung) sein.

Wenn Sie Beispiele für die Erstellung einer build.ninja-Datei benötigen, können Sie sich die Ausgabe von CMake ansehen, 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

Empfehlungen

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

Benannte Ausgaben mit phony-Regeln deklarieren

Wenn möglich, sollte die build.ninja-Struktur phony-Regeln verwenden, um Build-Ausgaben menschenlesbare Namen zu geben. Wenn Sie beispielsweise eine Ausgabe mit dem Namen c:/path/to/lib.so haben, können Sie ihr so einen menschenlesbaren Namen geben.

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

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

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

Ziel „all“ angeben

Wenn Sie ein all-Ziel angeben, ist 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 erweiterter Anwendungsfall ist das Wrapping eines vorhandenen Build-Systems, das nicht auf Ninja basiert. In diesem Fall müssen Sie weiterhin alle Quellen mit ihren Flags zusammen mit den Ausgabebibliotheken darstellen, damit Android Studio die richtigen Sprachdienstfunktionen wie die automatische Vervollständigung und die Definition „Gehe zu“ anzeigen kann. Sie möchten jedoch, dass das AGP während des eigentlichen Builds auf das zugrunde liegende Build-System zurückgreift.

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

Ein konkreteres Beispiel: Wenn Sie ein MSBuild-Skript einbinden möchten, generiert Ihr Konfigurationsskript wie gewohnt die build.ninja, fügt aber auch ein Passthrough-Ziel hinzu, das definiert, wie das 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 freuen uns daher über Feedback. Sie können Feedback über die folgenden Kanäle geben:

  • Wenn Sie allgemeines Feedback geben möchten, fügen Sie dem entsprechenden Fehler einen Kommentar hinzu.

  • Wenn Sie einen Fehler melden möchten, öffnen Sie Android Studio und klicken Sie auf Hilfe > Feedback geben. Verweisen Sie auf „Benutzerdefinierte C/C++-Build-Systeme“, um den Fehler zu melden.

  • Wenn Sie Android Studio nicht installiert haben, können Sie einen Fehler über die Fehlervorlage melden.