フェルマータ

個人用のメモ。ソフトウェアの導入とかが多くなる予定。

cmake の ExternalProject_add と add_subdirectory を利用して gtest を導入した。

ポエム

しばらく C++ を書いていなかったが趣味は C++ でやりたくなったので gtest を導入してテストを書けるようにしたくなった(テストを書くとは言っていない)。

やりたいこと

googletest を手動インストールしないで cmake 側からよしなに依存を解決したい。

方針

googletest のレポジトリの方針の通り、 add_subdirectory を利用して関連付けを行う。

googletest/README.md at master · google/googletest · GitHub

README にはこんなアツい思いが書かれている。

すでに CMake を利用しているプロジェクト内で gtest を使うのであれば、 gtest をそのプロジェクトの一部として直接ビルドしてしまうのが、ロバストで柔軟性の高いアプローチだ。このアプローチは、 GoogleTest のソースコードを メインのビルドでアクセス可能にし CMake の add_subdirectory()) コマンドで GoogleTest を追加することで実現できる。こうすることで、 gtest と自身で書いた残りのプロジェクトとでコンパイラとリンカの設定が同一のものを利用できるようになり、例えば debug/release のような、互換性のないライブラリを利用したことによる問題を回避できるといった素晴らしいメリットを得られる。特に Windows では顕著に有用である。GoogleTest のソースコードをメインのビルドで利用するにはいくつかの方法がある。

  • GoogleTest のコードを手動でダウンロードし既知の場所に保存する。これは最も柔軟性の低いアプローチであり、 CI などを利用するのを難しくしてしまう可能性がある。

  • メインのプロジェクトのソースツリーにGoogleTest のコードを直接コピーとして埋め込んでしまう。しばしば見られる最も単純なアプローチではあるが、最新の状態を保つのが最も難しい方法でもある。

  • git submodule かそれに準ずるもので GoogleTest を追加してしまう。この方法も常に取れるとは限らず適切とは限らない。例えば、 git submodule 自身に長所と欠点があるからだ。

  • CMake のビルドの configure step の一部として GoogleTest をダウンロードする。この方法はほんの少し複雑ではある、が、他の方法のような制限を受けない。

上にあげた方法の最後の方法を実現するには、分割した小さなファイル、例えば CMakeLists.txt.in とする、に、 CMake の stage 中にビルド内にコピーされ、当該のコードがビルドの一部として実行されるよう小さな CMake のスクリプトを書けば良い。

なんかたくさん地雷こと踏んだのかね?ぼくも前回はローカルに直接インストールしたけど頭痛いのでそれはもうやりたくないしこの方法を使ってみる。なお、 cmake のバージョンは 2.8.2 以降が要求されている。

やったこと

README.md に書いたことをそのままやった。

ディレクトリ構成

-+--- ./
 +--- CMakeLists.txt
 +-+- src/
 | +- CMakeLists.txt
 | +- Target.cpp
 | +- Target.hpp
 |
 +-+- test/
   +- CMakeLists.txt.in
   +- CMakeLists.txt
   +- main.cpp
   +- TargetText.cpp

./CMakeLists.txt

プロジェクトルートは子ディレクトリを追加する以外何もしない。

project(app_root)

add_subdirectory(src)
add_subdirectory(test)

./src/CMakeLists.txt

テスト対象となるコードはいつもどおり書けば良い。特段難しいことはしない。

project(app)

# add_library(app) なりなんなりいつものように書く

./test/CMakeLists.txt.in

GoogleTest を CMake が作業ディレクトリ内でよしなに展開するようにしている。あんまりポイントはないが、ソースコードの置き場が ${CMAKE_BINARY_DIR}/googletest-src になり、a ファイルの置き場が ${CMAKE_BINARY_DIR}/googletest-build になることだけ気をつけるといい。このコマンドは GoogleTest をダウンロードするがほかはなにもしない。

cmake_minimum_required(VERSION 2.8.2)
project(googletest-download NONE)

include(ExternalProject)
ExternalProject_Add(googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG master
    SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src"
    BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build"
    CONFIGURE_COMMAND ""
    BUILD_COMMAND ""
    INSTALL_COMMAND ""
    TEST_COMMAND ""
)

./test/CMakeLists.txt

テストの実行側で GoogleTest に CMake を実行し GoogleTest のダウンロード先のディレクトリを add_subdirectory()コンパイル対象に加える。

project(app_test)

# Download and unpack googletest at configure time
configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
        RESULT_VARIABLE result
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
if(result)
    message(FATAL_ERROR "CMake step for googletest failed: ${result}")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} --build .
        RESULT_VARIABLE result
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
if(result)
    message(FATAL_ERROR "Build step for googletest failed: ${result}")
endif()

# Prevent overriding the parent project's compiler/linker
# settings on Windows
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# Add googletest directly to our build. This defines
# the gtest and gtest_main targets.
add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
        ${CMAKE_CURRENT_BINARY_DIR}/googletest-build
        EXCLUDE_FROM_ALL)

# The gtest/gtest_main targets carry header search path
# dependencies automatically when using CMake 2.8.11 or
# later. Otherwise we have to add them here ourselves.
if (CMAKE_VERSION VERSION_LESS 2.8.11)
    include_directories("${gtest_SOURCE_DIR}/include")
endif()

# あとは GoogleTest を普通に使うだけでいい。

おわりに

英語読めば一発なんだが頭が弱っているから読むのにも時間かかる…とりあえず GoogleTest に限らず自作のライブラリとかにも応用できそうな気はした。しかしまあ CMake は便利なんだけど、結局ビルドプロセスを頭に入れないとかー、という気持ちにはなった。