Bazel과 함께 Android Native Development Kit 사용

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

개요

Bazel은 Android Native Development Kit (NDK) 도구 모음을 사용하는 여러 구성을 비롯한 다양한 빌드 구성에서 실행할 수 있습니다. 즉, 일반적인 cc_librarycc_binary 규칙을 Bazel 내에서 직접 Android용으로 컴파일할 수 있습니다. Bazel은 android_ndk_repository 저장소 규칙 및 관련 bzlmod 확장을 사용하여 이 작업을 실행합니다.

일반적인 Android 컴파일에는 rules_android를 사용합니다. 이 튜토리얼에서는 C++ 라이브러리 종속 항목을 Android 앱에 통합하는 방법을 보여주고 NDK 도구 모음 검색 및 등록에 rules_android_ndk를 사용합니다.

기본 요건

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

NDK 및 SDK 설정

외부 저장소 설정은 WORKSPACE 또는 bzlmod (MODULE.bazel)를 사용하는지에 따라 다릅니다. Bzlmod는 Bazel 7 이상에 권장되는 솔루션입니다. MODULE.bazel 및 WORKSPACE 설정 스탠자는 서로 독립적입니다. 종속 항목 관리 솔루션 하나를 사용하는 경우 다른 솔루션의 상용구를 추가할 필요가 없습니다.

Bzlmod MODULE.bazel 설정

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

기존 WORKSPACE 설정

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.
)

WORKSPACE 호환성 정보:

  • rules_androidrules_android_ndk 규칙 모두 위에 표시된 WORKSPACE 스니펫에 표시되지 않은 추가 상용구가 필요합니다. 최신 버전의 완전히 구성된 인스턴스화 스탠자는 WORKSPACE 파일을 참고하세요. rules_android_ndk의 기본 예시 앱

android_ndk_repository 규칙에 관한 자세한 내용은 독스트링을 참고하세요.

빠른 시작

Android용 C++을 빌드하려면 cc_library 종속 항목을 android_binary 또는 android_library 규칙에 추가하면 됩니다.

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

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

BUILD 파일은 다음 대상 그래프를 생성합니다.

결과 예시

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

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

bazel build //app/src/main:app --android_platforms=<your platform>

--android_platforms를 지정하지 않으면 빌드가 실패하고 JNI 헤더가 누락되었다는 오류가 발생합니다.

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를 단일 공유 객체 (.so) 파일로 컴파일합니다. --android_platforms로 지정된 아키텍처를 타겟팅합니다. 자세한 내용은 대상 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를 사용하여 컴파일러 및 링커 플래그 전달에 관해 자세히 알아보세요.

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

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

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

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

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

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

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

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

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

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

일반적으로 이 메서드는 하위 수준 대상 (cc_library 등)에 사용하거나 빌드하려는 항목을 정확히 알고 있을 때 사용합니다. 제어할 수 없는 많은 대상을 빌드할 것으로 예상되는 상위 수준 대상의 경우 android_binary의 자동 구성 전환을 사용합니다.