Si es la primera vez que usas Bazel, comienza con el instructivo Compila Android con Bazel.
Descripción general
Bazel puede ejecutarse en muchas configuraciones de compilación diferentes, incluidas varias que usan la cadena de herramientas del kit de desarrollo nativo (NDK) de Android. Eso significa que se pueden compilar las reglas cc_library
y cc_binary
normales para Android directamente dentro de Bazel. Bazel lo logra mediante la regla de repositorio android_ndk_repository
.
Requisitos previos
Asegúrate de haber instalado el SDK y NDK de Android.
Para configurar el SDK y 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 enciclopedia de compilación.
Inicio rápido
Si deseas compilar C++ para Android, simplemente agrega dependencias de cc_library
a tus reglas android_binary
o android_library
.
Por ejemplo, con el siguiente archivo BUILD
de 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:
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 las reglas de cc_library
, 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_library en un solo archivo de objeto compartido (.so
), segmentado para la ABI armeabi-v7a
de forma predeterminada. Si quieres cambiar esto o realizar compilaciones 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 objetivos con las reglas android_binary
, android_library
y cc_library
.
El objetivo de nivel superior android_binary
compila el APK.
El destino cc_library
contiene un solo archivo de origen 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 de 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();
}
Cómo configurar el STL
Para configurar la STL de C++, usa la marca --android_crosstool_top
.
bazel build //:app --android_crosstool_top=target label
Los STL disponibles en @androidndk
son los siguientes:
STL | Etiqueta de destino |
---|---|
STLport | @androidndk//:toolchain-stlport |
libc++ | @androidndk//:toolchain-libcpp |
Gnustl | @androidndk//:toolchain-gnu-libstdcpp |
Para r16 y versiones anteriores, el STL predeterminado es gnustl
. Para r17 y versiones posteriores, es libc++
. Para mayor comodidad, el @androidndk//:default_crosstool
de destino está asociado con las STL predeterminadas respectivas.
Ten en cuenta que, a partir de r18, se quitarán STLport y gnustl, lo que hará que libc++
sea la única STL del NDK.
Consulta la documentación del NDK para obtener más información sobre estos STL.
Configura la ABI de destino
Para configurar la ABI de destino, usa la marca --fat_apk_cpu
de la siguiente manera:
bazel build //:app --fat_apk_cpu=comma-separated list of ABIs
De forma predeterminada, Bazel compila código nativo de Android para armeabi-v7a
. Si deseas compilar para x86 (como los emuladores), pasa --fat_apk_cpu=x86
. Para crear un APK multiarquitectura para varias arquitecturas, puedes especificar varias CPU: --fat_apk_cpu=armeabi-v7a,x86
.
Si se especifica más de una ABI, Bazel compilará un APK que contendrá un objeto compartido para cada ABI.
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 anteriores | armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86 y x86_64. |
17 y más | armeabi-v7a, arm64-v8a, x86 y x86_64 |
Consulta la documentación del NDK para obtener más información sobre estas ABI.
No se recomiendan los APK de grasa con varias ABI para las compilaciones de lanzamiento, ya que aumentan el tamaño del APK, pero pueden ser útiles para compilaciones de desarrollo y control de calidad.
Cómo seleccionar un estándar C++
Usa los siguientes indicadores para compilar de acuerdo con un estándar C++:
C++ estándar | Marca |
---|---|
C++98 | Predeterminado, no se necesita marca |
C++11 | --cxxopt=-std=c++11 |
C++14 | --cxxopt=-std=c++14 |
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
mediante copts
y linkopts
. Por ejemplo:
cc_library(
name = "jni_lib",
srcs = ["cpp/native-lib.cpp"],
copts = ["-std=c++11"],
linkopts = ["-ldl"], # link against libdl
)
Integración a plataformas y cadenas de herramientas
El modelo de configuración de Bazel se traslada a las
plataformas y
cadenas de herramientas. Si tu compilación usa la marca --platforms
a fin de seleccionar la arquitectura o el sistema operativo para compilar, deberás pasar la marca --extra_toolchains
a Bazel a fin de usar el NDK.
Por ejemplo, para realizar la integración con la cadena de herramientas android_arm64_cgo
que proporcionan
las reglas de Go, pasa --extra_toolchains=@androidndk//:all
además de la
marca --platforms
.
bazel build //my/cc:lib \
--platforms=@io_bazel_rules_go//go/toolchain:android_arm64_cgo \
--extra_toolchains=@androidndk//:all
También puedes registrarlos directamente en el archivo WORKSPACE
:
android_ndk_repository(name = "androidndk")
register_toolchains("@androidndk//:all")
El registro de estas cadenas de herramientas le indica a Bazel que las busque en el archivo BUILD
del NDK (para el NDK 20) cuando resuelva restricciones de arquitectura y sistema operativo:
toolchain(
name = "x86-clang8.0.7-libcpp_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
target_compatible_with = [
"@platforms//os:android",
"@platforms//cpu:x86_32",
],
toolchain = "@androidndk//:x86-clang8.0.7-libcpp",
)
toolchain(
name = "x86_64-clang8.0.7-libcpp_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
target_compatible_with = [
"@platforms//os:android",
"@platforms//cpu:x86_64",
],
toolchain = "@androidndk//:x86_64-clang8.0.7-libcpp",
)
toolchain(
name = "arm-linux-androideabi-clang8.0.7-v7a-libcpp_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
target_compatible_with = [
"@platforms//os:android",
"@platforms//cpu:arm",
],
toolchain = "@androidndk//:arm-linux-androideabi-clang8.0.7-v7a-libcpp",
)
toolchain(
name = "aarch64-linux-android-clang8.0.7-libcpp_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
target_compatible_with = [
"@platforms//os:android",
"@platforms//cpu:aarch64",
],
toolchain = "@androidndk//:aarch64-linux-android-clang8.0.7-libcpp",
)
Cómo funciona: Presentación de las transiciones de configuración de Android
La regla android_binary
puede pedirle de forma explícita a Bazel que compile sus dependencias en una configuración compatible con Android para que la compilación de Bazel funcione sin marcas especiales, excepto --fat_apk_cpu
y --android_crosstool_top
para la configuración de ABI y STL.
En segundo plano, esta configuración automática usa las transiciones de configuración de Android.
Una regla compatible, como android_binary
, cambia automáticamente la configuración de sus dependencias a una configuración de Android, por lo que solo se ven afectados los subárboles específicos de Android de la compilación. Otras partes del gráfico de compilación se procesan con la configuración de destino de nivel superior. Incluso puede procesar un solo destino en ambas configuraciones si existen rutas de acceso a través del grafo de compilación que las admitan.
Una vez que Bazel tiene una configuración compatible con Android, ya sea especificada en el nivel superior o debido a un punto de transición de nivel superior, los puntos de transición adicionales encontrados no modifican la configuración.
La única ubicación integrada que activa la transición a la configuración de Android es el atributo deps
de android_binary
.
Por ejemplo, si intentas compilar un destino android_library
con una dependencia cc_library
sin ninguna marca, es posible que encuentres un error relacionado con un encabezado de JNI faltante:
ERROR: project/app/src/main/BUILD.bazel:16:1: C++ compilation of rule '//app/src/main:jni_lib' failed (Exit 1)
app/src/main/cpp/native-lib.cpp:1:10: fatal error: 'jni.h' file not found
#include <jni.h>
^~~~~~~
1 error generated.
Target //app/src/main:lib failed to build
Use --verbose_failures to see the command lines of failed build steps.
Idealmente, estas transiciones automáticas deberían hacer que Bazel haga lo correcto en la
mayoría de los casos. Sin embargo, si el destino en la línea de comandos de Bazel ya está por debajo de cualquiera de estas reglas de transición, como los desarrolladores de C++ que prueban un cc_library
específico, se debe usar un --crosstool_top
personalizado.
Cómo compilar un objeto cc_library
para Android sin usar android_binary
Para compilar un objeto cc_binary
o cc_library
independiente para Android sin usar un android_binary
, usa las marcas --crosstool_top
, --cpu
y --host_crosstool_top
.
Por ejemplo:
bazel build //my/cc/jni:target \
--crosstool_top=@androidndk//:default_crosstool \
--cpu=<abi> \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain
En este ejemplo, los destinos cc_library
y cc_binary
de nivel superior se compilan con la cadena de herramientas del NDK. Sin embargo, esto hace que las propias herramientas de host de Bazel se compilen con la cadena de herramientas del NDK (y, por lo tanto, para Android) porque la cadena de herramientas del host se copia de la de destino. Para solucionar este problema, especifica el valor de --host_crosstool_top
para que sea @bazel_tools//tools/cpp:toolchain
y, así, establecer de forma explícita la cadena de herramientas de C++ del host.
Con este enfoque, todo el árbol de compilación se ve afectado.
Estas marcas se pueden colocar en una configuración de bazelrc
(una para cada ABI), en project/.bazelrc
:
common:android_x86 --crosstool_top=@androidndk//:default_crosstool
common:android_x86 --cpu=x86
common:android_x86 --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
common:android_armeabi-v7a --crosstool_top=@androidndk//:default_crosstool
common:android_armeabi-v7a --cpu=armeabi-v7a
common:android_armeabi-v7a --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
# In general
common:android_<abi> --crosstool_top=@androidndk//:default_crosstool
common:android_<abi> --cpu=<abi>
common:android_<abi> --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
Luego, a fin de compilar un 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 con exactitud 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.