Bazel과 함께 Android Native Development Kit 사용

문제 신고 소스 보기 1박 · 7.2 · 7.1 · 7.0 · 6.5 · 6.4

Bazel을 처음 사용하는 경우 먼저 Bazel 튜토리얼

개요

Bazel은 다양한 빌드 구성에서 실행할 수 있으며, 여기에는 Android 네이티브 개발 키트 (NDK) 도구 모음을 지원합니다. 즉, 정상 cc_librarycc_binary 규칙은 Android용으로 직접 컴파일할 수 있습니다. Bazel을 사용하세요. Bazel은 android_ndk_repository 저장소를 사용하여 이 작업을 실행합니다. 있습니다.

기본 요건

Android SDK 및 NDK를 설치했는지 확인하세요.

SDK와 NDK를 설정하려면 다음 스니펫을 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.
)

android_ndk_repository 규칙에 관한 자세한 내용은 빌드 백과사전 항목

빠른 시작

Android용 C++를 빌드하려면 cc_library 종속 항목을 android_binary 또는 android_library 규칙

예를 들어 Android 앱에 다음과 같은 BUILD 파일이 있다고 가정해 보겠습니다.

# 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",
)

BUILD 파일로 인해 다음과 같은 타겟 그래프가 생성됩니다.

결과 예시

그림 1. cc_library 종속 항목이 있는 Android 프로젝트의 그래프를 빌드합니다.

앱을 빌드하려면 다음을 실행하기만 하면 됩니다.

bazel build //app/src/main:app

bazel build 명령어는 Java 파일, Android 리소스 파일, cc_library 규칙을 적용하고 모든 것을 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은 모든 cc_library를 단일 공유 객체 (.so) 파일로 컴파일합니다. 기본적으로 armeabi-v7a ABI용으로 타겟팅됩니다. 이를 변경하거나 빌드하려면 여러 ABI를 동시에 사용하는 경우 타겟 구성 ABI.

설정 예시

이 예시는 Bazel 예시에서 확인할 수 있습니다. 저장소를 사용합니다.

BUILD.bazel 파일에서 세 개의 타겟은 android_binary로 정의됩니다. android_library, cc_library 규칙

android_binary 최상위 타겟은 APK를 빌드합니다.

cc_library 타겟에는 JNI 함수가 있는 단일 C++ 소스 파일이 포함되어 있습니다. 구현:

#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());
}

android_library 타겟은 Java 소스, 리소스 파일, cc_library 타겟에 종속되지 않습니다. 이 예시에서는 MainActivity.java가 공유 객체 파일 libapp.so로, JNI의 메서드 서명을 정의합니다. 함수:

public class MainActivity extends AppCompatActivity {

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

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

    public native String stringFromJNI();

}

STL 구성

C++ STL을 구성하려면 --android_crosstool_top 플래그를 사용합니다.

bazel build //:app --android_crosstool_top=target label

@androidndk에서 사용 가능한 STL은 다음과 같습니다.

STL 대상 라벨
STLport @androidndk//:toolchain-stlport
libc++ @androidndk//:toolchain-libcpp
그누스틀 @androidndk//:toolchain-gnu-libstdcpp

r16 이하의 경우 기본 STL은 gnustl입니다. r17 이상에서는 libc++ 편의를 위해 대상 @androidndk//:default_crosstool는 다음과 같습니다. 각각의 기본 STL로 별칭이 지정되었습니다.

r18부터는 STLport 및 gnustl이 삭제됨, libc++가 NDK의 유일한 STL이 되었습니다.

자세한 내용은 NDK 문서 을(를) 참조하세요.

타겟 ABI 구성

타겟 ABI를 구성하려면 다음과 같이 --fat_apk_cpu 플래그를 사용합니다.

bazel build //:app --fat_apk_cpu=comma-separated list of ABIs

기본적으로 Bazel은 armeabi-v7a용 네이티브 Android 코드를 빌드합니다. x86용 빌드 (예: 에뮬레이터의 경우) --fat_apk_cpu=x86를 전달합니다. 여러 앱의 fat APK를 CPU를 여러 개 지정할 수 있습니다(예: --fat_apk_cpu=armeabi-v7a,x86).

ABI를 두 개 이상 지정하면 Bazel이 공유 API를 포함하는 APK를 빌드합니다. 각 ABI의 객체에 속하는지 확인합니다.

NDK 버전과 Android API 수준에 따라 다음과 같은 ABI가 사용됩니다. 사용 가능:

NDK 버전 ABI
16 이하 armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64
17 이상 armeabi-v7a, arm64-v8a, x86, x86_64

NDK 문서를 참조하세요. 참조하세요.

다중 ABI 지방 APK는 개발 및 QA 빌드에는 유용할 수 있습니다.

C++ 표준 선택

C++ 표준에 따라 빌드하려면 다음 플래그를 사용합니다.

