AtomicGameEngine - CMake(1)
大学のテスト期間で前回の投稿から大分期間が空いてしまいました。なんとか落単を一科目くらいに抑えられたかなって感じです(笑)。1週間くらい前から夏休みに入ってたんですけど、アメドラのBreakingBadとかBetterCallSaulとかThe 100とか、あと洋画いくつか、見たかったやつをイッキ見し続けてたら一週間も経ってたという。。。
とりあえず前回の続きからです。
CMake
前回の記事で、
Build_AtomicEditor.sh
が
/Artifacts/Build/Mac/
から、
cmake ../../../ -DATOMIC_DEV_BUILD=0 -G Xcode
↓
xcodebuild -target GenerateScriptBindings -target AtomicNETNative -configuration RELEASE
↓
xcodebuild -target AtomicEditor -target AtomicPlayer -configuration RELEASE
の順で、実行することは分かった。
で、今回はcmakeで何をしているのかを見て行きます。cmakeから、クロスプラットフォームなアプリケーションをどのようにしてビルドするのか参考になるかなと思ってます。
/CMakeLists.txt
cmake理解する。
"cmake ../../../ -DATOMIC_DEV_BUILD=0 -G Xcode"
はAtomicのプロジェクトのルートフォルダにある、CMakeLists.txt
でcmakeを実行する。-G Xcode
はxcode用のプロジェクトを作成する。-DATOMIC_DEV_BUILD=0
は、CMakeLists.txt
中で使うATOMIC_DEV_BUILD
って変数を定義して0をセットしているだけ。message()関数とか使用しながら確認する。massage(<mode> “text”)で、<mode> にFATAL_ERROR
とか指定したらエラーメッセージとして吐き出して終了してくれる。便利。
project(Atomic) #プロジェクト名 cmake_minimum_required(VERSION 2.8.12.1) #最低限必要なcmakeのバージョン set(ATOMIC_SOURCE_DIR ${Atomic_SOURCE_DIR}) # 'Atomic'はプロジェクト名と一致させる set(CMAKE_MODULE_PATH ${ATOMIC_SOURCE_DIR}/Build/CMake/Modules) # include()がモジュールを探しにいくパスを保持するCMAKE_MODULE_PATHに/Build/CMake/Modulesを設定 include(AtomicGit) include(AtomicUtils) include(AtomicCommon)
include()メソッドは、CMAKE_MODULE_PATHの場所にある拡張子が".cmake"のモジュールをそのまま実行してくれる。include(AtomicGit)だと、 ‘AtomicGit.cmake'をそのまま実行
/Build/CMake/Modules/AtomicGit.cmake
サブモジュール関係が初期化されてなかったら、gitを探して初期化(git submodule update –initしてくれる)
/Build/CMake/Modules/AtomicUtils.cmake
macro(名前 引数) ~ endmacro()で、マクロを定義。名前(引数)で実行できる。このモジュールではwindwosとlinuxの場合に何か処理を加える.
/Build/CMake/Modules/AtomicCommon.cmake
行数多いー、ってよくみたらほとんどマクロ定義 最初の20行くらいのコメントはコピーライトの話だけ Urho3dってなんだろう。stackoverflowにええ感じの説明があった。
include(CMakeParseArguments)
そんなモジュールないんですが。。と、思ったら、MakeParseArgumentsはcmakeにもとから入ってるモジュールだった。cmake --help-module-list
コマンドで確認できる。
で、ファイルのパスは多分これ /usr/local/Cellar/cmake/3.8.2/share/cmake/Modules/CMakeParseArguments.cmake
中身みたらコメントアウトだけだったので無視。その処理が以降の処理に依存するかどうかがとりあえず分かればいい。
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DATOMIC_DEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DATOMIC_DEBUG")
CMAKE_C_FLAGS_DEBUGとCMAKE_CXX_FLAGS_DEBUGはデフォルトでは-g
のデバッグ情報出力のオプションだけだけど、ATOMIC_DEBUGっていうマクロを追加するように更新。
if (CMAKE_SIZEOF_VOID_P MATCHES 8) set(ATOMIC_PROJECT_ARCH "x86_64") set(ATOMIC_PROJECT_ARCH_SHORT "x64") set(ATOMIC_PROJECT_ARCH_BITS "64") set(ATOMIC_64BIT 1) else () set(ATOMIC_PROJECT_ARCH "x86") set(ATOMIC_PROJECT_ARCH_SHORT "x86") set(ATOMIC_PROJECT_ARCH_BITS "32") set(ATOMIC_64BIT 0) endif ()
マシンのプロセッサに応じてビルドの仕方を変えるため?だと思う。また、後々出てくるでしょ。
以下がその後で定義されているマクロ。これも、出てきた時にまた確認したらいいので一旦パス。
macro(define_source_files) macro(setup_target) macro(check_source_files) macro(setup_library) macro(setup_executable) macro(replace_in_list substring replacement variable_list) macro(msvc_set_runtime runtime_flag) macro(deploy_qt_dlls target)
/CMakeLists.txt
で、/CMakeLists.txtに戻って、
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") set (ATOMIC_RELEASE_OFF OFF) set (ATOMIC_RELEASE_ON ON) else () set (ATOMIC_RELEASE_OFF ON) set (ATOMIC_RELEASE_ON OFF) endif ()
デバッグビルドかリリースビルドかのフラグを更新
add_definitions(-DATOMIC_ROOT_SOURCE_DIR="${ATOMIC_SOURCE_DIR}" -DATOMIC_ROOT_BUILD_DIR="${CMAKE_BINARY_DIR}")
こいつは、最終的にコンパイルするときのオプションを追加してる。よく使われてるっぽいのが、add_definitions(-Wall -std=c++14)みたいな感じ。
if (NOT DEFINED ATOMIC_DEV_BUILD) set(ATOMIC_DEV_BUILD 1) ENDIF () if (ATOMIC_DEV_BUILD) add_definitions("-DATOMIC_DEV_BUILD=1") endif ()
これは、cmakeの実行時のオプションで、-DATOMIC_DEV_BUILDがなかったら、1にして定義する感じ。 でATOMIC_DEV_BUILDが0じゃなかったら、コンパイルオプションで-DATOMIC_DEV_BUILD=1がつく感じ
で、次がinclude(AtomicPlatform)
なんで、
/Build/CMake/Modules/AtomicPlatform.cmake
まずこれ
set(ATOMIC_DYNAMIC_RUNTIME OFF CACHE BOOL "Build engine as shared library and link dynamically to system runtime.")
まぁまず、ATOMIC_DYNAMIC_RUNTIMEをOFFで定義して、bool型のキャッシュ変数に指定して、変数にBuild engine as shared library and link dynamically to system runtime.
っていうラベルをつけます、ってこと。この変数はソース中でどこからでも参照できて、cmake実行した時に生成されるCMakeCache.txt
に保存される。CMakeCache.txt
はcmakeを再度実行する際に以前にされた同じ処理を繰り返さないようにして処理時間を短縮するためのものっぽい。
if (WIN32) include(AtomicWindows) elseif (APPLE) if (IOS) include(AtomicIOS) else () include(AtomicMac) endif () elseif (LINUX) include(AtomicLinux) elseif (ANDROID) include(AtomicAndroid) elseif (WEB) include(AtomicWeb) endif ()
ビルド?の環境によって異なる処理をしたいっぽい。Macで動かしてる場合、APPLE以外の変数は定義されてないので、include(AtomicMac)
が実行される。真偽値は0も偽になるし、定義されていない場合も偽になるっぽい。
/Build/CMake/Modules/AtomicMac.cmake
AtomicMac.cmake
見ていくと
set(JAVASCRIPT_BINDINGS_PLATFORM "MACOSX") set(ATOMIC_NODE_JAKE Build/Mac/node/node Build/node_modules/jake/bin/cli.js -f Build/Scripts/Bootstrap.js)
二つ目のやつはリストになってる
include(BundleUtilities) include(AtomicDesktop)
この部分で、AtomicLinux.cmake
、AtomicMac.cmake
、AtomicWindows.cmake
のうちで、include(BundleUtilities)
があるのは、AtomicMac.cmake
だけ。なぜか?それは、MacとかIOSのアプリケーションはアプリケーションバンドルっていう形式で、そのアプリケーションバンドルをcmakeで作りやすくするためのものらしい。
/usr/local/Cellar/cmake/3.8.2/share/cmake/Modules/BundleUtilities.cmake
やってることは、ひたすら関数定義なのでスルー
/Build/CMake/Modules/AtomicDesktop.cmake
set(ATOMIC_DESKTOP TRUE) # Check whether the CEF submodule is available if (NOT DEFINED ATOMIC_WEBVIEW AND EXISTS ${ATOMIC_SOURCE_DIR}/Submodules/CEF) #Check if CEF got pulled by looking if the foldes is empty file(GLOB CEF_FILES ${ATOMIC_SOURCE_DIR}/Submodules/CEF/*) list(LENGTH CEF_FILES CEF_FILES_LEN) if (CEF_FILES_LEN EQUAL 0) message(STATUS "Initialising CEF submodule") find_package(Git REQUIRED) if (GIT_FOUND) execute_process(COMMAND git submodule update --init --remote) else () message(STATUS "Could not find git in your Path. Please install git") endif (GIT_FOUND) endif () set(ATOMIC_WEBVIEW TRUE) add_definitions(-DATOMIC_WEBVIEW) endif ()
こいつは、ATOMIC_DESKTOPってフラグを定義してから、サブモジュールのCEF(Chromium Embedded Framework)ってやつが利用可能(パスがある/gitが見つかる)かどうかをチェックする。CEFは、電子商取引の支払い、Facebookのソーシャルグラフ、ビデオストリーミング、およびその他の多くのユースケースにアクセスするために使うって、AtomicGameEngineのホームページに書いてた。
/Build/CMake/Modules/AtomicMac.cmake
で、AtomicMac.cmake
の方に戻って、
# for CEF3 set(PROJECT_ARCH "x86_64") set(CMAKE_OSX_ARCHITECTURES "x86_64") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9")
if (CMAKE_GENERATOR STREQUAL "Xcode") set(ATOMIC_XCODE 1) else () # When not using XCode, linker takes a long time, which this flag seems to be being passed during xcode builds set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Xlinker -no_deduplicate") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Xlinker -no_deduplicate") endif ()
cmakeを実行するときに、-G Xcodeのオプションをつけると、CMAKE_GENERATORに"Xcode"って値が入る。
-Xlinker -no_deduplicate
ってなんだろう。調べるとXlinkerってのがgccのオプションで、
このサイトをみると、
ld に渡すオプションを指定する。1つのトークンにつき -Xlinker を 一回使わねばならない。たとえば ld に“-rpath /usr/local/lib”を 渡したいときは、-Xlinker -rpath -Xlinker /usr/local/lib と指定する。
とあった。で、
ldってなに?、と思って調べると、gccはプリプロセスにcpp、コンパイルにcc1、アセンブルにas、リンキングにldを使ってやってるっぽくて(clangの場合は知らない)、でそのldのコマンドが実行されるときにオプションとして渡したいものがあったら、gccコマンドの実行の際に -Xlinkerってのを使って後ろにldのオプションをつけると、リンキングの時にそのオプション付きでldをしてくれるっていうスグレモノ。
で、-no_deduplicate
オプションが何をするのかっていうと、Don't run deduplication pass in Linker
ってman
コマンドで調べると「重複削除のパスを実行しない」?調べても特にヒットしないのでパス。次いく。まぁ、Xcodeじゃない場合の何らかの処理です。ソースのコメントを見ても、「Xcodeを使わないとき、リンカは長い時間がかかる。このフラグはxcodeビルド中に渡しているようだ。」っていう、コメントをつけた人自身も曖昧な感じ(笑)。
とりあえず、MacでXcodeを使わずにビルドするなら、CMAKE_EXE_LINKER_FLAGS
とCMAKE_SHARED_LINKER_FLAGAS
にXlinker -no_deduplicate
を付け足すんだということだ。
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -stdlib=libc++")
c++プログラムをコンパイルするときのオプションで自動的に付加されるのがCMAKE_CXX_FLAGSの値。c言語の場合はCMAKE_C_FLAGS
offsetof(type, member) っていう、構造体 type 中にあるフィールド member の、構造体先頭からのオフセットを size_t 型で返すマクロが存在するけど、それをnon-POD型の変数に対して行うとコンパイル時に警告が出る。そんで、その警告を無視するオプションが-Wno-invalid-offsetof
。offsetof()とか、non-POD型とかはこのあたり参考にした。
libc++はappleで作られた標準c++ライブラリで、従来までのlibstdc++に代わって標準化されるそう。
で、clangではlibc++もlibstdc++のどちらでも使えて、ここではlibc++を指定している。
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -framework AudioToolbox -framework Carbon -framework Cocoa -framework CoreAudio -framework CoreVideo -framework ForceFeedback -framework IOKit -framework OpenGL -framework CoreServices -framework Security -framework SystemConfiguration")
ココらへんのframeworkはOSXのframeworkで(ここにリストが)、AudioToolboxは音関係、Carbonは移植関係、CocoaはmacOSアプリケーションの基礎的なgui関係、CoreAudioはmacOSとIOSに搭載されているオーディオとMIDIを扱う機能の総称、Corevideoは何か動画系のframework、ForceFeedbackはフォースフィールドバックのサポート、IOKitはデバイス系の何か、OpenGLはOpenGL、CoreServicesはよくわからん、で、SecurityもSystemConfigurationもまぁ後々でてくるでしょってことで今はスルー。
/Build/CMake/Modules/AtomicPlatform.cmake
で、AtomicPlatform.cmake
の方に戻って、
message(STATUS "Atomic platform: ${JAVASCRIPT_BINDINGS_PLATFORM}")
JAVASCRIPT_BINDINGS_PLATFORM
にはAtomicMac.cmake
でセットした通り、MACOSX
が入ってた。
set(JAVASCRIPT_BINDINGS_PLATFORM_ROOT "${ATOMIC_SOURCE_DIR}/Artifacts/Build/Source/Generated") if (NOT EXISTS "${JAVASCRIPT_BINDINGS_PLATFORM_ROOT}/Javascript") execute_process(COMMAND ${ATOMIC_NODE_JAKE};build:precreateScriptBindings[${JAVASCRIPT_BINDINGS_PLATFORM}] WORKING_DIRECTORY "${ATOMIC_SOURCE_DIR}") endif ()
execute_process()でコマンド実行、COMMAND
の後ろにパスを書いて、WORKING_DIRECTORYの後ろにコマンドを実行するワーキングディレクトリを指定。で、ATOMIC_NODE_JAKEに何が入ってるかというと、Build/Mac/node/node
,Build/node_modules/jake/bin/cli.js
,-f
,Build/Scripts/Bootstrap.js
がリストになってた。そういえばこれも、AtomicMac.cmake
でセットしてたな。つまり、Build/Mac/node/node Build/node_modules/jake/bin/cli.js -f Build/Scripts/Bootstrap.js ;build:precreateScriptBindings[MACOSX]
がプロジェクトフォルダのルートで実行される。
で、何をしてるかっていうと、まずrm -rf /Artifacts/Build/Source/Generated/
して、/Script/Packages/のjson
ファイルを読み込んでいき、/Source/にあるソースファイル群を/Artifacts/Build/Source/Generated/に転送しよるっぽい
file(GLOB_RECURSE JAVASCRIPT_BINDINGS_NATIVE_FILENAMES ${JAVASCRIPT_BINDINGS_PLATFORM_ROOT}/*.cpp ${JAVASCRIPT_BINDINGS_PLATFORM_ROOT}/*.h)
これは、/Artifacts/Build/Source/Generated/以下にある拡張子cppとhのファイル全てのリストをJAVASCRIPT_BINDINGS_NATIVE_FILENAMESに入れる。これ以降この変数が使われることがないので、なんのための処理かは不明。作成者の確認用だろうね多分。
/CMakeLists.txt
で、やっと、CMakeLists.txt
に戻る
find_program(CLDOC cldoc) if (CLDOC) add_custom_target(docs DEPENDS AtomicEngineDocs) endif ()
find_program()
は、探したいファイルの名前をcldocにして、ファイルが見つかればそのファイルのパスがCLDOCに入るっていう処理。動かした結果見つからなかったぽい。
if (ATOMIC_WEBVIEW) message(FATAL_ERROR "") if (APPLE) if (POLICY CMP0037) # new cmake doesn't like creating framework whose name has spaces # which CEF3 scripts (including shell) currently require on OSX cmake_policy(SET CMP0037 OLD) endif () set(CEF_ROOT "${ATOMIC_SOURCE_DIR}/Submodules/CEF/MacOSX") elseif (WIN32) if (ATOMIC_PROJECT_ARCH STREQUAL "x86") else () set(CEF_ROOT "${ATOMIC_SOURCE_DIR}/Submodules/CEF/Windows/64bit") endif () else () set(CEF_ROOT "${ATOMIC_SOURCE_DIR}/Submodules/CEF/Linux") endif () set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CEF_ROOT}/cmake") find_package(CEF REQUIRED) include_directories(${CEF_ROOT}) add_subdirectory(${CEF_LIBCEF_DLL_WRAPPER_PATH} libcef_dll_wrapper) endif ()
この部分は、CEF関係のことで、'find_package(CEF REQUIRED)‘はCMAKE_MODULE_PATHのディレクトリでFIndCEF.cmake
ってやつを読み込んで実行してる
include_directories(<path>)
はgccとかで、ヘッダファイルのフォルダとか指定する-l<path>
のオプションのためのメソッド。include_directories(
CEF_LIBCEF_DLL_WRAPPER_PATH
は/Submodules/CEF/MacOSX/libcef_dll
が入ってた。でadd_subdirectory()なんで、そのディレクトリのCMakeLists.txtが実行されてる。まぁ、CEFの準備ってことでスルー
で、最後のブロックで
add_subdirectory(Source) if (ATOMIC_DESKTOP AND ATOMIC_DEV_BUILD) if (NOT DEFINED ATOMIC_CPLUSPLUS_EXAMPLES) set(ATOMIC_CPLUSPLUS_EXAMPLES 1) endif () if (ATOMIC_CPLUSPLUS_EXAMPLES) add_subdirectory(Submodules/AtomicExamples/FeatureExamples/CPlusPlus) endif () endif ()
があるけど、if文から下は、ATOMIC_DEV_BUILDが0でとりあえず実行されないからスルー。またいつか見る。
で、add_subdirectory(Source)
で、やっとソースファイルに触れていく感じかな。