Help us understand the problem. What is going on with this article?

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 を使うと、変数に値を設定することができます。
基本は以下の形です。

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があるディレクトリ
SOURCEDIR projet(name)で呼ばれたprojectのあるディレクトリ
CMAKE_CURRENT_SOURCE_DIR 現在処理中のCMakeLists.txtがあるディレクトリ

list: リストの操作

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 がないとエラーになるようです。

例(2つのsubdirectoryを追加してる)
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で連携します。

image.png

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

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

あとは、本家のも書き換えます。

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 に以下のように書いたらエラーになりました。

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away