CMakeのfind_packageでBoostを検索する

CMake で Boost ライブラリ を使う Visual Studio 2015 用プロジェクトファイル (および Linux では Makefile) を生成する方法について調査した結果を備忘録。

(2017-06-16追記:内容に不備があるので書き直し中。。)

今回のゴールは次の2点の達成:

  • CMake で Boost を使える Visual Studio 2015 のプロジェクトファイル (.vcxproj) を生成する。もちろん CMakeLists.txt は Linux でも使用可能とする。
  • 複数のプラットフォーム (Win32 と x64) 向けにビルドされた、複数バージョンの Boost が同一 OS 上に存在する状態で、適切なバージョン・プラットフォームを選択可能にする。

2 点目が少々厄介で、少し工夫が要る。詳細は追って記したい。

目次

  1. アプローチ
  2. Boost のインストール
  3. find_package() で Boost を検索する
  4. 複数のプラットフォーム向けの Boost ライブラリを共存させる

アプローチ

まず、究極的にはツールチェーンが適切な Boost ライブラリの置かれたディレクトリを見つけられれば良いのであり、そのためには誰かが場所を解決してやる必要がある。すると以下 2 つのアプローチが考えられる:

  1. Boost のパスを CMake に解決させる
  2. Boost のパスをツールチェーン側で解決させる(CMake は何もしない)

前者のアプローチでは CMake が Boost のパスを解決するので、ローカルコンピュータ上にインストールされた Boost のフルパスを .vcxproj や Makefile に直接書き込む。したがって環境が異なると使えない可能性が高く、CMake で生成した .vcxproj などをチーム共有することはできないと思った方が良い。もっとも、.vcxproj 等を共有すると、いつの間にか CMakeLists.txt に無い設定が .vcxproj だけに含まれていることに気が付く日が来てしまうと思う。あくまで .vcxprojMakefile は「CMakeLists.txt というソースを元にした生成物」なのだと意識して、それらは共有しない運用を心がけたいところ。

後者のアプローチでは、CMake 自身は Boost の場所を知らないため「.lib のファイル名だけ」CMakeLists.txt に書き、そのまま .vcxproj や Makefile にもファイル名だけ出力する。なのでツールチェーン側で Boost を見つけられるよう検索ディレクトリ類を設定してあれば、当然、 Boost を見つけられる。ただし、Boost の複雑な命名規則を考えると、開発環境の用意が難しいと思われる(Linux と Windows で Boost の命名規則が異なる点に注意。CMakeLists.txt 上でファイル名を OS ごとに変更するか、Winodws でも Linux と同じファイル名で Boost をビルドしつつ、ビルド設定ごとに .lib 置き場を分けて使い分ける必要あり)。

個人的見解としては、特別な事情でも無い限り後者のアプローチを選ぶメリットは無いと思う。というのも、まず CMake による Boost の検索機能は十分に強力で、かつ簡単に使える。しかもクロスプラットフォームで、Boost の複雑な命名規則についても面倒を見てくれる上に、上位バージョンを暗黙的に選択させることだってできる。そして後者のアプローチでは、Windows の開発環境を整えるだけでも一苦労だ。ということで、以下では CMake に解決させるアプローチに絞って書いていく。

Boost のインストール

CMake は Boost ライブラリを検索できる…と言っても万能ではないので、見つけやすいディレクトリ構成でインストールしておく方が良い。Boost 標準のインストールパスである C:Boost にインストールしておけば確実に見つけられるのだけれど、b2--prefix を変更するくらいならば問題なく見つけられるようだった。たとえば次のようにログオンユーザーディレクトリ直下の includelib にインストールしても、find_package() で見つけられる:

C:\Users\suguru\Source\boost_1_63_0>b2 install --build-type=complete --prefix="%UserProfile%" --with-system
...
C:\Users\suguru\Source\boost_1_63_0>dir /s /b "%UserProfile%include" | findstr "\system\"
C:Userssuguruincludeboost-1_63boostsystemapi_config.hpp
C:Userssuguruincludeboost-1_63boostsystemconfig.hpp
...
C:\Users\suguru\Source\boost_1_63_0>dir /s /b %userprofile%lib | findstr "boost_system"
C:Userssugurulibboost_system-vc140-mt-1_63.dll
C:Userssugurulibboost_system-vc140-mt-1_63.lib
...

ところで、この方法では Win32 版と x64 版を共存させることができない。共存させるには後述の工夫が必要になる。

find_package() で Boost を検索する

CMake で Boost ライブラリを検索させるには find_package() コマンドを使う。典型的な設定作業は次のような流れになる:

  • トップレベル CMakeLists.txt で find_package() コマンドを使い、システムにインストールされた Boost ライブラリを検索する
  • Boost を使う実行ファイルやライブラリの include_directories() コマンドに Boost_INCLUDE_DIRS 変数を指定する
  • ヘッダーオンリーでない Boost ライブラリを使う場合、さらに target_link_libraries() コマンドに Boost::date_time といった名前の IMPORT ターゲットを指定する

