cmake tutorialその2:ライブラリ、テスト、Usage Requierment
cmake tutorialその2
前回からの引き続き。
- step2からstep4まで読んだ。
- 次回はstep5から
https://cmake.org/cmake/help/latest/guide/tutorial/index.html
ライブラリの追加
- Adding a Library (Step 2)の内容
- ライブラリを利用するプロジェクト(step2)のsource directoryにライブラリのsource directory(MathFunctions)を作成し以下の一行のCMakelists.txtファイルを新規に作成する
add_library(MathFunctions mysqrt.cxx)
#include <iostream> // a hack square root calculation using simple operations double mysqrt(double x) { if (x <= 0) { return 0; } double result = x; // do ten iterations for (int i = 0; i < 10; ++i) { if (result <= 0) { result = 0.1; } double delta = x - (result * result); result = result + 0.5 * delta / result; std::cout << "Computing sqrt of " << x << " to be " << result << std::endl; } return result; }
- フォルダ構成の解説
/step2 ├── CMakeLists.txt ├── MathFunctions │ ├── CMakeLists.txt │ └── mysqrt.cxx ├── TutorialConfig.h.in └── tutorial.cxx
- ライブラリを利用する側(
step2
)側のCMakelists.txtの変更
- ライブラリの利用有無はオプションにする。
- サンプルプロジェクトだとあまり意味のないものだが、大規模プロジェクトになると必要。
- 自分が知ってるプロジェクトだと特定ライブラリの特定バージョンを使うみたいなオプションをつかって設定されていたりする。
- この設定はライブラリを利用する側の
CMakeLists.txt
に書くこと
# MathFunctionsの利用をオプション化 option(USE_MYMATH "Use tutorial provided math implementation" ON) configure_file(TutorialConfig.h.in TutorialConfig.h)
- 設定したoptionに応じてライブラリの利用有無を切り替える部分もCMakeListsに書く。
if(USE_MYMATH) add_subdirectory(MathFunctions) list(APPEND EXTRA_LIBS MathFunctions) list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions") endif() add_executable(Tutorial tutorial.cxx) target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS}) # add the binary tree to the search path for include files # so that we will find TutorialConfig.h target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}" ${EXTRA_INCLUDES} )
EXTRA_LIBS
とEXTRA_INCLUDES
にライブラリを登録し、target_include_directories
でそちらを呼び出すように変更します。EXTRA_INCLUDES
を使う方法は古いやり方なので非推奨(らしい)
- ライブラリを使う側のコード(
tutorial.cxx
)でプリプロセッサを追加する。USE_MYMATH
が存在しているときのみライブラリのヘッダファイルをインクルード&ライブラリ関数利用 tutorial.cxx
#ifdef USE_MYMATH # include "MathFunctions.h" #endif ・・・ //平方根計算関数を呼んでいる箇所でもプリプロセッサを使って利用する関数を呼び分け。 #ifdef USE_MYMATH const double outputValue = mysqrt(inputValue); #else const double outputValue = sqrt(inputValue); #endif
TutorialConfig.h.in
にライブラリオプションをcmakeに書かせるための設定を追記
#cmakedefine USE_MYMATH
- ビルドして実行すると標準出力に表示されるメッセージが変わり、無事ライブラリの平方根をもとめるコードが使われていることがわかる
$mkdir build && cd build $cmake ../step2 $cmake --build . $./Tutorial 1024 Computing sqrt of 1024 to be 512.5 Computing sqrt of 1024 to be 257.249 Computing sqrt of 1024 to be 130.615 Computing sqrt of 1024 to be 69.2273 Computing sqrt of 1024 to be 42.0096 Computing sqrt of 1024 to be 33.1925 Computing sqrt of 1024 to be 32.0214 Computing sqrt of 1024 to be 32 Computing sqrt of 1024 to be 32 Computing sqrt of 1024 to be 32 The square root of 1024 is 32
- step2まとめ
Usage Requierment
このStepは難しかった。多分初心者は全員個々で脱落すると思う。
target_*
系キーワードは自分自信に関する設定だけでなく、自分自信を参照している別のCMakeLists.txt
にどれほど設定内容を波及させるか、つまり**設定のスコープを定
- 自分自信(producer)の設定と自分を利用している側(concumer)の2つに対して設定する
- 例えばライブラリがC++20の新しいAPIを使っているから
target_compile_options(ターゲット名 PUBLIC cxx_std_20)
を指定して呼び出し元にもC++20のコンパイルオプションを利用することを強制する、といったことができる どの程度設定を波及させるのか
PUBLIC
,PRIVATE
,INTERFACE
キーワードで指定する- 指定しないとどれになるか?ということを調べたがよく分からなかった
- https://stackoverflow.com/questions/51396608/what-is-default-target-link-libraries-privacy-setting
- このstackoverflow的には未指定は非推奨っぽいが...
- 指定しないとどれになるか?ということを調べたがよく分からなかった
step2/MathFunctions/CMakeLists.txt
# MathFunctionsのビルド時に target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} )
step2/CMakeLists.txt
if(USE_MYMATH) add_subdirectory(MathFunctions) list(APPEND EXTRA_LIBS MathFunctions) # ライブラリ側でconsumerのinclude directoryに含めるよに指定しているのでconsumer側での設定が不要になった! # list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions") endif() ・・・中略・・・ target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}" # 同じく不要! # ${EXTRA_INCLUDES} )
プチ解説
先述のどの程度設定を波及させるのか
PUBLIC
,PRIVATE
,INTERFACE
キーワードで指定するという話だが、target_include_directories
の場合:https://cmake.org/cmake/help/v3.16/command/target_include_directories.htmltarget_include_directories(<target> [SYSTEM] [BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
- PRIVATE,PUBLICを指定した場合
INCLUDE_DIRECTORIES
にitemの内容が入る- コマンドを発行したターゲット内で利用される
- PUBLIC,INTERFACEを指定した場合
INTERFACE_INCLUDE_DIRECTORIES
にitemの内容が入る- コマンドを発行したターゲットを利用するconsumer側に設定内容がリークする
- PRIVATE,PUBLICを指定した場合
公式tutorialの場合ライブラリ側のソース
MathFunctions/mysqrt.cxx
はヘッダファイルの利用はなし
#include <iostream> // a hack square root calculation using simple operations double mysqrt(double x) { ・・・実装略 }
- 利用側のソース
step2/tutorial.cxx
はヘッダファイルをincludeしている
// A simple program that computes the square root of a number #include <cmath> #include <iostream> #include <string> #include "TutorialConfig.h" ・・・実装略
- というわけで、チュートリアルプロジェクトの場合、ライブラリ(
`MathFunctions
) ソースコード側のCMakeLists.txt内target_include_directories
にはINTERFACEを指定するのが適切である - 上記の適切なUsage Requiermentの導入により、producer(ライブラリ側)でconsumer(利用側)のinclude_directoryを設定したのでconsumer側でいちいち設定していた
EXTRA_INCLUDES
の設定が不要になった。
補足:step3理解のために参考にしたもの
- 日本語情報。参考資料もなかなか充実している
- 日本語情報。PUBLIC,PRIVATE,INTERFACEの理解の助けになった。
- 会社で共有されている秘伝のcmake資料...リンクは秘密。(見えるところに存在しない)
- cmake関連の和書が出版されない理由って、必要としてる会社は内部で分かりやすいcmake資料が作成・継承されてて、それで間に合ってるんだろうか。
step3疑問
CMAKE_CURRENT_SOURCE_DIR
はこのキーワードが記述されたCMakeLists.txtファイルのパスが値になるんだろうか。(多分そうだと思うが確証がない)consumerをライブラリ利用側、producerを提供側と解釈したけど妥当なんだろうか。
Remember
INTERFACE
means things that consumers require but the producer doesn’t.ググってもマルチスレッドのデザインパターンの情報しか出てこない...
installとtest
install
install()
installするターゲットとヘッダファイルをそれぞれ指定するのみ
この章のコマンドそのまま実行すると
/usr/local/bin
とかに実行ファイル置かれてウザくなると思うのでビルド時に-DCMAKE_INSTALL_PREFIX=<適当なディレクトリ>
の指定を推奨
実行するコマンド:本来ならば以下を実行するはずだが、エラーが発生した。※
DCMAKE_INSTALL_PREFIX
はお好みで
cmake -DCMAKE_INSTALL_PREFIX=/home/yabu/Documents/study/cmake-sandbox/CMake/Help/guide/tutorial/bin ../Step5 cmake --install .
- 結果
$ cmake -DCMAKE_INSTALL_PREFIX=/home/yabu/Documents/study/cmake-sandbox/CMake/Help/guide/tutorial/bin ../Step5 -- The C compiler identification is GNU 7.4.0 -- The CXX compiler identification is GNU 7.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/yabu/Documents/study/cmake-sandbox/CMake/Help/guide/tutorial/build $ cmake --install . -- Install configuration: "" CMake Error at cmake_install.cmake:47 (file): file INSTALL cannot find "/home/yabu/Documents/study/cmake-sandbox/CMake/Help/guide/tutorial/build/Tutorial": No such file or directory.
- チュートリアルには存在しない
DCMAKE_INSTALL_PREFIX
を入れたのが悪さをしたのか、と思いきや、抜いても特に変わらない。 cmake --install .
の代わりにmake install
を使うと問題なくビルド&実行できた。(なんじゃそりゃ。)
$ make install Scanning dependencies of target MathFunctions [ 25%] Building CXX object MathFunctions/CMakeFiles/MathFunctions.dir/mysqrt.cxx.o [ 50%] Linking CXX static library libMathFunctions.a [ 50%] Built target MathFunctions Scanning dependencies of target Tutorial [ 75%] Building CXX object CMakeFiles/Tutorial.dir/tutorial.cxx.o [100%] Linking CXX executable Tutorial [100%] Built target Tutorial Install the project... -- Install configuration: "" -- Installing: /home/yabu/Documents/study/cmake-sandbox/CMake/Help/guide/tutorial/bin/bin/Tutorial -- Installing: /home/yabu/Documents/study/cmake-sandbox/CMake/Help/guide/tutorial/bin/include/TutorialConfig.h -- Installing: /home/yabu/Documents/study/cmake-sandbox/CMake/Help/guide/tutorial/bin/lib/libMathFunctions.a -- Installing: /home/yabu/Documents/study/cmake-sandbox/CMake/Help/guide/tutorial/bin/include/MathFunctions.h $ ../bin/bin/Tutorial 4 Computing sqrt of 4 to be 2.5 Computing sqrt of 4 to be 2.05 Computing sqrt of 4 to be 2.00061 Computing sqrt of 4 to be 2 Computing sqrt of 4 to be 2 Computing sqrt of 4 to be 2
- 確かにドキュメントには古いバージョンなら
make install
を使えとあるが私が使っているのはドキュメントで示されている3.15より新しい3.16。
Run the install step by typing
cmake --install .
(introduced in 3.15, older versions of CMake must usemake install
)
- version
$ cmake --version cmake version 3.16.0-rc3 CMake suite maintained and supported by Kitware (kitware.com/cmake).
- うーんなんだこれ。
test
- CMakeLists.txtのビルドやらインストールやらのコマンドの後ろにテストコマンドを書ける
enable_testing() # セグフォなど発生しないか確認。戻り値が0ならOK add_test(NAME Runs COMMAND Tutorial 25) # 引数なしで実行じたときのメッセージ表示を確認 # 正規表現を使って実行ファイルの名前のブレを許容している # どの正規表現を使っているかは謎。 add_test(NAME Usage COMMAND Tutorial) set_tests_properties(Usage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number" ) # テスト関数を定義できる。変数や正規表現を使って柔軟なテストを構築可能 function(do_test target arg result) add_test(NAME Comp${arg} COMMAND ${target} ${arg}) set_tests_properties(Comp${arg} PROPERTIES PASS_REGULAR_EXPRESSION ${result} ) endfunction(do_test) # 定義したdo_test()関数で適当にテストを登録する do_test(Tutorial 4 "4 is 2") do_test(Tutorial 9 "9 is 3") do_test(Tutorial 5 "5 is 2.236") do_test(Tutorial 7 "7 is 2.645") do_test(Tutorial 25 "25 is 5") do_test(Tutorial -25 "-25 is [-nan|nan|0]") do_test(Tutorial 0.0001 "0.0001 is 0.01")
DCMAKE_INSTALL_PREFIX
を使ってインストールしたときに、非標準パスのバイナリをctestでテストする方法がわからなかったので改めて普通にbuildした- コマンド
$cmake ../Step4 $cmake --build . $ ctest -C Debug -W
- 結果
Test project /home/yabu/Documents/study/cmake-sandbox/CMake/Help/guide/tutorial/build Start 1: Runs 1/9 Test #1: Runs ............................. Passed 0.00 sec Start 2: Usage 2/9 Test #2: Usage ............................ Passed 0.00 sec Start 3: Comp4 3/9 Test #3: Comp4 ............................ Passed 0.00 sec Start 4: Comp9 4/9 Test #4: Comp9 ............................ Passed 0.00 sec Start 5: Comp5 5/9 Test #5: Comp5 ............................ Passed 0.00 sec Start 6: Comp7 6/9 Test #6: Comp7 ............................ Passed 0.00 sec Start 7: Comp25 7/9 Test #7: Comp25 ........................... Passed 0.00 sec Start 8: Comp-25 8/9 Test #8: Comp-25 .......................... Passed 0.00 sec Start 9: Comp0.0001 9/9 Test #9: Comp0.0001 ....................... Passed 0.00 sec 100% tests passed, 0 tests failed out of 9 Total Test time (real) = 0.01 sec
- テストにこういう正規表現使っても良いもんだろうか。
do_test(Tutorial 1024 "1024 is 3")
こんなんでも通ってしまうから普通はダメな気がするが。
他
- やってる途中に見つけたが、cmake policyなんてものが公式からお通達されてるらしい。
C++(というかcmake界隈?)ではいろんなところでヘッダファイルのことをインターフェースと言ってるような気がする。
- 文法の
interface
と混ざってややこしい。
- 文法の