cocos2d-x3.17でcmakeできる外部ライブラリを作成する

# 概要

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と同じようなものかな？)

## もう少し詳しく
下に書いた例などは全部以下から引用しました。
https://github.com/cocos2d/cocos2d-x/blob/v3/templates/cpp-template-default/CMakeLists.txt

### set: 変数の設定
set を使うと、変数に値を設定することができます。
基本は以下の形です。

```cmake
set(変数名 設定内容)
```
設定内容が複数ある場合、リストとして格納されます。(その場合、スペースかセミコロンで区切る)

```cmake:例
set(APP_UI_RES proj.ios_mac/mac/Icon.icns proj.ios_mac/mac/Info.plist)
```

```cmake:変数の使い方の例
${変数名}
```

参考: Cmake: 変数
https://qiita.com/mrk_21/items/68470da5d1931915cde2

#### 環境変数
setに入れることでCmake全体の動作に影響させることができます。

|環境変数|説明|
|:--|:--|
|CMAKE_MODULE_PATH|includeなどで参照するパス|
|PROJECT_NAME|project()が呼ばれた最も直近の名前が入る|
|PROJECT_SOURCE_DIR|project()が呼ばれた直近のCMakeLists.txtがあるディレクトリ|
|<name>_SOURCE_DIR|projet(name)で呼ばれたprojectのあるディレクトリ|
|CMAKE_CURRENT_SOURCE_DIR|現在処理中のCMakeListｓ.txtがあるディレクトリ|


### list: リストの操作

list() を使うと、リストの変数をあれこれ操作することができます。

```cmake: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を使うことで、その取り込みを行います。

```cmake:例
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を指定すると、ビルド時のオプションを設定することができます。

```cmake:例
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コマンド
ターミナルとかにメッセージを出力します。

```cmake:例
# message(status, メッセージ...)
message(STATUS "PROJECT_NAME:" ${PROJECT_NAME})
```
statusには以下が指定できます。

|status|説明|出力先|処理|
|:--|:--|:--|:--|
|-|- | STDERR |継続|
| STATUS |INFO | STDOUT |継続|
| WARNING |WARN | STDERR |継続|
| FATAL_ERROR |致命的なエラー | STDERR |終了|

### 条件分岐
Cmakeでは条件分岐もできます。

```cmake:例
if(ANDROID)
   # Androidでの処理
elseif(IOS)
   # iOSでの処理
endif()
```
この条件分岐は、以下のような形で文字列比較もできます。

```cmake:文字列比較の例
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: ライブラリの作成

```cmake:例
add_library(ライブラリ名 SHARED|STATIC 追加するファイル...)
```

`SHARED`を設定すると動的ライブラリが作成でき、 `STATIC`を設定すると静的ライブラリが作成できます。

### add_subdirectory: ディレクトリをCMakeの管理に追加する

つまりは一緒にビルドするようになるっぽい。
ただし、subdirectoryの直下に CMakeLists.txt がないとエラーになるようです。

```cmake:例(2つのsubdirectoryを追加してる)
add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos/platform/android ${ENGINE_BINARY_PATH}/cocos/platform)
```

### target_link_libraries: ライブラリを実行ファイルにリンクする

```cmake:例
# 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: ヘッダーファイルのインクルード
 
ヘッダーを参照するディレクトリを指定するもののようです。

```cmake:例
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

といっても、見たまんまですね。
[ここで設定](https://github.com/cocos2d/cocos2d-x/blob/v3/templates/cpp-template-default/proj.android/app/build.gradle#L34-L39)をして、[ここでcmakeを実行](https://github.com/cocos2d/cocos2d-x/blob/v3/templates/cpp-template-default/proj.android/app/build.gradle#L63-L65)してるようです。


なお、[こちら](https://github.com/cocos2d/cocos2d-x/blob/v3/templates/cpp-template-default/proj.android/app/build.gradle#L36)に指定している `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で連携します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/e0d2ab4f-40c9-bc4f-f2cd-6adc23a97643.png)

結構トライアンドエラーを繰り返したんですが、結論からいうとできました。

```cmake: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:proj.android/CMakeLists.txt
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` に以下のように書いたらエラーになりました。

```cmake: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