Skip to content

CMakeを使用した実行手順

Kasugaccho edited this page May 19, 2019 · 2 revisions

以下、yumetodoさんによる解説。

必要なもの

cmakeの3.10.0以降というわりと新しめのが必要です。これはVS2017以降での/std:に対応するためです。 MSVC standard version switches (#16482) · Issues · CMake / CMake · GitLab aptとかyumとかで落ちてくるのだと古いかもしれません。公式サイトから拾ってきてください。linuxでインストールするときはporgを使うとなんちゃってパッケージ管理ができてよいです。Windowsではmsys2のpacmanかchocolateyで入れると良いです。

build方法

DungeonTemplateLibrary/

に移動します。

mkdir build
cd build
cmake ..
make ci

でサンプルのコンパイル&実行ができます。コンパイルだけしたい場合はmake sampleです。

WindowsのMSYS2環境下ではcmakeのオプションに-G "MSYS Makefiles"が必要です。

makeには必要に応じて-j4などをつけて並列化してください。自分の環境では3倍も時間が短縮できました。

特にWindowsだとGNU Makeがちょっと遅いのでかわりにNinjaを使って

mkdir build
cd build
cmake -G "Ninja" ..
ninja ci -j4

などとできます。

Release buildがしたい場合はcmakeに-DCMAKE_BUILD_TYPE=Releaseを渡してください。

標準C++のバージョンを変えたい場合はDCMAKE_CXX_STANDARD=14とかしてください。14より前を入れるとエラーになるようにしています。何も渡さないといい感じに新しいやつを使うようになっています(C++20は勝手には有効になりません)C++11も使いたいという場合は/cmake/modules/DecideCXXStandard.cmake

    if(CMAKE_CXX_STANDARD LESS 14)
        message(FATAL_ERROR "Specify C++14 or later")
    endif()

をいじってください。

gccじゃなくてclangが使いたいんじゃ、という場合はcmakeに-DCMAKE_CXX_COMPILER=clang++を渡してください。

msvcがいいんじゃって場合はcmake-guiからいい感じにやってください(説明放棄。Clang CodeGenやfafnirで入れたclangを呼び出す場合も image -Tで当該toolset名を渡してください。自分はやってないけど動くんじゃないですかね?

使う機会があるかは謎ですが一応make installできるようになっています。prefixはcmakeに-DCMAKE_INSTALL_PREFIX=/path/to/prefixを渡してください。

cmakeの構築の方針と解説

この項はcmakeの公式documentを参考に読みすすめてください。

xxx-config.cmake

cmakeの構築の方針ですが、まずライブラリはxxx-config.cmakeを提供するべし、という最近の原則に則っています。ナニソレオイシイノという場合は お手軽な xxx-config.cmake の作成方法 を参照してください。

ただ、ヘッダーオンリーなので自動生成はかえって面倒だなと思っていたところ、C++の単体テストフレームワークであるiutest https://github.com/srz-zumix/iutest がいい感じにしていたので採用しました。

xxx-config.cmake、つまり今回はdtl-config.cmakeを手動で書くのですが、これがメンテナンスされているという保証が必要です。今回で言えば、サンプルコードをビルドする時のinclude pathの設定を多少冗長でもわざわざこれを利用して設定するようにします。

まず

set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)

のようにしてシステムに既にインストールされているDTLを見ないようにした上で、

find_path(DTL_ROOT_DIR
  NAMES include/DTL.hpp
  HINTS
    ${CMAKE_CURRENT_SOURCE_DIR}/../../../
  NO_DEFAULT_PATH
)

DTLのporject rootを探し(set(DTL_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../)で良かった気もするが一応)

set(DTL_DIR ${DTL_ROOT_DIR})
list(APPEND CMAKE_PREFIX_PATH ${DTL_ROOT_DIR})
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../)
list(APPEND CMAKE_PREFIX_PATH ${DTL_ROOT_DIR}/cmake/modules)
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/modules)
list(APPEND CMAKE_MODULE_PATH ${DTL_ROOT_DIR}/cmake/modules)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/modules)
find_package(DTL REQUIRED)

のようにdtl-config.cmakeを呼び出してDTL_INCLUDE_DIR変数にinclude dirが入るようにしました。

cmakeの階層構造

cmakeの変数のスコープはかなりアクロバティックで、原則directoryごとにスコープがあります。 ref: CMake: 変数のスコープ - Qiita

今回

  • Sample
    • DTL
      • Shape
      • Storage

のような階層構造になっているので、これに合わせるために各directoryにCMakeLists.txtを作成し、add_subdirectoryで下のdirectoryを指定するようにしています。

原則project rootのCMakeLists.txtをみてコンパイルしますが(上の手順もそれで解説している)、一応それぞれ独立して動けるようにはなっているはずです(非推奨)。

sampleをcmakeから実行する仕組み

Shape

これは出力ファイルがないので話が簡単で、add_custom_targetCOMMANDadd_executableで指定した名前${target}を指定するだけで実行してくれます。あとは

add_custom_target(sample_DTL_Shape)
add_custom_target(run_sample_DTL_Shape)

として、

  add_dependencies(sample_DTL_Shape
    ${target}
  )
  add_dependencies(run_sample_DTL_Shape
    run_${target}
  )

でグループ化しています。上の階層でもadd_custom_targetでターゲットをグループ化することで最終的にsamplerun_sampleに集約されて、将来testが書かれることを想定してこれとまとめるためのciを用意しています(ciは今の所run_sampleしか実行しない)

Storage

これは実行時に出力ファイルがあります。

幸いにして出力ファイルが1つだけだったので良かったですが、これが複数あったら生成ファイルをmake cleanに引っ掛けるために血反吐を吐きつつ汚いハックが必要でした

でどうしたかというと

  add_custom_target(run_${target}
    SOURCES ${output_name}
    COMMENT "Execute produced executable."
  )
  add_custom_command(
    OUTPUT ${output_name}
    COMMAND ${target}
    WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
    DEPENDS ${target}
  )

のようにadd_custom_targetadd_custom_commandの合わせ技で乗り切っています。 ref: CMake: カスタムターゲットによるグループ化 - Qiita

あとは上と同じですね。

ところで${output_name}はどうやって生成しているのでしょうか?

FileXXX.cppというファイルをコンパイルして実行するとfile_sample.xxxが生成されるという規則性を利用します(このための #17 )

  string(REGEX REPLACE "File([A-Z]+)" "file_sample.\\1" output_name_base ${target})
  string(TOLOWER ${output_name_base} output_name)

つまり正規表現でXXXを引っ張って、次に小文字に変換しています。一行で書けそうな気もしますが、私の技術力では無理です(ぇ。

Clone this wiki locally