誰にも見えないブログ

雑なメモ。まとまってない文章等

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)の内容
    • step1で平方根の計算sqrt()コンパイラ標準?のものを使っていた。*1
    • ユーザーが用意したライブラリをつかうような改変を入れて、cmakeをそれに対応させる
  • ライブラリを利用するプロジェクト(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_LIBSEXTRA_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まとめ
    • ライブラリ側のCMakeにはadd_library()コマンドで上位のCMakeに認識させる名前を設定する
    • 呼び出し側のCMakeではadd_subdirectory()で追加するライブラリを決める
    • target_link_librariesでリンクするターゲットを指定する
    • オプションを定義しifコマンドでライブラリの利用有無を決定可能

Usage Requierment

  • Step3(usage requirement)の内容

  • このStepは難しかった。多分初心者は全員個々で脱落すると思う。

target_*系キーワードは自分自信に関する設定だけでなく、自分自信を参照している別のCMakeLists.txtにどれほど設定内容を波及させるか、つまり**設定のスコープを定

  • 自分自信(producer)の設定と自分を利用している側(concumer)の2つに対して設定する
  • 例えばライブラリがC++20の新しいAPIを使っているからtarget_compile_options(ターゲット名 PUBLIC cxx_std_20)を指定して呼び出し元にもC++20のコンパイルオプションを利用することを強制する、といったことができる
  • どの程度設定を波及させるのかPUBLIC,PRIVATE,INTERFACEキーワードで指定する

  • 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.html

    target_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側に設定内容がリークする
  • 公式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 use make 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")こんなんでも通ってしまうから普通はダメな気がするが。

*1:The executable can then use this library instead of the standard square root function provided by the compiler.とあるけどC++だとsqrtはコンパイラが提供するのかな?