Integrazione di sistemi di build C/C++ personalizzati utilizzando Ninja (sperimentale)

Se non utilizzi CMake o ndk-build, ma vuoi l'integrazione completa della build C/C++ del plug-in Android Gradle (AGP) e Android Studio, puoi creare un sistema di compilazione C/C++ personalizzato creando uno script shell che scrive le informazioni sulla build nel formato file di build Ninja.

In Android Studio e AGP è stato aggiunto il supporto sperimentale per i sistemi di build C/C++ personalizzati. Questa funzionalità è disponibile a partire da Android Studio Dolphin | 2021.3.1 Canary 4.

Panoramica

Un modello comune per i progetti C/C++, in particolare quelli che hanno come target più piattaforme, è la generazione di progetti per ciascuna di queste piattaforme da una rappresentazione sottostante. Un esempio importante di questo pattern è CMake. CMake può generare progetti per Android, iOS e altre piattaforme da un'unica rappresentazione sottostante, salvata nel file CMakeLists.txt.

Sebbene CMake sia supportato direttamente da AGP, sono disponibili altri generatori di progetti che non sono supportati direttamente:

Questi tipi di generatori di progetti supportano Ninja come rappresentazione backend della build C/C++ o possono essere adattati per generare Ninja come rappresentazione backend.

Se configurato correttamente, un progetto AGP con un generatore di sistema di progetti C/C++ integrato consente agli utenti di:

  • Crea dalla riga di comando e da Android Studio.

  • Modifica le origini con supporto completo dei servizi linguistici (ad esempio la definizione di punto accesso) in Android Studio.

  • Utilizza i debugger di Android Studio per eseguire il debug di processi nativi e misti.

di Gemini Advanced.

Modificare la build per utilizzare uno script di configurazione di build C/C++ personalizzato

Questa sezione illustra i passaggi per utilizzare uno script di configurazione di build C/C++ personalizzato da AGP.

Passaggio 1: modifica il file build.gradle a livello di modulo in modo che faccia riferimento a uno script di configurazione

Per attivare il supporto di Ninja in AGP, configura experimentalProperties nel file build.gradle a livello di modulo:

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

Le proprietà sono interpretate da AGP come segue:

  • ninja.abiFilters è un elenco di ABI da creare. I valori validi sono: x86, x86-64, armeabi-v7a e arm64-v8a.

  • ninja.path è un percorso di file di progetto C/C++. Il formato di questo file può essere qualsiasi cosa tu voglia. Le modifiche a questo file attiveranno un prompt di sincronizzazione Gradle in Android Studio.

  • ninja.configure è un percorso di un file di script che verrà eseguito da Gradle quando sarà necessario configurare il progetto C/C++. Un progetto viene configurato nella prima build, durante una sincronizzazione Gradle in Android Studio o quando uno degli input di configurazione dello script cambia.

  • ninja.arguments è un elenco di argomenti che verranno passati allo script definito da ninja.configure. Gli elementi in questo elenco possono fare riferimento a un insieme di macro i cui valori dipendono dal contesto di configurazione corrente in AGP:

    • ${ndk.moduleMakeFile} è il percorso completo del file ninja.configure. Nell'esempio sarebbe C:\path\to\configure-ninja.bat.

    • ${ndk.variantName} è il nome della variante AGP corrente che viene creata. Ad esempio, debug o release.

    • ${ndk.abi} è il nome dell'ABI AGP attuale che viene creata. Ad esempio, x86 o arm64-v8a.

    di Gemini Advanced.
    • ${ndk.buildRoot} è il nome di una cartella, generata da AGP, in cui lo script scrive l'output. I dettagli di questa operazione saranno spiegati nel Passaggio 2: crea lo script di configurazione.

    • ${ndk.ndkVersion} è la versione dell'NDK da utilizzare. Solitamente si tratta del valore trasmesso ad android.ndkVersion nel file build.gradle o di un valore predefinito se non ne è presente nessuno.

    • ${ndk.minPlatform} è la piattaforma Android minima richiesta da AGP.

  • ninja.targets è un elenco di target ninja specifici che dovrebbero essere costruiti.

