概要
Android NDKのビルドっていつからかcmakeを推奨するようになっており、cocos2d-xも3.17.1からはデフォルトがcmakeでビルドするようになっています。そのため全体的にcmakeでビルドできるように移行させたいのですが、今まで使ったことなかったし、外部ライブラリなんかはcmake対応してなかったりすることもあるんでちゃんと自作できるように勉強しました。
想定読者
- (ぼくみたいに)これまでノリで生きてきたからcmake全然知らないって人
- ただとりあえずcocos2d-xでAndroidビルドできる最低限の知識がほしい
今回のゴール
- build.gradleに記載されたcmake関連をある程度理解する
- 外部ライブラリをCMakeList.txtで作成し、cocosのコンパイル時に一緒にビルドする
cmakeって何?
超ざっくりいうとビルドを自動化してくれる子です。ndk-buildも同じくそうでした。
同じならなぜ変えるのでしょう。時代が変わるからでしょうか。
ただマルチプラットフォームに対応してたり、色々な関数使いやすかったりしてcmakeいいらしいです。今ではAndroidもこちらを推奨してます。
知っておいたほうがよさげな内容
多分以下がわかると、cocos2d-xのCMakeLists.txtは読めるようになると思います。
- ビルド対象のディレクトリにCMakeLists.txt があると、cmake コマンドが対象だと認識する
- 関数名(出力先(対象) 入力1 入力2 ...) という形をとる
- 基本は以下が必要なだけ
- cmake_minimum_required: 実行時に必要なcmakeのバージョンを指定
- add_executable: 実行ファイル名とビルドするc / cpp ファイルを指定
- 他以下のようなコマンドはおぼえておくとよさげ
- set: 変数をセット
- いくつか環境変数もある
- list: リストの操作ができる
- project: プロジェクト名。指定すると別プロジェクトから読み込んだりできるようになる
- include: モジュールのインクルードができる
- message: メッセージを出力する
- option: ビルド時のオプションを設定
- target_link_libraries: リンクするライブラリの指定する
- add_library: 静的ライブラリを作成する
- add_subdirectory: 指定したディレクトリもcmake時にビルドする
- そのディレクトリ直下にCMakeLists.txtがないとエラーになる
- target_include_directories: 参照するディレクトリ(include serach pathsと同じようなものかな?)
- set: 変数をセット
もう少し詳しく
下に書いた例などは全部以下から引用しました。
https://github.com/cocos2d/cocos2d-x/blob/v3/templates/cpp-template-default/CMakeLists.txt
set: 変数の設定
set を使うと、変数に値を設定することができます。
基本は以下の形です。
set(変数名 設定内容)
設定内容が複数ある場合、リストとして格納されます。(その場合、スペースかセミコロンで区切る)
set(APP_UI_RES proj.ios_mac/mac/Icon.icns proj.ios_mac/mac/Info.plist)
${変数名}
参考: Cmake: 変数
https://qiita.com/mrk_21/items/68470da5d1931915cde2
環境変数
setに入れることでCmake全体の動作に影響させることができます。
環境変数 | 説明 |
---|---|
CMAKE_MODULE_PATH | includeなどで参照するパス |
PROJECT_NAME | project()が呼ばれた最も直近の名前が入る |
PROJECT_SOURCE_DIR | project()が呼ばれた直近のCMakeLists.txtがあるディレクトリ |
_SOURCE_DIR | projet(name)で呼ばれたprojectのあるディレクトリ |
CMAKE_CURRENT_SOURCE_DIR | 現在処理中のCMakeLists.txtがあるディレクトリ |
list: リストの操作
list() を使うと、リストの変数をあれこれ操作することができます。
# listの追加
list(APPEND 変数名 追加する内容 ...)
# listの長さを第三引数に代入
list(LENGTH 変数名 結果を代入する変数名)
# listのインデックスから値を取得(indexの先頭:0、末尾から検索:-1, -2, ...)
list(GET 変数名 インデックス インデックス2... 結果を代入する変数)
参考: CMake: リスト
https://qiita.com/mrk_21/items/082bae48a5ef2ac1564c
include: モジュールの取り込み
Cmakeでは、「<名前>.cmake」というモジュールを作成することができます。
そしてincludeを使うことで、その取り込みを行います。
set(COCOS2DX_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cocos2d
set(CMAKE_MODULE_PATH ${COCOS2DX_ROOT_PATH}/cmake/Modules/)
include(CocosBuildSet) # ${CMAKE_CURRENT_SOURCE_DIR}/cocos2d/cmake/Modules/CocosBuildSet.cmake を呼び出す
参考: Cmake: モジュール
https://qiita.com/mrk_21/items/ab32a83a12f5d37acc64
optionコマンド
optionを指定すると、ビルド時のオプションを設定することができます。
option(DEBUG_MODE "Debug or Release?" ON)
option(BUILD_LUA_LIBS "Build lua libraries" OFF)
option(BUILD_JS_LIBS "Build js libraries" OFF)
あとは、 cmake
する際に -DDEBUG_MODE=OFF
とかにするといいらしい。
messageコマンド
ターミナルとかにメッセージを出力します。
# message(status, メッセージ...)
message(STATUS "PROJECT_NAME:" ${PROJECT_NAME})
statusには以下が指定できます。
status | 説明 | 出力先 | 処理 |
---|---|---|---|
- | - | STDERR | 継続 |
STATUS | INFO | STDOUT | 継続 |
WARNING | WARN | STDERR | 継続 |
FATAL_ERROR | 致命的なエラー | STDERR | 終了 |
条件分岐
Cmakeでは条件分岐もできます。
if(ANDROID)
# Androidでの処理
elseif(IOS)
# iOSでの処理
endif()
この条件分岐は、以下のような形で文字列比較もできます。
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
set(WINDOWS TRUE)
endif()
参考: CMake: 条件分岐
https://qiita.com/mrk_21/items/49d8802dc63a2791bcc3
クロスプラットフォームの分岐
Cmakeのいいねポイントだそうです。
ビルドする環境によってtoolchainが設定されており、それで自動で「ANDROID」「IOS」などのフラグが立つようです。
このあたり、どういう仕組かよくわかってないんですがAndroidのToolchainは AndroidNDK/build/cmake/android.toolchain.cmake
に存在し、そこを見るとたしかに ANDROIDのフラグがセットされていました。
add_library: ライブラリの作成
add_library(ライブラリ名 SHARED|STATIC 追加するファイル...)
SHARED
を設定すると動的ライブラリが作成でき、 STATIC
を設定すると静的ライブラリが作成できます。
add_subdirectory: ディレクトリをCMakeの管理に追加する
つまりは一緒にビルドするようになるっぽい。
ただし、subdirectoryの直下に CMakeLists.txt がないとエラーになるようです。
add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos/platform/android ${ENGINE_BINARY_PATH}/cocos/platform)
target_link_libraries: ライブラリを実行ファイルにリンクする
# cocos2dのライブラリをリンクしてる
target_link_libraries(${APP_NAME} cocos2d)
# gcc(g++)のリンカ指定(この場合、-Wl,--whole-archive と -Wl,--no-whole-archive に挟まれた静的ライブラリに含まれる全てのオブジェクトファイルがリンクされるようになる(…らしい)
target_link_libraries(${APP_NAME} -Wl,--whole-archive cpp_android_spec -Wl,--no-whole-archive)
target_include_directories: ヘッダーファイルのインクルード
ヘッダーを参照するディレクトリを指定するもののようです。
target_include_directories(${APP_NAME}
PRIVATE Classes
PRIVATE ${COCOS2DX_ROOT_PATH}/cocos/audio/include/
)
build.gradle でどのようにcmakeを呼び出してるのか
次もcocos2d-xのbuild.gradleを参考に勉強します。
https://github.com/cocos2d/cocos2d-x/blob/v3/templates/cpp-template-default/proj.android/app/build.gradle
といっても、見たまんまですね。
ここで設定をして、ここでcmakeを実行してるようです。
なお、こちらに指定している targets(MyGame)
ですが、CmakeLists.txtではここで指定しています。
変数の定義
https://github.com/cocos2d/cocos2d-x/blob/v3/templates/cpp-template-default/CMakeLists.txt#L61
ライブラリの作成
https://github.com/cocos2d/cocos2d-x/blob/v3/templates/cpp-template-default/CMakeLists.txt#L119
うん、完璧理解した。
実際に外部ライブラリを作成して組み込んでみる
はい、やっとここから本題です。
Classesの外側に、common
というフォルダを作り、それをCMakeLists.txtで連携します。
結構トライアンドエラーを繰り返したんですが、結論からいうとできました。
set(COMMON_LIB commonlib)
file(GLOB_RECURSE COMMON_SOURCE RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*.cpp")
file(GLOB_RECURSE COMMON_HEADER RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*.h")
list(APPEND COMMON_SOURCE ${COMMON_HEADER})
add_library(commonlib ${COMMON_SOURCE})
target_link_libraries(commonlib cocos2d)
この file(GLOB_RECURSE COMMON_SOURCE RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*.cpp")
って書き方をすると再帰的に取得することができるそうです。素敵。
参考: AndroidStudio&cmakeでcocos2d-xをビルドする
https://qiita.com/ugonight_nanase/items/0cab13437dfa2ae003bd
あとは、本家のも書き換えます。
cmake_minimum_required(VERSION 3.6)
set(APP_NAME JankenShogiOnline)
project(${APP_NAME})
set(COCOS2DX_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cocos2d)
set(CMAKE_MODULE_PATH ${COCOS2DX_ROOT_PATH}/cmake/Modules/)
include(CocosBuildSet)
add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos ${ENGINE_BINARY_PATH}/cocos/core)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/common) # 追加
# (省略)
# mark app complie info and libs info
set(all_code_files
${GAME_HEADER}
${GAME_SOURCE}
)
if(NOT ANDROID)
add_executable(${APP_NAME} ${all_code_files})
else()
add_library(${APP_NAME} SHARED ${all_code_files})
add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos/platform/android ${ENGINE_BINARY_PATH}/cocos/platform)
target_link_libraries(${APP_NAME} -Wl,--whole-archive cpp_android_spec -Wl,--no-whole-archive)
endif()
target_link_libraries(${APP_NAME} cocos2d)
target_link_libraries(${APP_NAME} commonlib) #追加
target_include_directories(${APP_NAME}
PRIVATE Classes
PRIVATE common/ #追加
PRIVATE ${COCOS2DX_ROOT_PATH}/cocos/audio/include/
)
そしてビルドすると、無事通って共通ライブラリを使用することができました。 簡単ですね。
わかってない部分
common/CMakeLists.txt
に以下のように書いたらエラーになりました。
set(COMMON_LIB commonlib)
add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos ${ENGINE_BINARY_PATH}/cocos/core) # これを追加
file(GLOB_RECURSE COMMON_SOURCE RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*.cpp")
file(GLOB_RECURSE COMMON_HEADER RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*.h")
list(APPEND COMMON_SOURCE ${COMMON_HEADER})
add_library(commonlib ${COMMON_SOURCE})
target_link_libraries(commonlib cocos2d)
理由は簡単で、すでに /proj.android/CMakeLists.txt
で cocosを add_subdirectory
してたのに、上記でもしてたという重複を示すエラーでした。
なので、その行をなくせば通ったんですが、気になってるのは「気をつける以外に回避策ない?」って部分です。
(まだちゃんと調べてないけど)
誰か知ってる人いたら教えて下さい(´・ω・`)
所感
案外簡単とか言ってはいけない。C++の闇は人間じゃ簡単には理解できない。
参考
@mrk_21 さんの記事一覧
https://qiita.com/mrk_21
AndroidStudio&cmakeでcocos2d-xをビルドする
https://qiita.com/ugonight_nanase/items/0cab13437dfa2ae003bd
プロジェクトへの C / C++ コードの追加
https://developer.android.com/studio/projects/add-native-code?hl=JA