CMake導入
前提
経緯
PROCESS WARPのビルドは元々Makefileを手作りしていました。 Linux/MacOSXごとにライブラリのパス、デバック用かリリース用かなどををハードコードしており、Makefileのメンテナンスの必要がありました。 他のOSSプロジェクトでよしなに動いているので、どうやっているのか調べたら、Automake - GNU Project - Free Software Foundation (FSF)、CMakeといツールを使っている場合が多かったです。特徴を調べてCMakeを使うことにしました。
CMakeの特徴
- Unix、XCode、Windowsなどクロスプラットフォームでのビルドを補助する。
- ビルドを直接行うわけでなく、Makefile、XCode、VisualStudioのプロジェクトを出力するなど、各ネイティブ開発環境との親和性が高そう。(automakeにはないメリット)
- ソースとは独立した場所でビルドが可能なので、ソースを格納している場所を汚さずにすむ。
使い方は参考ページの方がよくわかるので、自分が特に引っかかったところだけ書きます。
利用ライブラリのコンパイルオプションの取得方法
C/C++のプログラムのコンパイルにはコンパイルオプション(依存するヘッダファイルの場所、最適化オプション、プログラムに渡す定義など)とリンカオプション(依存するライブラリファイルの場所、リンカオプション)が必要になります。それぞれ利用ライブラリに合せて追加する必要があります。 CMakeでは調べた限り以下の3つの方法でオプションを集めることができます。
find_package
依存しているライブラリがFind◯◯.cmakeというファイルを含んでいる場合、ソレを読み込んでオプションを取得することができます。大体のライブラリで使い方は統一されているようですが、LLVMなど複雑なライブラリは、必要な引数を渡すとソレに対応するオプションが戻ってくるなどの少し複雑な動作をする場合もあります。
find_package(LLVM REQUIRED CONFIG) # llvmのオプションを取得する処理(個別) include_directories(${LLVM_INCLUDE_DIRS}) llvm_map_components_to_libnames(llvm_libs support core irreader) list(APPEND extra_libs ${llvm_libs}) add_definitions(${LLVM_DEFINITIONS})
pkg_check_module/pkg_search_modules
Find.cmakeファイルがなくてもpkg-configコマンドに対応している(.pcファイルを提供している)ばあい、そこから情報を取得することができます。
pkg_search_module(FFI REQUIRED libffi) include_directories(${FFI_INCLUDE_DIRS}) string(REPLACE ";" " " FFI_CFLAGS_STR "${FFI_CFLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FFI_CFLAGS_STR}") list(APPEND extra_libs ${FFI_LIBRARIES})
find_library
libmなど、基本的すぎてコンパイルオプションなどほとんどなく、find_packageやpkg-configにも対応していないライブラリはfind_libraryでオプションを取得することができます。
find_library(FFI_LIBRARY NAMES ffi) set(extra_libs ${extra_libs} ${FFI_LIBRARY})
環境ごとにcmake、pkg-configどちらがあるか分からないので、組み合わせて書くことができます。
# REQUIREDがついていない場合、エラーにはならない find_package(FFI) # 読み込みに成功するとFFI_FOUNDなどが設定される if (FFI_FOUND) # FindFFI.cmakeなどがあった場合の読み込み処理 else() # REQUIREDが付いているので、pkg-configでlibffiが見つからない場合、停止する pkg_search_module(FFI REQUIRED libffi) # pkg-configがあった場合の読み込み処理 endif()
ListとStringが混同してしまう罠
CMakeではListとStringの2つのデータ形式があります。
# Listの結合 set(変数名 A B) # 文字列としての結合 set(変数名 "${A} B")
Listの内部形式は"A;B"となっており文字列をセミコロンで結合したものです。 そのため、リストと文字列を混同すると"A B;C"のようになり、意図したものと異なる可能性があります。 find_packageで読み込んだ値は.cmakeファイルの作りによりListか文字列か変わるようです。 pkg_search_moduleの場合、〜CFLAG、〜LIBRARIESはどちらもListで渡されます。 CMake中では、CMAKE_C(XX)FLAGSは文字列、ライブラリはListとして渡す必要があるので、 〜CFLAGをCMAKE_C_FLAGSに結合すると意図しないセミコロンが混入してコンパイルに失敗します。 Listを文字列に結合する場合は以下のように変換する必要がります。
string(REPLACE ";" " " FFI_CFLAGS_STR "${FFI_CFLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FFI_CFLAGS_STR}")