Como usar o kit de desenvolvimento nativo do Android com o Bazel

Informar um problema Acessar a origem

Se você não conhece o Bazel, comece pelo tutorial Como criar para o Android com o Bazel.

Informações gerais

Ele pode ser executado em muitas configurações de build diferentes, incluindo várias que usam o conjunto de ferramentas do Android Native Development Kit (NDK, na sigla em inglês). Isso significa que as regras cc_library e cc_binary normais podem ser compiladas para o Android diretamente no Bazel. Para fazer isso, o Bazel usa a regra de repositório android_ndk_repository.

Pré-requisitos

Verifique se você instalou o SDK e o NDK do Android.

Para configurar o SDK e o NDK, adicione o seguinte snippet ao seu 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 mais informações sobre a regra android_ndk_repository, consulte a entrada da enciclopédia de criação.

Se você está usando uma versão recente do Android NDK (r22 e mais recentes), use a implementação do Starlark de android_ndk_repository. Siga as instruções no arquivo README.

Início rápido

Para criar C++ para Android, basta adicionar dependências cc_library às suas regras android_binary ou android_library.

Por exemplo, considerando o seguinte arquivo BUILD para um app 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",
)

Esse arquivo BUILD resulta no seguinte gráfico de destino:

Resultados de exemplo

Figura 1. Gráfico de criação do projeto Android com dependências cc_library.

Para criar o app, basta executar:

bazel build //app/src/main:app

O comando bazel build compila os arquivos Java, arquivos de recursos do Android e regras cc_library e empacota tudo em um 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

O Bazel compila todas as cc_library em um único arquivo de objeto compartilhado (.so), direcionado à ABI armeabi-v7a por padrão. Para mudar isso ou criar para várias ABIs ao mesmo tempo, consulte a seção sobre como configurar a ABI de destino.

Exemplo de configuração

Este exemplo está disponível no repositório de exemplos do Bazel (link em inglês).

No arquivo BUILD.bazel, três destinos são definidos com as regras android_binary, android_library e cc_library.

O destino de nível superior android_binary cria o APK.

O destino cc_library contém um único arquivo de origem C++ com uma implementação de função 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());
}

O destino android_library especifica as origens Java, os arquivos de recursos e a dependência em um destino cc_library. Neste exemplo, MainActivity.java carrega o arquivo de objeto compartilhado libapp.so e define a assinatura do método para a função JNI:

public class MainActivity extends AppCompatActivity {

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

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

    public native String stringFromJNI();

}

Como configurar a ABI de destino

Para configurar a ABI de destino, use a sinalização --android_platforms desta maneira:

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

Assim como a flag --platforms, os valores transmitidos para --android_platforms são os rótulos dos destinos platform, usando valores de restrição padrão para descrever seu dispositivo.

Por exemplo, para um dispositivo Android com um processador ARM de 64 bits, você definiria sua plataforma da seguinte maneira:

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

Cada platform Android precisa usar a restrição de SO @platforms//os:android. Para migrar a restrição de CPU, consulte este gráfico:

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

E, é claro, para um APK de multiarquitetura, você transmite vários rótulos, por exemplo: --android_platforms=//:arm64,//:x86_64 (supondo que você os tenha definido no arquivo BUILD.bazel de nível superior).

O Bazel não consegue selecionar uma Plataforma Android padrão. Portanto, uma precisa ser definida e especificada com --android_platforms.

Dependendo da revisão do NDK e do nível da API do Android, as seguintes ABIs estão disponíveis:

Revisão do NDK Interfaces binárias de aplicativo (ABIs, na sigla em inglês)
16 e anteriores armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64
17 e acima armeabi-v7a, arm64-v8a, x86 e x86_64

Consulte os documentos do NDK para ver mais informações sobre essas ABIs.

Esses APKs não são recomendados para builds de lançamento porque aumentam o tamanho do APK, mas podem ser úteis para desenvolvimento e builds de controle de qualidade.

Como selecionar um padrão C++

Use as sinalizações a seguir para criar de acordo com um padrão C++:

Padrão C++ Flag
C++98 Padrão, nenhuma flag necessária
C++11 --cxxopt=-std=c++11
C++14 --cxxopt=-std=c++14
C++17 --cxxopt=-std=c++17

Exemplo:

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

Leia mais sobre como transmitir sinalizações do compilador e do vinculador com --cxxopt, --copt e --linkopt no Manual do usuário.

As sinalizações do compilador e do vinculador também podem ser especificadas como atributos em cc_library usando copts e linkopts. Exemplo:

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

Como criar um cc_library para Android sem usar android_binary

Para criar um cc_binary ou cc_library autônomo para Android sem usar um android_binary, use a sinalização --platforms.

Por exemplo, supondo que você tenha definido as plataformas Android em my/platforms/BUILD:

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

Com essa abordagem, toda a árvore de compilação é afetada.

Essas flags podem ser colocadas em uma configuração bazelrc (uma para cada ABI), em 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>

Em seguida, para criar um cc_library para x86, por exemplo, execute:

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

Em geral, use esse método para destinos de baixo nível (como cc_library) ou quando você sabe exatamente o que está criando. Confie nas transições de configuração automáticas de android_binary para destinos de alto nível, em que você espera criar muitos destinos que não controla.