Cómo usar el kit de desarrollo nativo de Android con Bazel

Si eres nuevo en Bazel, comienza con el instructivo Compila Android con Bazel.

Descripción general

Bazel se puede ejecutar en muchas configuraciones de compilación diferentes, incluidas varias que usan la cadena de herramientas del Kit de desarrollo nativo (NDK) de Android. Esto significa que las reglas normales cc_library y cc_binary se pueden compilar para Android directamente en Bazel. Bazel lo logra con la regla de repositorio android_ndk_repository.

Requisitos previos

Asegúrate de haber instalado el SDK y el NDK de Android.

Para configurar el SDK y el NDK, agrega el siguiente fragmento a tu WORKSPACE:

android_sdk_repository(
    name = "androidsdk", # Required. Name *must* be "androidsdk".
    path = "/path/to/sdk", # Optional. Can be omitted if `ANDROID_HOME` environment variable is set.
)

android_ndk_repository(
    name = "androidndk", # Required. Name *must* be "androidndk".
    path = "/path/to/ndk", # Optional. Can be omitted if `ANDROID_NDK_HOME` environment variable is set.
)

Para obtener más información sobre la regla android_ndk_repository, consulta la entrada de Build Encyclopedia.

Si usas una versión reciente del NDK de Android (r22 y versiones posteriores), usa la implementación de Starlark de android_ndk_repository. Sigue las instrucciones de su README.

Inicio rápido

Para compilar C++ para Android, simplemente agrega cc_library dependencias a tus android_binary o android_library reglas.

Por ejemplo, dado el siguiente archivo BUILD para una app para Android:

# In <project>/app/src/main/BUILD.bazel

cc_library(
    name = "jni_lib",
    srcs = ["cpp/native-lib.cpp"],
)

android_library(
    name = "lib",
    srcs = ["java/com/example/android/bazel/MainActivity.java"],
    resource_files = glob(["res/**/*"]),
    custom_package = "com.example.android.bazel",
    manifest = "LibraryManifest.xml",
    deps = [":jni_lib"],
)

android_binary(
    name = "app",
    deps = [":lib"],
    manifest = "AndroidManifest.xml",
)

Este archivo BUILD genera el siguiente gráfico de destino:

Resultados de ejemplo

Figura 1: Gráfico de compilación del proyecto de Android con dependencias de cc_library

Para compilar la app, simplemente ejecuta lo siguiente:

bazel build //app/src/main:app

El comando bazel build compila los archivos Java, los archivos de recursos de Android y cc_library reglas, y empaqueta todo en un APK:

$ zipinfo -1 bazel-bin/app/src/main/app.apk
nativedeps
lib/armeabi-v7a/libapp.so
classes.dex
AndroidManifest.xml
...
res/...
...
META-INF/CERT.SF
META-INF/CERT.RSA
META-INF/MANIFEST.MF

Bazel compila todas las cc_libraries en un solo archivo de objeto compartido (.so), orientado a la ABI armeabi-v7a de forma predeterminada. Para cambiar esto o compilar para varias ABI al mismo tiempo, consulta la sección sobre cómo configurar la ABI de destino.

Configuración de ejemplo

Este ejemplo está disponible en el repositorio de ejemplos de Bazel.

En el archivo BUILD.bazel, se definen tres destinos con las reglas android_binary, android_library y cc_library.

El destino de nivel superior android_binary compila el APK.

El destino cc_library contiene un solo archivo fuente de C++ con una implementación de función JNI:

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring

JNICALL
Java_com_example_android_bazel_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

El destino android_library especifica las fuentes Java, los archivos de recursos y la dependencia de un destino cc_library. En este ejemplo, MainActivity.java carga el archivo de objeto compartido libapp.so y define la firma del método para la función JNI:

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("app");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       // ...
    }

    public native String stringFromJNI();

}

Configura la ABI de destino

Para configurar la ABI de destino, usa la marca --android_platforms de la siguiente manera:

bazel build //:app --android_platforms=comma-separated list of platforms

Al igual que la marca --platforms, los valores que se pasan a --android_platforms son las etiquetas de los destinos platform, que usan valores de restricción estándar para describir tu dispositivo.

Por ejemplo, para un dispositivo Android con un procesador ARM de 64 bits, definirías tu plataforma de la siguiente manera:

platform(
    name = "android_arm64",
    constraint_values = [
        "@platforms//os:android",
        "@platforms//cpu:arm64",
    ],
)

Cada Android platform debe usar la @platforms//os:android restricción de SO. Para migrar la restricción de CPU, consulta este gráfico:

Valor de CPU Plataforma
armeabi-v7a @platforms//cpu:armv7
arm64-v8a @platforms//cpu:arm64
x86 @platforms//cpu:x86_32
x86_64 @platforms//cpu:x86_64

Y, por supuesto, para un APK de varias arquitecturas, debes pasar varias etiquetas, por ejemplo: --android_platforms=//:arm64,//:x86_64 (siempre que las hayas definido en tu archivo BUILD.bazel de nivel superior).

Bazel no puede seleccionar una plataforma de Android predeterminada, por lo que se debe definir y especificar con --android_platforms.

Según la revisión del NDK y el nivel de API de Android, están disponibles las siguientes ABI:

Revisión del NDK ABI
16 y versiones anteriores armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64
17 y versiones posteriores armeabi-v7a, arm64-v8a, x86, x86_64

Consulta la documentación del NDK para obtener más información sobre estas ABI.

No se recomiendan los APK Fat de varias ABI para las compilaciones de lanzamiento, ya que aumentan el tamaño del APK, pero pueden ser útiles para las compilaciones de desarrollo y QA.

Selecciona un estándar de C++

Usa las siguientes marcas para compilar según un estándar de C++:

Estándar de C++ Marcar
C++98 Configuración predeterminada, no se necesita ninguna marca
C++11 --cxxopt=-std=c++11
C++14 --cxxopt=-std=c++14
C++17 --cxxopt=-std=c++17

Por ejemplo:

bazel build //:app --cxxopt=-std=c++11

Obtén más información para pasar marcas del compilador y del vinculador con --cxxopt, --copt y --linkopt en el Manual del usuario.

Las marcas del compilador y del vinculador también se pueden especificar como atributos en cc_library con copts y linkopts. Por ejemplo:

cc_library(
    name = "jni_lib",
    srcs = ["cpp/native-lib.cpp"],
    copts = ["-std=c++11"],
    linkopts = ["-ldl"], # link against libdl
)

Compila una cc_library para Android sin usar android_binary

Para compilar una cc_binary o cc_library independiente para Android sin usar una android_binary, usa la --platforms flag.

Por ejemplo, si definiste plataformas de Android en my/platforms/BUILD:

bazel build //my/cc/jni:target \
      --platforms=//my/platforms:x86_64

Con este enfoque, se ve afectado todo el árbol de compilación.

Estas marcas se pueden colocar en una bazelrc configuración (una para cada ABI), en project/.bazelrc:

common:android_x86 --platforms=//my/platforms:x86

common:android_armeabi-v7a --platforms=//my/platforms:armeabi-v7a

# In general
common:android_<abi> --platforms=//my/platforms:<abi>

Luego, para compilar una cc_library para x86, por ejemplo, ejecuta lo siguiente:

bazel build //my/cc/jni:target --config=android_x86

En general, usa este método para destinos de bajo nivel (como cc_library) o cuando sepas exactamente lo que estás compilando; confía en las transiciones de configuración automáticas de android_binary para destinos de alto nivel en los que esperas compilar muchos destinos que no controlas.