Si es la primera vez que usas Bazel, comienza con el instructivo Cómo compilar 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 de cc_library
y cc_binary
se pueden compilar para Android directamente en Bazel. Bazel logra esto con la regla de repositorio android_ndk_repository
y su extensión bzlmod relacionada.
Para la compilación general de Android, usa rules_android
.
En este instructivo, se muestra cómo integrar dependencias de bibliotecas de C++ en apps para Android y se usa rules_android_ndk
para el registro y la detección de la cadena de herramientas del NDK.
Requisitos previos
Asegúrate de haber instalado el SDK y el NDK de Android.
Configuración del NDK y el SDK
La configuración del repositorio externo varía según si usas WORKSPACE o bzlmod (MODULE.bazel). Bzlmod es la solución preferida para Bazel 7 y versiones posteriores. Ten en cuenta que las estrofas de configuración de MODULE.bazel y WORKSPACE son independientes entre sí. Si usas una solución de administración de dependencias, no necesitas agregar el código estándar para la otra.
Configuración de MODULE.bazel de Bzlmod
Agrega el siguiente fragmento a tu archivo MODULE.bazel:
# NDK
bazel_dep(name = "rules_android_ndk", version = "0.1.3")
android_ndk_repository_extension = use_extension("@rules_android_ndk//:extension.bzl", "android_ndk_repository_extension")
use_repo(android_ndk_repository_extension, "androidndk")
register_toolchains("@androidndk//:all")
# SDK
bazel_dep(name = "rules_android", version = "0.6.6")
register_toolchains(
"@rules_android//toolchains/android:android_default_toolchain",
"@rules_android//toolchains/android_sdk:android_sdk_tools",
)
android_sdk_repository_extension = use_extension("@rules_android//rules/android_sdk_repository:rule.bzl", "android_sdk_repository_extension")
use_repo(android_sdk_repository_extension, "androidsdk")
register_toolchains("@androidsdk//:sdk-toolchain", "@androidsdk//:all")
Configuración heredada de WORKSPACE
Agrega el siguiente fragmento a tu WORKSPACE
:
load("@rules_android//rules:rules.bzl", "android_sdk_repository")
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.
)
load("@rules_android_ndk//:rules.bzl", "android_ndk_repository")
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.
)
Notas de compatibilidad para WORKSPACE:
- Las reglas
rules_android
yrules_android_ndk
requieren texto adicional estándar que no se muestra en el fragmento de WORKSPACE anterior. Para obtener una instancia actualizada y completa, consulta el archivo WORKSPACE de la app de ejemplo básica derules_android_ndk
.
Para obtener más información sobre la regla android_ndk_repository
, consulta su cadena de documentación.
Inicio rápido
Para compilar C++ para Android, simplemente agrega dependencias de cc_library
a tus reglas de android_binary
o android_library
.
Por ejemplo, dado el siguiente archivo BUILD
para una app para Android:
# In <project>/app/src/main/BUILD.bazel
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_android//rules:rules.bzl", "android_binary", "android_library")
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: Es el grafo de compilación del proyecto de Android con dependencias de cc_library.
Para compilar la app, simplemente ejecuta el siguiente comando:
bazel build //app/src/main:app --android_platforms=<your platform>
Ten en cuenta que, si no especificas --android_platforms
, se producirá un error en la compilación debido a la falta de encabezados de JNI.
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_libraries en un solo archivo de objeto compartido (.so
), orientado a las arquitecturas especificadas por --android_platforms
.
Consulta la sección sobre cómo configurar la ABI de destino para obtener más detalles.
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 en 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 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 de platform
, y 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 platform
de Android debe usar la restricción del SO @platforms//os:android
. 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
(suponiendo que las definiste 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 una con --android_platforms
.
Según la revisión del NDK y el nivel de API de Android, están disponibles los siguientes ABIs:
Revisión del NDK | ABI |
---|---|
16 y versiones anteriores | armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64 |
17 y más | armeabi-v7a, arm64-v8a, x86, x86_64 |
Consulta la documentación del NDK para obtener más información sobre estas ABIs.
No se recomiendan los APKs multiarquitectura para las compilaciones de lanzamiento, ya que aumentan el tamaño del APK, pero pueden ser útiles para las compilaciones de desarrollo y QA.
Cómo seleccionar un estándar de C++
Usa los siguientes indicadores para compilar según un estándar de C++:
Estándar de C++ | Marcar |
---|---|
C++98 | Predeterminado, 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
)
Cómo compilar un cc_library
para Android sin usar android_binary
Para compilar un cc_binary
o cc_library
independiente para Android sin usar un android_binary
, usa la marca --platforms
.
Por ejemplo, supongamos que 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 configuración de bazelrc
(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 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 objetivos 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 objetivos de alto nivel en los que esperes compilar muchos objetivos que no controlas.