CMake は find_package() コマンドでローカルコンピュータ上にインストールされた Boost を見つけると、Boost_INCLUDE_DIRS という変数に Boost のヘッダーファイル群のあるディレクトリパスを設定してくれる。したがってヘッダーオンリーライブラリを使う場合は、これを include_directories() に指定するだけで設定完了となる。さらに CMake は見つけた Boost のライブラリ(コンポーネント)ごとに "Boost::コンポーネント名" という命名規則で IMPORT ターゲットを定義してくれる。したがって、ヘッダーオンリーでない Boost ライブラリを使う場合は Boost::date_timeBoost::system といった名前のターゲットを各実行ファイル用の target_link_libraries() に指定すれば良い。

なお Boost の検索や各種変数の設定などは FindBoost という CMake が内蔵しているモジュールで実現している。詳細な仕様は、当該モジュールの説明を参照されたい。

次に例を記す:

# src/CMakeLists.txt
cmake_minimum_required(VERSION 3.0)

project(hoge_service
        VERSION 2017.6.10
        LANGUAGES C CXX)

set(Boost_USE_STATIC_LIBS        ON)
set(Boost_USE_MULTITHREADED      ON)
set(Boost_USE_STATIC_RUNTIME   ON)
find_package(Boost 1.36.0 REQUIRED
             COMPONENTS date_time system)
message("## Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}")  # デバッグ用:解決したパスを表示
message("## Boost_LIBRARY_DIRS: ${Boost_LIBRARY_DIRS}")  # デバッグ用:解決したパスを表示

add_subdirectory(hoge_server)
# src/hoge_server/CMakeLists.txt
add_executable(hoge_server
    hoge_server.cc hoge_server.hh)

include_directories(
    "${PROJECT_SOURCE_DIR}"
    "${Boost_INCLUDE_DIRS}")

target_link_libraries(hoge_server
    Boost::date_time Boost::system)

複数のプラットフォーム向けの Boost ライブラリを共存させる

Boost を標準の方法でインストールすると Win32 版と x64 版のライブラリファイルを共存させることはできない。Boost の命名規則では address-model の指定がファイル名に含まれないので、両者の名前が衝突してしまうから。ファイル名が衝突する以上、同一環境で共存させるにはディレクトリを分ける必要がある。なので、たとえば次のようにビルド・インストールすることが考えられる(例ではあるものの Win32x64 というサブディレクトリ名は後で重要になってくるので注意):

C:\Users\suguru\Source\boost_1_63_0>b2 install address-model=32 --build-type=complete --prefix="%UserProfile%" --libdir="%UserProfile%libWin32" --with-system
C:\Users\suguru\Source\boost_1_63_0>b2 install address-model=64 --build-type=complete --prefix="%UserProfile%" --libdir="%UserProfile%libx64" --with-system
C:\Users\suguru\Source\boost_1_63_0>dir /s /b "%UserProfile%lib" | findstr "libboost_system-vc140-mt-1_63.lib"
C:UserssugurulibWin32libboost_system-vc140-mt-1_63.lib
C:Userssugurulibx64libboost_system-vc140-mt-1_63.lib

上記のようにビルド・インストールすればターゲットプラットフォーム (Win32, x64) ごとに異なるディレクトリにライブラリファイルが配置され、共存できるのだけれど、今度は find_package() でこれらを見つけられなくなってしまう。ここで、実は find_package() の検索パターンには "libアーキテクチャ名" というものもある。そして Visual Studio の .vcxproj を使用する場合、ターゲットプラットフォーム名は CMAKE_VS_PLATFORM_NAME 変数で取得できる。したがって、CMakeLists.txt でアーキテクチャ名をターゲットプラットフォーム名で動的に上書きしてやると、見つけられるようになる。具体的には、find_package() の前に次のような命令を入れてやる。

if (MSVC)
    set(CMAKE_LIBRARY_ARCHITECTURE "${CMAKE_VS_PLATFORM_NAME}")
endif (MSVC)

こうすると、Win32 バイナリを生成する .vcxproj を生成した場合でも、x64 バイナリを生成する .vcxproj を生成した場合でも、適切な Boost ライブラリを見つけられるようになる。これで、目標達成だ。

あとがき

なぜ Visual Studio のプラットフォームとアーキテクチャを同一視してくれないのかまでは未調査だけれど、3.8.0 ではそのような動作だった。もしかすると将来の CMake では、このような対処は不要になるかもしれない。ともあれ、この工夫さえしておけば Windows (Visual Studio 2015) と Linux でのクロス開発は非常に楽になると思う。