CMake

Android NDK는 CMake를 사용하여 애플리케이션의 C 및 C++ 코드를 컴파일하도록 지원합니다. 이 페이지에서는 Android Gradle 플러그인의 ExternalNativeBuild를 통해서 또는 CMake를 직접 호출할 때 NDK와 함께 CMake를 사용하는 방법을 설명합니다.

CMake 도구 모음 파일

NDK는 도구 모음 파일을 통해 CMake를 지원합니다. 도구 모음 파일은 크로스 컴파일을 위해 도구 모음의 동작을 맞춤설정하는 CMake 파일입니다. NDK에 사용되는 도구 모음 파일은 NDK의 <NDK>/build/cmake/android.toolchain.cmake에 있습니다.

ABI, minSdkVersion 등과 같은 빌드 매개변수는 cmake를 호출할 때 명령줄에 제공됩니다. 지원되는 인수 목록은 도구 모음 인수 섹션을 참고하세요.

'새' 도구 모음 파일

이전 NDK에서는 NDK의 도구 모음 파일을 사용하는 것과 내장 CMake 지원을 사용하는 것 간의 동작 차이를 줄이는 도구 모음 파일의 새로운 구현을 실험했습니다. 이로 인해 상당한 작업이 필요했지만 (완료되지 않음) 실제로 동작이 개선되지 않았으므로 더 이상 이 작업을 진행하지 않습니다.

'새' 도구 모음 파일에는 '기존' 도구 모음 파일에 비해 동작 회귀가 있습니다. 기본 동작은 권장 워크플로입니다. -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF를 사용하는 경우 빌드에서 이 플래그를 삭제하는 것이 좋습니다. 새 도구 모음 파일이 기존 도구 모음 파일과 동등한 상태에 도달한 적이 없으므로 동작 회귀가 발생할 수 있습니다.

새 도구 모음 파일의 사용은 권장하지 않지만 현재 NDK에서 삭제할 계획은 없습니다. 이렇게 하면 새 도구 모음 파일과 기존 도구 모음 파일 간의 동작 차이에 의존하는 빌드가 손상됩니다. 아쉽게도 '레거시'가 실제로 권장된다는 것을 분명히 하기 위해 옵션 이름을 바꾸는 경우에도 해당 옵션을 사용할 수 없습니다. 새 도구 모음 파일을 잘 사용하고 있다면 이전할 필요가 없지만 새 도구 모음 파일 동작에 대해 신고된 버그는 수정되지 않을 가능성이 높으므로 대신 이전해야 합니다.

사용

Gradle

externalNativeBuild를 사용할 때는 CMake 도구 모음 파일이 자동으로 사용됩니다. 자세한 내용은 Android 스튜디오의 C 및 C++ 코드를 프로젝트에 추가 가이드를 참고하세요.

명령줄

Gradle 외부에서 CMake로 빌드할 때는 도구 모음 파일 자체 및 인수를 CMake에 전달해야 합니다. 예:

$ cmake \
    -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI=$ABI \
    -DANDROID_PLATFORM=android-$MINSDKVERSION \
    $OTHER_ARGS

도구 모음 인수

CMake 도구 모음 파일에 다음 인수를 전달할 수 있습니다. Gradle로 빌드하는 경우 ExternalNativeBuild 문서의 설명대로 android.defaultConfig.externalNativeBuild.cmake.arguments에 인수를 추가합니다. 명령줄에서 빌드하는 경우 -D를 사용해 CMake에 인수를 전달합니다. 예를 들어 armeabi-v7a가 Neon 지원을 통해 빌드되지 않도록 하려면 -DANDROID_ARM_NEON=FALSE를 전달합니다.

ANDROID_ABI

타겟 ABI입니다. 지원되는 ABI에 관한 자세한 내용은 Android ABI를 참고하세요.

Gradle

Gradle은 이 인수를 자동으로 제공합니다. 이 인수를 build.gradle 파일에서 명시적으로 설정하지 마세요. ABI Gradle의 타겟을 제어하려면 Android ABI의 설명대로 abiFilters를 사용하세요.

명령줄

CMake는 빌드별 하나의 타겟을 기준으로 빌드합니다. Android ABI 두 개 이상을 타겟팅하려면 ABI별로 한 번씩 빌드해야 합니다. ABI별로 다른 빌드 디렉터리를 사용하여 빌드 간의 충돌을 피하는 것이 좋습니다.

메모
armeabi-v7a
armeabi-v7a with NEON armeabi-v7a과 동일합니다.
arm64-v8a
x86
x86_64

ANDROID_ARM_MODE

armeabi-v7a용 arm 명령을 생성할지 아니면 thumb 명령을 생성할지 지정합니다. 다른 ABI의 경우 영향이 없습니다. 자세한 내용은 Android ABI 문서를 참고하세요.

메모
arm
thumb 기본 동작입니다.

