Bazel과 함께 Android Native Development Kit 사용

7.3 · 7.2 · 7.1 · 7.0 · 6.5

Bazel을 처음 사용하는 경우 Bazel을 사용하여 Android 빌드 튜토리얼로 시작하세요.

개요

Bazel은 Android Native Development Kit (NDK) 도구 모음을 사용하는 여러 빌드 구성을 비롯한 다양한 빌드 구성에서 실행할 수 있습니다. 즉, 일반 cc_librarycc_binary 규칙을 Bazel 내에서 직접 Android용으로 컴파일할 수 있습니다. 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 NDK (r22 이상)를 사용 중인 경우 android_ndk_repository의 Starlark 구현을 사용합니다. 리드미의 안내를 따르세요.

빠른 시작

Android용 C++를 빌드하려면 android_binary 또는 android_library 규칙에 cc_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_libraries를 기본적으로 armeabi-v7a ABI를 타겟팅하는 단일 공유 객체 (.so) 파일로 컴파일합니다. 이를 변경하거나 여러 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();

}

타겟 ABI 구성

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

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

--platforms 플래그와 마찬가지로 --android_platforms에 전달되는 값은 표준 제약 조건 값을 사용하여 기기를 설명하는 platform 타겟의 라벨입니다.

예를 들어 64비트 ARM 프로세서가 있는 Android 기기의 경우 다음과 같이 플랫폼을 정의합니다.

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

모든 Android platform@platforms//os:android OS 제약 조건을 사용해야 합니다. CPU 제약조건을 이전하려면 다음 차트를 확인하세요.

CPU 값 플랫폼
armeabi-v7a @platforms//cpu:armv7
arm64-v8a @platforms//cpu:arm64
x86 @platforms//cpu:x86_32
x86_64 @platforms//cpu:x86_64

물론 멀티 아키텍처 APK의 경우 여러 라벨(예: --android_platforms=//:arm64,//:x86_64)을 전달합니다(최상위 BUILD.bazel 파일에서 정의했다고 가정).

Bazel은 기본 Android 플랫폼을 선택할 수 없으므로 --android_platforms로 정의하고 지정해야 합니다.

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

이러한 ABI에 관한 자세한 내용은 NDK 문서를 참고하세요.

멀티 ABI fat APK는 APK 크기를 늘리므로 출시 빌드에는 권장되지 않지만 개발 및 QA 빌드에는 유용할 수 있습니다.

C++ 표준 선택

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

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

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

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

--cxxopt, --copt, --linkopt를 사용하여 컴파일러 및 링커 플래그를 전달하는 방법에 관한 자세한 내용은 사용자 설명서를 참고하세요.

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

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

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

android_binary를 사용하지 않고 Android용 독립형 cc_binary 또는 cc_library를 빌드하려면 --platforms 플래그를 사용합니다.

예를 들어 my/platforms/BUILD에서 Android 플랫폼을 정의했다고 가정합니다.

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

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

이러한 플래그는 project/.bazelrcbazelrc 구성 (각 ABI에 하나씩)에 배치할 수 있습니다.

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>

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

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

일반적으로 이 메서드는 하위 수준 타겟 (예: cc_library) 또는 빌드 중인 항목을 정확히 알고 있는 경우에 사용합니다. 제어하지 않는 많은 타겟을 빌드할 것으로 예상되는 상위 수준 타겟의 경우 android_binary의 자동 구성 전환을 사용합니다.