Cómo integrar sistemas de compilación de C/C++ personalizados con Ninja (experimental)

Si no usas CMake ni ndk-build, pero deseas una integración completa de la compilación de C/C++ del complemento de Android para Gradle (AGP) y Android Studio, puedes crear un sistema de compilación de C/C++ personalizado mediante la creación de una secuencia de comandos de shell que escriba la información de compilación en el formato de archivo de compilación Ninja.

Se agregó compatibilidad experimental con los sistemas de compilación de C/C++ personalizados a Android Studio y AGP. Esta función está disponible a partir de Android Studio Dolphin | 2021.3.1 Canary 4.

Descripción general

Un patrón común para los proyectos de C/C++, en especial aquellos que se orientan a varias plataformas, es generar proyectos para cada una de esas plataformas a partir de alguna representación subyacente. Un ejemplo destacado de este patrón es CMake. CMake puede generar proyectos para Android, iOS y otras plataformas desde una única representación subyacente que está guardada en el archivo CMakeLists.txt.

Si bien CMake es compatible directamente con AGP, hay otros generadores de proyectos disponibles que no se admiten de forma directa:

Estos tipos de generadores de proyectos admiten Ninja como una representación de backend de la compilación de C/C++ o pueden adaptarse para generar Ninja como representación de backend.

Si se configura de forma correcta, un proyecto de AGP con un generador de sistemas de proyecto de C/C++ integrado les permite a los usuarios hacer lo siguiente:

  • Compilar desde la línea de comandos y Android Studio

  • Editar las fuentes que son completamente compatibles con el servicio de lenguaje (por ejemplo, la función Ir a la definición) en Android Studio

  • Usar depuradores de Android Studio para procesos nativos y mixtos

Cómo modificar tu compilación para usar una secuencia de comandos de configuración de compilación de C/C++ personalizada

En esta sección, se explican los pasos para usar una secuencia de comandos de configuración de compilación de C/C++ personalizada desde AGP.

Paso 1: Modifica el archivo build.gradle a nivel del módulo para hacer referencia a una secuencia de comandos de configuración

Para habilitar la compatibilidad con Ninja en AGP, configura experimentalProperties en el archivo build.gradle a nivel del módulo:

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

El AGP interpreta las propiedades de la siguiente manera:

  • ninja.abiFilters es una lista de ABI para compilar. Los valores válidos son x86, x86-64, armeabi-v7a y arm64-v8a.

  • ninja.path es una ruta de acceso a un archivo del proyecto de C/C++. El formato de este archivo puede ser el que desees. Los cambios en este archivo activarán una solicitud para sincronizar Gradle en Android Studio.

  • ninja.configure es la ruta de acceso a un archivo de secuencia de comandos que Gradle ejecutará cuando sea necesario para configurar el proyecto de C/C++. Un proyecto se configura en la primera compilación, cuando se sincroniza Gradle en Android Studio o cuando se cambia una de las entradas de la secuencia de comandos de configuración.

  • ninja.arguments es una lista de argumentos que se pasará a la secuencia de comandos que define ninja.configure. Los elementos de esta lista pueden hacer referencia a un conjunto de macros cuyos valores dependen del contexto de configuración actual en AGP:

    • ${ndk.moduleMakeFile} es la ruta de acceso completa al archivo ninja.configure. En el ejemplo, sería C:\path\to\configure-ninja.bat.

    • ${ndk.variantName} es el nombre de la variante actual del AGP que se está compilando. Por ejemplo, depuraciones o lanzamientos.

    • ${ndk.abi} es el nombre de la ABI de AGP actual que se está compilando. Por ejemplo, x86 o arm64-v8a.

    • ${ndk.buildRoot} es el nombre de una carpeta, que genera AGP, en la que la secuencia de comandos escribe su resultado. Se explicarán los detalles en el Paso 2: Crea la secuencia de comandos de configuración.

    • ${ndk.ndkVersion} es la versión del NDK que se usará. Por lo general, este es el valor que se pasa a android.ndkVersion en el archivo build.gradle o un valor predeterminado si no hay ninguno.

    • ${ndk.minPlatform} es la plataforma de Android de destino mínima que solicita AGP.

  • ninja.targets es una lista de destinos específicos de Ninja que se deben compilar.

