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 Android Studio wünschen, 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 unterstützen jetzt experimentell 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 ausgerichtet sind, besteht darin, Projekte für jede dieser Plattformen aus einer zugrunde liegenden Darstellung zu generieren. Ein prominentes 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 von AGP direkt unterstützt. Es gibt jedoch 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 richtiger Konfiguration ermöglicht ein AGP-Projekt mit einem integrierten C/C++-Projektsystemgenerator Nutzern Folgendes:

  • Builds über die Befehlszeile und Android Studio ausführen

  • Quellen mit vollständiger Sprachdienstunterstützung (z. B. „Zur Definition springen“) in Android Studio bearbeiten

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

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

In diesem Abschnitt wird beschrieben, wie Sie ein benutzerdefiniertes C/C++-Build-Konfigurationsskript aus AGP verwenden.

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

Wenn du die Ninja-Unterstützung in AGP aktivieren möchtest, konfiguriere 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 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 der Pfad zu einer C/C++-Projektdatei. Das Format dieser Datei kann beliebig sein. Änderungen an dieser Datei lösen eine Aufforderung zur Gradle-Synchronisierung in Android Studio aus.

  • ninja.configure ist der Pfad zu einer Scriptdatei, 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 bei einer Änderung einer der Eingaben des Konfigurationsscripts konfiguriert.

  • ninja.arguments ist eine Liste von Argumenten, die an das von ninja.configure definierte Script ü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 ninja.configure-Datei. Im 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 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 Script seine Ausgabe schreibt. Weitere Informationen dazu finden Sie unter Schritt 2: Konfigurationsskript erstellen.

    • ${ndk.ndkVersion} ist die zu verwendende NDK-Version. Dies ist normalerweise der Wert, der in der Datei build.gradle an „android.ndkVersion“ übergeben wird, oder ein Standardwert, falls keiner 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

Das Konfigurationsskript (configure-ninja.bat im vorherigen Beispiel) muss mindestens eine build.ninja-Datei generieren, die beim Erstellen mit Ninja alle nativen Ausgaben des Projekts kompiliert und verknüpft. In der Regel sind das .o- (Objekt), .a- (Archiv) und .so-Dateien (freigegebenes Objekt).

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

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

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

Struktur der build.ninja-Datei

Im Allgemeinen funktioniert die meisten Strukturen, die einen Android-C/C++-Build korrekt darstellen. Die wichtigsten Elemente, die für AGP und Android Studio erforderlich sind:

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

  • 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 zum Generieren einer build.ninja-Datei benötigen, können Sie sich die Ausgabe von CMake ansehen, wenn der build.ninja-Generator verwendet wird.

Hier 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

Nach Möglichkeit sollten für die build.ninja-Struktur phony-Regeln verwendet werden, um Build-Ausgaben visuell lesbare Namen zu geben. Wenn Sie beispielsweise eine Ausgabe namens c:/path/to/lib.so haben, können Sie ihr einen für Menschen lesbaren Namen geben.

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

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

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

„Alle“ als Ziel festlegen

Wenn Sie ein all-Ziel angeben, wird dies von AGP als Standardsatz von Bibliotheken erstellt, 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 erweiterter Anwendungsfall besteht darin, ein vorhandenes Build-System zu verpacken, das nicht auf Ninja basiert. In diesem Fall müssen Sie alle Quellen mit ihren Flags zusammen mit den Ausgabebibliotheken angeben, damit Android Studio die entsprechenden Sprachdienstfunktionen wie die automatische Vervollständigung und die Definition aufrufen kann. Sie möchten jedoch, dass AGP während des eigentlichen Builds auf das zugrunde liegende Buildsystem verweist.

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

Angenommen, Sie möchten ein MSBuild-Objekt einbinden. Ihr Konfigurationsskript generiert die build.ninja wie gewohnt, fügt aber 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 in der Testphase. Dein Feedback ist uns sehr wichtig. Sie können uns Feedback über die folgenden Kanäle geben:

  • Allgemeines Feedback kannst du in diesem Kommentar hinterlassen.

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

  • Wenn Sie Android Studio nicht installiert haben, können Sie einen Fehler mit dieser Vorlage melden.