Android NDKにおけるAndroid.mkファイルとビルドプロセスの理解

Android NDK (Native Development Kit) を利用してC/C++コードをAndroidアプリケーションに組み込む際、Android.mkファイルはビルドシステムに対し、ソースコードに関する情報を提供する上で不可欠な役割を担います。このファイルはGNU Makefileの一部として解釈され、ネイティブモジュール(静的ライブラリ、共有ライブラリ、実行可能ファイルなど)の構築方法を定義します。

Android.mkの主な利点は、開発者が複雑なビルドの詳細(ヘッダーパスや外部依存関係など)を個々に記述する必要がない点にあります。NDKビルドシステムがこれらの情報を自動的に解決するため、ツールチェーンやプラットフォームが更新されても、Android.mkファイルを頻繁に修正することなく新しいNDKの恩恵を受けられます。

基本的なAndroid.mkの構成

まず、最も単純な共有ライブラリをビルドするためのAndroid.mkファイルの例を見てみましょう。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := my-native-lib
LOCAL_SRC_FILES := native-code.c

include $(BUILD_SHARED_LIBRARY)

この例の各行について説明します。

  • LOCAL_PATH := $(call my-dir)
    すべてのAndroid.mkファイルは、この行から始めるのが慣例です。これは、現在のAndroid.mkファイルが存在するディレクトリのパスをLOCAL_PATH変数に設定します。my-dirマクロは、ビルドシステムによって提供され、現在のMakefileのパスを返します。
  • include $(CLEAR_VARS)
    CLEAR_VARSは、ビルドシステムが提供する変数で、以前に定義されたLOCAL_MODULELOCAL_SRC_FILESLOCAL_STATIC_LIBRARIESなどの多くのLOCAL_xxx変数をクリアするGNU Makefileスクリプトを指します。これは、同じビルドプロセス内で複数のモジュールを定義する際に、前のモジュールの設定が新しいモジュールに影響しないようにするために必要です。ただし、LOCAL_PATHはクリアされません。
  • LOCAL_MODULE := my-native-lib
    これは、モジュールに一意の名前を付けるために必須の変数です。名前にはスペースを含めることはできません。ビルドシステムは、この名前に適切なプレフィックス(例: lib)とサフィックス(例: .so)を自動的に追加し、最終的な出力ファイル名(この場合はlibmy-native-lib.so)を生成します。
  • LOCAL_SRC_FILES := native-code.c
    この変数には、モジュールにコンパイルするC/C++ソースファイルのリストを指定します。ヘッダーファイルは自動的に検出されるため、ここに含める必要はありません。ファイルのパスはLOCAL_PATHに対する相対パスで記述できます。
  • include $(BUILD_SHARED_LIBRARY)
    BUILD_SHARED_LIBRARYもビルドシステムが提供する変数で、CLEAR_VARSの呼び出し以降に定義されたすべてのLOCAL_xxx情報を収集し、これに基づいて共有ライブラリをビルドするためのGNU Makefileスクリプトを指します。

NDKビルドシステムが提供する主要な変数とマクロ

NDKビルドシステムは、Android.mkファイル内で利用できる多くの変数と関数マクロを提供します。

ビルドターゲット指定変数

これらの変数は、モジュールをどのような形式でビルドするかを決定します。

  • BUILD_STATIC_LIBRARY: 静的ライブラリ(.aファイル)をビルドします。通常、他の共有ライブラリや実行可能ファイルにリンクするために使用されます。
  • BUILD_SHARED_LIBRARY: 共有ライブラリ(.soファイル)をビルドします。AndroidアプリケーションのAPKに含められるのは通常この形式です。
  • BUILD_EXECUTABLE: ネイティブの実行可能ファイルをビルドします。

プリビルドライブラリの組み込み

すでにコンパイル済みのライブラリ(サードパーティ製ライブラリなど)をプロジェクトに組み込むための変数です。

  • PREBUILT_SHARED_LIBRARY: 事前コンパイル済みの共有ライブラリを指定します。LOCAL_SRC_FILESにはソースファイルではなく、.soファイルのパスを指定します。
  • PREBUILT_STATIC_LIBRARY: 事前コンパイル済みの静的ライブラリを指定します。LOCAL_SRC_FILESには.aファイルのパスを指定します。

例(プリビルド共有ライブラリの場合):

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := prebuilt-foo
LOCAL_SRC_FILES := lib/libfoo.so # LOCAL_PATHからの相対パス
include $(PREBUILT_SHARED_LIBRARY)

パス関連の関数マクロ

これらのマクロは、$(call function-name)の形式で呼び出され、テキスト情報を返します。

  • my-dir: 現在のAndroid.mkファイルが存在するディレクトリのパスを返します。通常、LOCAL_PATHの定義に使用されます。
  • all-subdir-makefiles: my-dirで指定されたディレクトリ内のすべてのサブディレクトリにあるAndroid.mkファイルのリストを返します。これにより、複数のモジュールを効率的にインクルードできます。
  • import-module: 指定されたモジュール名をNDK_MODULE_PATH環境変数から検索し、そのAndroid.mkファイルをインポートします。