Paso 2: Crea la secuencia de comandos de configuración

La responsabilidad mínima de la secuencia de comandos de configuración (configure-ninja.bat en el ejemplo anterior) es generar un archivo build.ninja que, cuando se cree con Ninja, compile y vincule todos los resultados nativos del proyecto. Por lo general, estos son archivos .o (objeto), .a (archivo) y .so (objeto compartido).

La secuencia de comandos de configuración puede escribir el archivo build.ninja en dos lugares diferentes según tus necesidades.

  • Si está bien que AGP elija una ubicación, la secuencia de comandos de configuración escribe build.ninja en la ubicación establecida en la macro ${ndk.buildRoot}.

  • Si la secuencia de comandos de configuración necesita elegir la ubicación del archivo build.ninja, también escribe un archivo con el nombre build.ninja.txt en la ubicación establecida en la macro ${ndk.buildRoot}. Este archivo contiene la ruta de acceso completa al archivo build.ninja que escribió la secuencia de comandos de configuración.

Estructura del archivo build.ninja

En general, funcionará la mayoría de las estructuras que representan con exactitud una compilación de C/C++ de Android. Los elementos clave que necesitan AGP y Android Studio son los siguientes:

  • La lista de archivos de origen de C/C++ junto con las marcas que necesita Clang para compilarlos.

  • La lista de bibliotecas de resultados. Por lo general, son archivos .so (objeto compartido), pero también pueden ser .a (archivo) o ejecutables (sin extensión).

Si necesitas ejemplos para generar un archivo build.ninja, puedes ver el resultado de CMake cuando se usa el generador build.ninja.

A continuación, se muestra el ejemplo de una plantilla build.ninja mínima.

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

Prácticas recomendadas

Además de los requisitos (lista de archivos de origen y bibliotecas de resultados), se mencionan algunas prácticas recomendadas.

Declara resultados con nombre con reglas phony

Cuando sea posible, se recomienda que la estructura build.ninja use reglas phony para asignarles nombres legibles a los resultados de compilación. Por ejemplo, si tienes un resultado con el nombre c:/path/to/lib.so, puedes asignarle un nombre legible de la siguiente manera.

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

El beneficio de hacerlo es que, luego, puedes especificar este nombre como un destino de compilación en el archivo build.gradle. Por ejemplo:

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

Especifica un destino "all"

Cuando especifiques un destino all, este será el conjunto predeterminado de bibliotecas que compila AGP cuando no se especifican destinos de forma explícita en el archivo 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

Especifica un método de compilación alternativo (opcional)

Un caso de uso más avanzado es unir un sistema de compilación existente que no se base en Ninja. En este caso, deberás representar todas las fuentes con sus marcas junto con las bibliotecas de destino para que Android Studio pueda presentar funciones correctas del servicio de lenguaje, como Ir a la definición y Autocompletar. Sin embargo, te gustaría que AGP defiera al sistema de compilación subyacente durante la compilación real.

Para ello, puedes usar un resultado de compilación de Ninja con una extensión específica .passthrough.

Para dar un ejemplo más concreto, supongamos que deseas unir MSBuild. Tu secuencia de comandos de configuración generaría build.ninja como siempre, pero también agregaría un destino de transferencia que define cómo AGP invocará 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

Cómo enviar comentarios

Esta función es experimental, por lo que apreciamos mucho los comentarios. Puedes enviar comentarios a través de los siguientes canales:

  • Para enviar comentarios generales, agrega un comentario a este error.

  • Para informar un error, abre Android Studio y haz clic en Help > Submit Feedback. Asegúrate de hacer referencia a "Sistemas de compilación de C/C++ personalizados" para ayudar a corregir el error.

  • Para informar un error si no tienes Android Studio instalado, envía un informe de error con esta plantilla.