Android NDK로 3rd party 라이브러리 빌드하기

NDK를 사용하는 프로젝트에서 여러 개의 네이티브 라이브러리가 필요한 경우가 있다. 처음에는 jni/  하위 디렉토리에 라이브러리 소스코드를 전부 받아두고 Android.mk 파일을 적절히 구성해서 한번에 빌드해보려고 시도했었다.

jni/
    Android.mk
    (project source code...)
    libraryA/
        Android.mk
        (library a source code...)
    libraryB/
        Android.mk
        (library b source code...)

jni/Android.mk 는 하위 디렉토리를 탐색하고, jni/libraryA/Android.mk 와 jni/libraryB/Android.mk 는 그 디렉토리의 Makefile을 바탕으로 빌드(make)를 실행하면 되지 않을까 하는 생각이었다.

결론부터 말하자면 그런 거 안된다. 소스코드를 바로 받아서 그대로 둔 상태에서 Android.mk만 만든다고 해서 빌드가 되지는 않는다. (정확히 말하자면, 하려면 할 수야 있겠지만 수고에 비해서 얻을 것이 없어보인다.) 이는 빌드 시의 환경설정 단계 때문인데 자세한 내용은 다음과 같다.

대부분의 C/C++ 라이브러리들은 다중 플랫폼 지원을 위해 autoconf, automake, libtool 등을 사용하여 build time에 자동으로 환경을 구성하고, 그 환경에 따라 동적으로 Makefile 및 config.h 파일 등을 생성하게 된다. 크로스 컴파일 환경이 아니라면 아래와 같이 간단하게 환경 설정과 빌드 및 설치가 가능하다.

$ ./configure && make && make install

그러나 ndk-build는 크로스 컴파일이고, 크로스 컴파일을 하려면 make install 은 당연히 의미가 없고 (빌드하는 머신과 설치되는 머신이 다르고 시스템에 직접 설치하는 것이 아니라 앱의 패키지에 포함되어야한다) configure 역시 수작업으로 변수들을 지정해주어야한다 (이런 작업들을 물론 Android.mk로 하려면 할 수야 있겠지만…). configure가 실행되지 않으면 Makefile이 생성되지 않아 빌드를 할 수 없다.

다행히 유명한 라이브러리들은 Android용 바이너리를 빌드하기 위한 방법을 문서로 제공하고 있다.

예: libjpeg-turbo/BUILDING.md

위 문서의 내용은 사실은 안드로이드 공식 문서에 나와있는 Standalone 빌드 방식을 사용해서 빌드하는 방법이다.

그리고 라이브러리 빌드와는 별개의 문제이지만 디렉토리 구조 또한 빌드가 꼬이기 쉬운 구조이다.

Standalone 방식으로 빌드해서 헤더파일과 .a, .so 파일을 아래와 같은 Android.mk 설정으로 포함시키는 것이 가장 스트레스가 적다.

jni/
    Android.mk
    my_jni_code/
        Android.mk
        (project source code...)
    libraryA/
        Android.mk
        liba.h
        liba.a

my_jni_code/ 라는 디렉토리가 추가되었다. 프로젝트의 java 코드와 직접 연관있는 소스 코드는 jni/my_jni_code/ 로 옮겨둔다. 라이브러리 소스코드들이 프로젝트 코드의 하위 디렉토리에 있으면 Android.mk에서 또다시 하위 디렉토리를 include하도록 해야하는데, 이렇게 하면 빌드가 꼬이게 된다. NDK 빌드는 recursive하게 설계되지 않았다 (사실은 굳이 그렇게 할 필요가 없다). 따라서 각 모듈들이 동일한 디렉토리 레벨에 존재하도록 하고 jni/Android.mk 는 아래 한 줄만 포함하도록 한다.

include $(call all-subdir-makefiles)

그러면 ndk 빌드 시스템이 자동으로 의존성을 파악하고 각각의 모듈을 빌드하게 된다.

jni/libraryA/Android.mk 파일은 prebuilt library 설정을 사용

LOCAL_PATH := $(call my-dir)
include ($CLEAR_VARS)

LOCAL_MODULE := lib-a
LOCAL_SRC_FILES :=\
liba.a \

LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.

include $(PREBUILT_STATIC_LIBRARY)

jni/my_jni_code/Android.mk 에서는 다른 디렉토리 신경쓸 필요 없이 아래와 같이 모듈을 참조하는 한 줄만 추가해주면 된다.

(기타등등 기본적인 설정들은 생략...)
LOCAL_STATIC_LIBRARIES := lib-a

파일 이름(liba.a)이 아니라 LOCAL_MODULE로 지정한 lib-a를 써준다. ndk 빌드 시스템이 자동으로 의존성을 파악하고 lib-a 모듈을 찾아서 그 모듈의 Android.mk 파일에 설정된 값들(헤더파일 경로 등)을 적용해준다.

Leave a Comment

Your email address will not be published.