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

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

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

Panoramica

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

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

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

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

  • Esegui la compilazione dalla riga di comando e da Android Studio.

  • Modifica le origini con il supporto completo del servizio linguistico (ad esempio, la definizione di destinazione) in Android Studio.

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

Come modificare la compilazione per utilizzare uno script di configurazione di compilazione C/C++ personalizzato

Questa sezione illustra i passaggi per utilizzare uno script di configurazione di compilazione 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à vengono 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 è il percorso di un file di progetto C/C++. Il formato di questo file può essere qualsiasi. Le modifiche a questo file attiveranno una richiesta di sincronizzazione di Gradle in Android Studio.

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

  • ninja.arguments è un elenco di argomenti che verranno passati allo script definito da ninja.configure. Gli elementi di 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. Quindi nell'esempio sarà C:\path\to\configure-ninja.bat.

    • ${ndk.variantName} è il nome della variante AGP corrente in fase di compilazione. Ad esempio, debug o release.

    • ${ndk.abi} è il nome dell'ABI AGP corrente in fase di compilazione. Ad esempio, x86 o arm64-v8a.

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

    • ${ndk.ndkVersion} è la versione dell'NDK da utilizzare. Di solito è il valore passato ad android.ndkVersion nel file build.gradle o un valore predefinito se non è presente.

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

  • ninja.targets è un elenco dei target Ninja specifici da creare.

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, se compilato con Ninja, compilerà e collegherà tutti gli output nativi del progetto. Di solito si tratta di file .o (Object), .a (Archive) e .so (Shared Object).

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

  • Se è consentito all'AGP scegliere una posizione, 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 compilazione C/C++ per Android funzionerà. Gli elementi chiave necessari per AGP e Android Studio sono:

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

  • L'elenco delle librerie di output. In genere si tratta di file .so (oggetti condivisi), ma possono essere anche .a (archivi) o eseguibili (senza estensione).

Se hai bisogno di esempi su 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 dei file di origine e delle librerie di output), di seguito sono riportate alcune best practice consigliate.

Dichiarare output denominati con regole phony

Se possibile, è consigliabile che la struttura build.ninja utilizzi le regole phony per assegnare nomi leggibili agli output della compilazione. 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 target di compilazione nel file build.gradle. Ad esempio,

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

Specificare un target "tutti"

Quando specifichi un target all, questo sarà l'insieme predefinito di librerie compilate da AGP quando non vengono specificati target nel file all.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 compilazione alternativo (facoltativo)

Un caso d'uso più avanzato è quello di eseguire il wrapping di un sistema di compilazione 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à appropriate del servizio linguistico, come il completamento automatico e la definizione di vai a. Tuttavia, vuoi che AGP rimandi al sistema di compilazione sottostante durante la compilazione effettiva.

Per farlo, puoi utilizzare l'output di una compilazione Ninja con un'estensione specifica .passthrough.

Come esempio più concreto, supponiamo che tu voglia eseguire il wrapping di un file MSBuild. Lo script di configurazione genererà il file build.ninja come di consueto, ma aggiungerà anche un target passthrough che definisce in che modo AGP chiamerà 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

Invia feedback

Questa funzionalità è sperimentale, pertanto il tuo feedback è molto apprezzato. Puoi inviare un 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 "Custom C/C++ Build Systems" per indirizzare il bug.

  • Per segnalare un bug se non hai installato Android Studio, utilizza questo modello.