KK PROJEKT RED 勉強ブログ

ゲーム好き技術系大学生のメモのようなもの

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 Xcodexcode用のプロジェクトを作成する。-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.cmakeAtomicMac.cmakeAtomicWindows.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ビルド中に渡しているようだ。」っていう、コメントをつけた人自身も曖昧な感じ(笑)。 とりあえず、MacXcodeを使わずにビルドするなら、CMAKE_EXE_LINKER_FLAGSCMAKE_SHARED_LINKER_FLAGASXlinker -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は移植関係、CocoamacOSアプリケーションの基礎的なgui関係、CoreAudioはmacOSIOSに搭載されているオーディオとMIDIを扱う機能の総称、Corevideoは何か動画系のframework、ForceFeedbackはフォースフィールドバックのサポート、IOKitはデバイス系の何か、OpenGLOpenGL、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)で、やっとソースファイルに触れていく感じかな。

今回はこんなもんで終わります。 次回は、/Source/CMakeLists.txt