C++ 표준 플래그
C++98 기본값, 플래그 필요 없음
C++11 --cxxopt=-std=c++11
C++14 --cxxopt=-std=c++14

예를 들면 다음과 같습니다.

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

--cxxopt, --copt, 사용자 설명서--linkopt

컴파일러 및 링커 플래그를 cc_library에서 속성으로 지정할 수도 있습니다. coptslinkopts 사용 예를 들면 다음과 같습니다.

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

플랫폼 및 도구 모음과 통합

Bazel의 구성 모델은 플랫폼도구 모음입니다. 만약 빌드에서 --platforms 플래그를 사용하여 아키텍처나 운영체제를 선택합니다. 빌드하려면 --extra_toolchains 플래그를 Bazel에 설치해야 합니다

예를 들어 다음에서 제공하는 android_arm64_cgo 도구 모음과 통합하려면 Go 규칙의 경우, --extra_toolchains=@androidndk//:all를 전달합니다. --platforms 플래그.

bazel build //my/cc:lib \
  --platforms=@io_bazel_rules_go//go/toolchain:android_arm64_cgo \
  --extra_toolchains=@androidndk//:all

WORKSPACE 파일에 직접 등록할 수도 있습니다.

android_ndk_repository(name = "androidndk")
register_toolchains("@androidndk//:all")

이러한 도구 모음을 등록하면 Bazel이 NDK BUILD에서 도구 모음을 찾도록 지시합니다. 파일 (NDK 20용)으로 변경합니다.

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",
)

작동 방식: Android 구성 전환 소개

android_binary 규칙은 Bazel에게 별도의 구성 없이 Bazel 빌드가 작동만할 수 있도록 특수 플래그(--fat_apk_cpu--android_crosstool_top는 제외) ABI 및 STL 구성입니다.

이 자동 구성은 백그라운드에서 Android 구성을 사용합니다. 전환을 실행합니다.

android_binary와 같이 호환되는 규칙은 종속 항목 구성을 Android 구성으로 변경해야 하므로 빌드의 Android 관련 하위 트리가 영향을 받습니다. 빌드의 다른 부분 최상위 대상 구성을 사용하여 처리됩니다. 심지어 두 구성 모두에서 단일 타겟을 처리합니다. 이를 지원하는 그래프를 빌드할 수 있습니다.

Bazel이 Android 호환 구성으로 전환되면 더 높은 수준의 전환 지점으로 인해, 추가 전환이 구성을 추가로 수정하지 않습니다.

Android로의 전환을 트리거하는 유일한 내장 위치 구성은 android_binarydeps 속성입니다.

예를 들어 cc_libraryandroid_library 타겟을 빌드하려고 한다면 종속 항목이 없는 경우 JNI 누락에 관한 오류가 발생할 수 있습니다. 헤더:

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.

이상적으로는 이러한 자동 전환을 통해 Bazel이 대부분입니다. 하지만 Bazel 명령줄의 대상이 이미 전환 규칙에 따라 수행할 수 있습니다. 예를 들어 C++ 개발자가 cc_library이면 맞춤 --crosstool_top를 사용해야 합니다.

android_binary를 사용하지 않고 Android용 cc_library 빌드

다음을 사용하지 않고 Android용 독립형 cc_binary 또는 cc_library를 빌드하려면 android_binary, --crosstool_top, --cpu, --host_crosstool_top 사용 있습니다.

예를 들면 다음과 같습니다.

bazel build //my/cc/jni:target \
      --crosstool_top=@androidndk//:default_crosstool \
      --cpu=<abi> \
      --host_crosstool_top=@bazel_tools//tools/cpp:toolchain

이 예에서는 최상위 cc_librarycc_binary 타겟이 빌드됩니다. 빌드한 것입니다. 그러나 이로 인해 Bazel의 자체 호스트 도구를 이를 NDK 툴체인과 함께 사용해야 합니다 (따라서 Android의 경우). 타겟 툴체인에서 복사됩니다. 이 문제를 해결하려면 다음 목표: --host_crosstool_top @bazel_tools//tools/cpp:toolchain 호스트의 C++ 툴체인을 명시적으로 설정할 수 있습니다.

이 접근 방식을 사용하면 전체 빌드 트리가 영향을 받습니다.

이러한 플래그는 bazelrc config (ABI당 하나)에 입력할 수 있습니다. 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

그런 다음, 예를 들어 x86cc_library를 빌드하려면 다음을 실행합니다.

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

일반적으로 하위 수준 타겟 (예: cc_library) 또는 다음과 같은 경우 이 메서드를 사용합니다. 무엇을 빌드하는지 정확히 알고 있습니다. 자동 구성을 사용하여 예상되는 상위 수준 타겟의 경우 android_binary에서 전환 통제하지 않는 많은 타겟을 빌드할 수 있습니다