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

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

Ad 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

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 significativo 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 direttamente supportati:

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

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

  • Crea dalla riga di comando e da Android Studio.

  • Modifica le fonti con il supporto completo dei servizi linguistici (ad esempio, la definizione di riferimento) in Android Studio.

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

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

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

Passaggio 1: modifica il file build.gradle a livello di modulo per fare riferimento a uno script di configurazione

Per attivare il supporto 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 è il percorso di un file di progetto C/C++. Il formato di questo file può essere qualsiasi cosa tu voglia. Le modifiche a questo file attiveranno una richiesta 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 cambia uno degli input dello script di configurazione.

  • 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. Nell'esempio sarebbe C:\path\to\configure-ninja.bat.

    • ${ndk.variantName} è il nome dell'attuale variante AGP in fase di creazione. Ad esempio, esegui il debug o la release.

    • ${ndk.abi} è il nome dell'attuale ABI ABI in fase di creazione. 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 saranno spiegati nel Passaggio 2: crea lo script di configurazione.

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

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

  • ninja.targets è un elenco di 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 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 AGP può scegliere una località, lo script di configurazione scrive build.ninja nella località 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 in modo accurato una build Android C/C++ è valida. 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 anche .a (archivio) o eseguibili (nessuna 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 di file di origine e librerie di output), di seguito sono riportate alcune best practice consigliate.

Dichiara gli output con nome con phony regole

Se possibile, è consigliabile che la struttura build.ninja utilizzi le regole phony per assegnare nomi leggibili agli output della build. 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 potrai specificare questo nome come destinazione nel file build.gradle. Ad esempio,

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

Specifica un target completo

Quando specifichi un target all, questo sarà l'insieme predefinito di librerie create da AGP quando nessun target è specificato esplicitamente 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

(Facoltativo) Specifica un metodo di compilazione alternativo

Un caso d'uso più avanzato è quello di includere un sistema di build esistente non basato su Ninja. In questo caso, devi comunque rappresentare tutte le origini con i relativi flag, oltre alle librerie di output, in modo che Android Studio possa presentare le funzionalità dei servizi linguistici corrette, come il completamento automatico e la definizione go-to. Tuttavia, vuoi che AGP si riferisca al sistema di build 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 un MSBuild. Lo script di configurazione genererà il build.ninja come di consueto, ma aggiungerebbe 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, quindi il tuo feedback è molto importante. 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 indirizzare il bug.

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