Passaggio 2: crea lo script di configurazione

La responsabilità minima dello script di configurazione (configure-ninja.bat nell'esempio precedente) è generare un file build.ninja che, una volta creato con Ninja, compilerà e collegherà tutti gli output nativi del progetto. Di solito si tratta di file .o (oggetto), .a (archivio) e .so (oggetto condiviso).

Lo script di configurazione può scrivere il file build.ninja in due posizioni diverse a seconda delle tue esigenze.

  • Se può essere scelto da AGP, lo script di configurazione scrive build.ninja nella posizione impostata nella macro ${ndk.buildRoot}.

  • Se lo script di configurazione deve scegliere la posizione del file build.ninja, scrive anche un file denominato build.ninja.txt nella posizione impostata nella macro ${ndk.buildRoot}. Questo file contiene il percorso completo del file build.ninja scritto dallo script di configurazione.

Struttura del file build.ninja

In genere, la maggior parte delle strutture che rappresentano con precisione una build Android C/C++ funzionerà. Gli elementi chiave richiesti da AGP e Android Studio sono:

  • L'elenco dei file sorgente C/C++ insieme ai flag necessari a Clang per compilarli.

  • L'elenco delle librerie di output. In genere si tratta di file .so (oggetto condiviso), ma possono anche essere .a (archivio) o eseguibili (nessuna estensione).

Se hai bisogno di esempi di come generare un file build.ninja, puoi esaminare l'output di CMake quando viene utilizzato il generatore build.ninja.

Ecco un esempio di modello build.ninja minimo.

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 practice

Oltre ai requisiti (elenco di file di origine e librerie di output), ecco alcune best practice consigliate.

Dichiara gli output denominati con phony regole

Se possibile, si consiglia alla struttura build.ninja di utilizzare le regole phony per assegnare agli output della build nomi leggibili da una persona. Quindi, ad esempio, se hai un output denominato c:/path/to/lib.so, puoi assegnargli un nome leggibile come segue.

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

Il vantaggio di questa operazione è che puoi specificare questo nome come destinazione di build nel file build.gradle. Ad esempio,

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

Specifica un'opzione "Tutte" destinazione

Se specifichi un target all, questo sarà l'insieme predefinito di librerie create da AGP quando non viene specificata esplicitamente nessuna destinazione nel file 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

Specifica un metodo di build alternativo (facoltativo)

Un caso d'uso più avanzato è l'aggregazione di un sistema di build esistente non basato su ninja. In questo caso, devi comunque rappresentare tutte le origini con i relativi flag insieme alle librerie di output, in modo che Android Studio possa presentare le funzionalità del servizio linguistico appropriate come il completamento automatico e la definizione di punto di accesso. Tuttavia, vorresti che AGP possa rimandare al sistema di compilazione sottostante durante la build effettiva.

A questo scopo, puoi utilizzare un output di build Ninja con un'estensione specifica .passthrough.

Come esempio più concreto, supponiamo che tu voglia eseguire il wrapping di MSBuild. Lo script di configurazione genererebbe build.ninja come al solito, ma aggiungerà anche una destinazione passthrough che definisce il modo in cui AGP richiama 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

Fornisci feedback

Questa funzionalità è sperimentale, pertanto il tuo feedback è molto apprezzato. Puoi inviare feedback tramite i seguenti canali:

  • Per un feedback generale, aggiungi un commento a questo bug.

  • Per segnalare un bug, apri Android Studio e fai clic su Guida > Invia feedback. Assicurati di fare riferimento a "Sistemi di build C/C++ personalizzati" per aiutare a individuare il bug.

  • Per segnalare un bug se non hai installato Android Studio, segnala il bug utilizzando questo modello.