ANDROID_NATIVE_API_LEVEL

ANDROID_PLATFORM의 별칭입니다.

ANDROID_PLATFORM

애플리케이션 또는 라이브러리에서 지원하는 최소 API 수준을 지정합니다. 이 값은 애플리케이션의 minSdkVersion에 해당합니다.

Gradle

Android Gradle 플러그인을 사용하는 경우 이 값은 애플리케이션의 minSdkVersion과 일치하는 값으로 자동 설정되며 수동으로 설정해서는 안 됩니다.

명령줄

CMake를 직접 호출하는 경우 사용 중인 NDK가 지원하는 API 수준 중 가장 낮은 수준이 기본값으로 설정됩니다. 예를 들어 NDK r20의 경우 이 값은 API 수준 16으로 기본 설정됩니다.

이 매개변수에는 여러 형식이 허용됩니다.

  • android-$API_LEVEL
  • $API_LEVEL
  • android-$API_LETTER

$API_LETTER 형식을 사용하면 출시와 관련된 번호를 확인하지 않고도 android-N을 지정할 수 있습니다. 일부 출시에서는 문자 증가 없이 API가 증가했습니다. 이러한 API는 -MR1 접미사를 추가하여 지정할 수 있습니다. 예를 들어 API 수준 25는 android-N-MR1입니다.

ANDROID_STL

이 애플리케이션에 사용할 STL을 지정합니다. 자세한 내용은 C++ 라이브러리 지원을 참고하세요. 기본적으로 c++_static이 사용됩니다.

메모
c++_shared libc++의 공유 라이브러리 변형입니다.
c++_static libc++의 정적 라이브러리 변형입니다.
none C++ 표준 라이브러리 지원이 없습니다.
system 시스템 STL입니다.

컴파일러 플래그 관리

특정 플래그를 빌드의 컴파일러 또는 링커에 전달해야 하는 경우 CMake 문서에서 set_target_compile_options 및 관련 옵션 모음을 참고하세요. 페이지 하단의 '관련 도움말' 섹션에 유용한 단서가 있습니다.

일반적으로 컴파일러 플래그를 사용 가능한 가장 좁은 범위로 적용하는 것이 좋습니다. 모든 타겟에 적용하려는 플래그 (예: -Werror)는 모듈별로 반복하는 것이 불편하지만 전역적으로 적용하는 경우 (CMAKE_CXX_FLAGS) 프로젝트의 서드 파티 종속 항목에 원치 않는 영향을 미칠 수 있으므로 거의 적용해서는 안 됩니다. 이러한 경우 디렉터리 범위 (add_compile_options)에서 플래그를 적용할 수 있습니다.

컴파일러 플래그의 좁은 하위 집합의 경우 cppFlags 또는 유사한 속성을 사용하여 build.gradle 파일에 설정할 수도 있습니다. 이렇게 해서는 안 됩니다. Gradle에서 CMake에 전달된 플래그는 예상치 못한 우선순위 동작을 보일 수 있습니다. 경우에 따라 Android 코드 빌드에 필요한 구현에서 암시적으로 전달된 플래그를 재정의합니다. 항상 CMake에서 직접 CMake 동작을 처리하는 것이 좋습니다. AGP buildType별로 컴파일러 플래그를 제어해야 하는 경우 CMake에서 AGP 빌드 유형 사용을 참고하세요.

CMake에서 AGP 빌드 유형 사용

CMake 동작을 맞춤 Gradle buildType에 맞게 조정해야 하는 경우 해당 빌드 유형을 사용하여 CMake 빌드 스크립트에서 읽을 수 있는 추가 CMake 플래그 (컴파일러 플래그 아님)를 전달합니다. 예를 들어 build.gradle.kts로 제어되는 '무료' 및 '프리미엄' 빌드 변형이 있고 이 데이터를 CMake에 전달해야 하는 경우 다음과 같이 하면 됩니다.

android {
    buildTypes {
        free {
            externalNativeBuild {
                cmake {
                    arguments.add("-DPRODUCT_VARIANT_PREMIUM=OFF")
                }
            }
        }
        premium {
            externalNativeBuild {
                cmake {
                    arguments.add("-DPRODUCT_VARIANT_PREMIUM=ON")
                }
            }
        }
    }
}

그런 다음 CMakeLists.txt에서 다음을 수행합니다.

if (DPRODUCT_VARIANT_PREMIUM)
  # Do stuff for the premium build.
else()
  # Do stuff for the free build.
endif()

변수 이름은 개발자가 지정할 수 있지만 기존 플래그와 충돌하거나 혼동을 피하기 위해 ANDROID_, APP_ 또는 CMAKE_ 접두사가 있는 이름은 피해야 합니다.

예시는 Sanitizers NDK 샘플을 참고하세요.

CMake 빌드 명령어 이해

