[Android][NDK]ndk-buildについて

<p>業務で調べたことを自分自身の理解を含めるために文書にする。</p>

<p>AndroidのNDKでビルドする主な方法は以下の3つ。</p>

<ul>
<li>ndk-build</li>
<li>CMake</li>
<li>スタンドアロン・ツールチェーン</li>
</ul>

<p>今主流なのはCMakeだが、取り組んでいる業務で使用するのがndk-buildなため、ndk-buildについてまとめる。</p>

<p>ndk-buildスクリプトは、GNU Makeを利用したビルドシステムだ。ndk-buildスクリプトは、以下のコマンドを実行することと同等。</p>

<pre><code>$ make -f /path/to/NDK/build/core/build-local.mk <パラメータ>
</code></pre>

<p>ndk-buildスクリプトは、以下のように呼び出す。</p>

<pre><code>$ cd /path/to/project
$ /path/to/NDK/ndk-build
</code></pre>

Android.mk
<p>ソースとライブラリの情報を記述するもので、jni/ディレクトリ配下（サブディレクトリ配下でも問題ない）に置かれる。</p>

<p>以下は、Android.mkの例。</p>

<pre><code># Android.mkが置かれているパスを設定。
LOCAL_PATH := $(call my-dir)
 
# LOCAL_PATH 以外のLOCAL_XXX変数をクリアする。
include $(CLEAR_VARS)
 
# モジュール名を設定（生成されるライブラリ名は libhello-jni.so となる）。
LOCAL_MODULE := hello-jni
 
# 生成するライブラリ名を libhello.so に設定する。
LOCAL_MODULE_FILENAME := libhello
 
# ソースファイルを設定
LOCAL_SRC_FILES := hello-ini.c utils.c
 
# 共有ライブラリを生成する。
include $(BUILD_SHARED_LIBRARY)
</code></pre>

<p>共有ライブラリ生成の記述を以下に差し替えると静的ライブラリが生成される。</p>

<pre><code>include $(BUILD_STATIC_LIBRARY)
</code></pre>

<p>ターゲットとなるCPUとアーキテクチャは、Application.mkのAPP_ABI変数で指定され、ABIごとにAndroid.mkが評価される。</p>

<pre><code>APP_ABI := armeabi-v7a arm64-v8a x86
</code></pre>

<p>TARGET_ARCH変数に、CPUファミリー（arm, arm64, x86, x86_64）が設定される。</p>

<p>TARGET_PLATFORM変数に、Android API レベル番号（例えば、"android-22"）が設定される。</p>

<p>TARGET_ARCH_ABI変数に、CPUとアーキテクチャ（armeabi-v7a, arm64-v8a, x86, x86_64）が設定される。</p>

<p>TARGET_ABI変数に、Android API レベルとABI（例えば、"android-22-arm64-v8a"）が設定される。</p>

<p>インクルードファイルの検索パスの設定は以下の通り。</p>

<pre><code>LOCAL_C_INCLUDES := sources/foo
</code></pre>

<p>複数の値が設定できる変数では、以下のように値が追加できる。</p>

<pre><code>LOCAL_C_INCLUDES += $(LOCAL_PATH)/ディレクトリ名/foo
</code></pre>

<p>依存している静的ライブラリの設定。<br />現在のモジュールが共有ライブラリなら、この共有ライブラリを利用する先に自動で変数に設定された静的ライブラリがリンクされる。<br />現在のモジュールが静的ライブラリならリンクされない。</p>

<pre><code>LOCAL_STATIC_LIBRARIES += hello-jni
</code></pre>

<p>cocos2d-xのように、C/C++コードをlibgame.soという共有ライブラリに固め、Java/Kotlinコードのビルドで繋げる場合、この共有ライブラリが依存している静的ライブラリを取り込むため、以下の変数に静的ライブラリを設定する。</p>

<pre><code>LOCAL_WHOLE_STATIC_LIBRARIES += hello-jni
</code></pre>

<p>現在のモジュールを利用先に変数の値を渡す場合は、LOCAL_EXPORT_XXX変数を利用する。</p>

<pre><code>LOCAL_EXPORT_CFLAGS
LOCAL_EXPORT_CPPFLAGS
LOCAL_EXPORT_C_INCLUDES
LOCAL_EXPORT_LDFLAGS
LOCAL_EXPORT_LDLIBS
</code></pre>

<p>Android.mkの例のmy-dirのように、NDKはGNU Make関数マクロが用意している。</p>

<p>関数マクロは、他のAndroid.mk呼び出しの影響を受けるため、Android.mkの末尾に記述する。以下の例では、my-dirが返す値が、1個目と2個目で異なっている。</p>

<pre><code>LOCAL_PATH := $(call my-dir)
include $(LOCAL_PATH)/foo/Android.mk
LOCAL_PATH := $(call my-dir)
</code></pre>

<p>import-moduleマクロ関数を利用すると、任意のモジュールのAndroid.mkをモジュール名で指定できる。</p>

<pre><code>$(call import-module,モジュール名)
</code></pre>

<p>上記の例では、NDK_MODULE_PATH環境変数が参照するディレクトリで設定した名前でモジュールを検索して、そのモジュールのAndroid.mkがインクルードされる。</p>

<p>ビルド済みライブラリを提供する場合のAndroid.mkの例。</p>

<pre><code>LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include
LOCAL_SRC_FILES := ../libs/libfoo.so
include $(PREBUILT_SHARED_LIBRARY)
</code></pre>

<p>上記のようにヘッダーファイルのパスをLOCAL_EXPORT_C_INCLUDESに設定しておくと、利用する側は楽になる。</p>

<p>静的ライブラリの場合は、PREBUILT_SHARED_LIBRARYがPREBUILT_STATIC_LIBRARYになる。</p>

<pre><code>include $(PREBUILT_STATIC_LIBRARY)
</code></pre>

<p>ビルド済みライブラリを利用するAndroid.mkの例。</p>

<pre><code>include $(CLEAR_VARS)
LOCAL_MODULE := foo-user
LOCAL_SRC_FILES := foo-user.c
LOCAL_SHARED_LIBRARIES := foo-prebuilt
include $(BUILD_SHARED_LIBRARY)
</code></pre>

<p>ビルド済みライブラリが静的ライブラリの場合は、LOCAL_SHARED_LIBRARIESがLOCAL_STATIC_LIBRARIESになる。</p>

【関連情報】
<a href="https://developer.android.com/ndk/guides/ndk-build?hl=ja">ndk-build</a><br />
<a href="https://cocoa-kanto.connpass.com/">Cocoa.swift</a><br />
<a href="https://www.facebook.com/groups/cocoa.kanto/">Cocoa勉強会 関東</a><br />
<a href="http://www.bitz.co.jp/weblog/">Cocoa練習帳</a><br />