モジュール記述変数

これらの変数は、モジュールの特性を詳細に定義するために使用されます。

  • LOCAL_MODULE_FILENAME: (オプション) LOCAL_MODULEで指定されたモジュール名とは異なる出力ファイル名を定義します。
  • LOCAL_CPP_EXTENSION: C++ソースファイルの拡張子を指定します。デフォルトは.cppですが、.cxxなども指定できます。NDK R7以降は複数の拡張子を指定可能です。
    LOCAL_CPP_EXTENSION := .cxx .cpp .cc
  • LOCAL_CPP_FEATURES: C++特定の機能(例: RTTI, 例外処理)を有効にします。
    LOCAL_CPP_FEATURES := rtti exceptions
  • LOCAL_C_INCLUDES: ヘッダーファイルが置かれているディレクトリへのパスリストです。これらのパスはNDKルートディレクトリからの相対パス、またはLOCAL_PATHからの相対パスとして指定できます。
  • LOCAL_CFLAGS / LOCAL_CPPFLAGS (または LOCAL_CXXFLAGS): C/C++コンパイラに渡す追加のフラグを指定します。最適化レベルやデバッグ設定は通常、Application.mkで管理するため、ここでは使用を避けるべきです。インクルードパスを指定する場合は、LOCAL_C_INCLUDESの使用が推奨されます。
  • LOCAL_STATIC_LIBRARIES: 現在のモジュールにリンクする静的ライブラリのリスト。
  • LOCAL_SHARED_LIBRARIES: 現在のモジュールにリンクする共有ライブラリのリスト。
  • LOCAL_WHOLE_STATIC_LIBRARIES: 静的ライブラリを「丸ごと」リンクします。これはリンカの--whole-archiveオプションに相当し、通常の静的ライブラリリンクでは省かれる未使用のシンボルもすべて含めます。
  • LOCAL_LDLIBS: リンカに渡す追加のフラグです。システムライブラリ(例: -llogでAndroidログライブラリ、-lzでzlibライブラリ)の指定によく使われます。
    LOCAL_LDLIBS := -llog -lz
  • LOCAL_ARM_MODE: ARMターゲットコードを16ビットのThumbモード(デフォルト)ではなく、32ビットのARMモードでコンパイルするかどうかを指定します(armを指定)。
    LOCAL_ARM_MODE := arm
  • LOCAL_ARM_NEON: ターゲットがarmeabi-v7aの場合に、浮動小数点演算をNEON命令で最適化するかどうかを指定します。パフォーマンス向上に寄与します。

NDKビルドにおけるディレクトリ構造

ndk-buildコマンドは、デフォルトでプロジェクトルート内のjni/ディレクトリを探し、その中に配置されたAndroid.mkファイルをビルドスクリプトとして使用します。もしAndroid.mkjni/ディレクトリの外にある場合、ndk-buildはプロジェクトディレクトリを見つけられずエラーを発生させることがあります。

この問題を解決するには、ndk-buildコマンド実行時に以下の変数を明示的に指定することで、Android.mkApplication.mkの場所を知らせることができます。

/path/to/android-ndk/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk NDK_APPLICATION_MK=./Application.mk

このコマンドは、Android.mkApplication.mkがカレントディレクトリにある場合に有効です。

実践例:既存の静的ライブラリとC++ソースのリンク

ここでは、事前にコンパイルされた静的ライブラリ(例: mylogger.a)と、それを呼び出すC++ソースファイル(native_main.cpp)をリンクして、最終的にAndroidアプリケーションで使用する共有ライブラリをビルドするAndroid.mkの例を示します。

LOCAL_PATH := $(call my-dir)

# プリビルド静的ライブラリの定義
include $(CLEAR_VARS)
LOCAL_MODULE := myLoggerStaticLib
LOCAL_SRC_FILES := $(LOCAL_PATH)/../prebuilt_libs/armeabi-v7a/mylogger.a
include $(PREBUILT_STATIC_LIBRARY)

# 共有ライブラリの定義
include $(CLEAR_VARS)
LOCAL_MODULE := nativeAppLogic
LOCAL_SRC_FILES := native_main.cpp

# 定義済みの静的ライブラリをリンク
LOCAL_STATIC_LIBRARIES := myLoggerStaticLib

# ARMモードでビルド(32ビット命令)
LOCAL_ARM_MODE := arm

# Androidのログライブラリをリンク
LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)

このAndroid.mkでは、まずmyLoggerStaticLibという名前でプリビルドされた静的ライブラリをビルドシステムに登録しています。次に、nativeAppLogicという名前の共有ライブラリを定義し、そのソースファイルとしてnative_main.cppを指定しています。重要なのは、LOCAL_STATIC_LIBRARIES := myLoggerStaticLibの行で、この共有ライブラリがmyLoggerStaticLibに依存していることをビルドシステムに伝えている点です。また、デバッグ目的などでよく使用されるAndroidのログ機能を有効にするため、-llogをリンカフラグに追加しています。

タグ: Android NDK JNI Android.mk Native Development C++

6月4日 17:41 投稿