CMake 빌드 문제를 디버깅할 때 Android용 크로스 컴파일 시 Gradle에서 사용하는 특정 빌드 인수를 알고 있으면 유용합니다.

Android Gradle 플러그인은 각 ABI-빌드 유형 쌍의 CMake 빌드를 실행하는 데 사용하는 빌드 인수를 build_command.txt에 저장합니다. 이러한 파일은 다음 디렉터리에 있습니다.

<project-root>/<module-root>/.cxx/cmake/<build-type>/<ABI>/

다음 스니펫은 armeabi-v7a 아키텍처를 타겟팅하는 hello-jni 샘플의 디버깅 가능한 출시를 빌드하는 CMake 인수의 예를 보여줍니다.

                    Executable : ${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/cmake
arguments :
-H${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/src/main/cpp
-DCMAKE_FIND_ROOT_PATH=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/prefab/armeabi-v7a/prefab
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_TOOLCHAIN_FILE=${HOME}/Android/Sdk/ndk/22.1.7171670/build/cmake/android.toolchain.cmake
-DANDROID_ABI=armeabi-v7a
-DANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DANDROID_PLATFORM=android-23
-DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a
-DCMAKE_ANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_MAKE_PROGRAM=${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/ninja
-DCMAKE_SYSTEM_NAME=Android
-DCMAKE_SYSTEM_VERSION=23
-B${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/armeabi-v7a
-GNinja
jvmArgs :


                    Build command args: []
                    Version: 1

미리 빌드된 라이브러리 사용

가져와야 하는 미리 빌드된 라이브러리가 AAR로 배포된 경우 스튜디오의 종속 항목 문서를 따라 라이브러리를 가져오고 사용하세요. AGP를 사용하지 않는 경우 https://google.github.io/prefab/example-workflow.html을 따르면 되지만, AGP로 이전하는 것이 훨씬 쉬울 수 있습니다.

AAR로 배포되지 않는 라이브러리의 경우 CMake로 미리 빌드된 라이브러리를 사용하는 방법은 CMake 매뉴얼에서 IMPORTED 타겟에 관한 add_library 문서를 참고하세요.

서드 파티 코드 빌드

CMake 프로젝트의 일부로 서드 파티 코드를 빌드하는 방법에는 여러 가지가 있으며 가장 효과적인 옵션은 상황에 따라 다릅니다. 가장 좋은 방법은 CMake 프로젝트에서 서드 파티 코드를 빌드하지 않는 것입니다. 대신 라이브러리의 AAR을 빌드하여 애플리케이션에서 사용합니다. 이 AAR을 게시할 필요는 없습니다. Gradle 프로젝트 내부에 있으면 됩니다.

이런 방법을 사용할 수 없는 경우 다음 단계를 따르세요.

  • 서드 파티 소스를 저장소에 복사하고 add_subdirectory를 사용하여 빌드합니다. 이는 다른 라이브러리도 CMake로 빌드된 경우에만 작동합니다.
  • ExternalProject를 정의합니다.
  • 라이브러리를 프로젝트와 별개로 빌드하고 미리 빌드된 라이브러리 사용에 따라 미리 빌드된 라이브러리로 가져옵니다.

CMake에서 YASM 지원

NDK는 YASM으로 작성된 어셈블리 코드를 x86 및 x86-64 아키텍처에서 실행하도록 빌드할 수 있게 CMake 지원을 제공합니다. YASM은 x86 및 x86-64 아키텍처용 오픈소스 어셈블러로, NASM 어셈블러에 기반합니다.

CMake로 어셈블리 코드를 빌드하려면 프로젝트의 CMakeLists.txt에서 다음과 같이 변경합니다.

  1. 값을 ASM_NASM으로 설정하여 enable_language를 호출합니다.
  2. 빌드 중인 것이 공유 라이브러리 또는 실행 바이너리 중 무엇인지에 따라 add_library 또는 add_executable을 호출합니다. 인수에서 .asm 파일(YASM의 어셈블리 프로그램용) 및 .c 파일(연결된 C 라이브러리 또는 함수용)로 이루어진 소스 파일 목록을 전달합니다.

다음 스니펫은 YASM 프로그램을 공유 라이브러리로 빌드하도록 CMakeLists.txt를 구성하는 방법을 보여줍니다.

cmake_minimum_required(VERSION 3.6.0)

enable_language(ASM_NASM)

add_library(test-yasm SHARED jni/test-yasm.c jni/print_hello.asm)

YASM 프로그램을 실행 파일로 빌드하는 방법의 예는 NDK git 저장소의 yasm 테스트를 참고하세요.

문제 신고

NDK 또는 CMake 도구 모음 파일에 문제가 발생하면 GitHub의 android-ndk/ndk Issue tracker를 통해 보고하세요. Gradle 또는 Android Gradle 플러그인 문제의 경우 대신 Studio 버그를 신고하세요.