はじめに
修正:<package>-config.cmake
が得られる場合はそちらを使うようにという点を追記(@yumetodo さんありがとうございます。)
CMakeには自身のプロジェクトに属していないライブラリを自動的に検索してくれる便利なコマンドfind_package
があります。
例えばBoostライブラリを自作プログラムで使っている場合、
cmake_minimum_required(VERSION 3.8.2)
project(find_package_example CXX)
find_package(Boost REQUIRED)
add_executable(foo foo.cpp)
target_link_libraries(foo
Boost::boost
)
とすればfoo
をコンパイルする際にBoostライブラリのヘッダーファイルがインクルードされます。
非常に便利なのですが、全てのライブラリに対してfind_package
を使えるわけではありません。
このコマンドを使うには、目的のライブラリを検索するロジックを示すFind<package>.cmake
または<package>Config.cmake
/<lower-case-package>-config.cmake
がなければいけません。
参考記事:find_packageの動作
基本的にC/C++ライブラリの作成側がこれらのスクリプトを提供するべきです。
CMakeが<package>-config.cmake
を自動作成する機能を提供していますので、そちらを使いましょう。
参考記事:お手軽な xxx-config.cmake の作成方法
ただ提供されていない場合は自作する必要があります。
ここに必要最小限の要素に絞ったFind<package>.cmake
のテンプレートを載せておきますので、参考にしてください。
より細かく設定したい場合(プラットフォームやバージョンを考慮するなど)は、CMake自身が提供するスクリプト(例えばFindPNG.cmake)を参考にしてください。
参考:C++Now 2017: Daniel Pfeifer “Effective CMake"
Find<package>.cmake
を作成したら、CMAKE_MODULE_PATH
にそのスクリプトを置いてあるディレクトリを追加します。
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} <path-to-dir>)
これだけでfind_package(<package>)
が使えるようになります。
例えば下の例のスクリプトを使うと
# プロジェクトのルートディレクトリ下にあるcmakeというディレクトリに
# FindGMP.cmakeを置き、そのディレクトリを登録
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
# ライブラリを検索
find_package(GMP REQUIRED)
add_executable(myapp main.cpp)
# myappにGMPをリンク
target_link_libraries(myapp GMP::GMP)
とできるようになります。
自作したFind<Package>.cmake
をgithubにアップしていますので、自由に利用してください。現在利用可能なパッケージは以下の通りです。
- CGAL(The Computational Geometry Algorithms Library)
- GMP(The GNU Multiple Precision Arithmetic Library)
- MPFR(The GNU MPFR Library)
- Microsoft GSL
- Spdlog
テンプレート
必要最低限のコマンドからなるFind<package>.cmake
は以下のとおりです。
find_path(<package>_INCLUDE_DIR ...)
find_library(<package>_LIBRARY ...) # ヘッダーのみのライブラリの場合は不要
mark_as_advanced(
<package>_INCLUDE_DIR
<package>_LIBRARY # ヘッダーのみのライブラリの場合は不要
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(<package>
REQUIRED_VARS
<package>_INCLUDE_DIR
<package>_LIBRARY # ヘッダーのみのライブラリの場合は不要
)
if(<package>_FOUND AND NOT TARGET <package>::<package>)
add_library(<package>::<package> UNKNOWN IMPORTED)
set_target_properties(<package>::<package> PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES ["C"|"CXX"] # ヘッダーのみのライブラリの場合は不要
IMPORTED_LOCATION "${<package>_LIBRARY}" # ヘッダーのみのライブラリの場合は不要
INTERFACE_INCLUDE_DIRECTORIES "${<package>_INCLUDE_DIR}"
)
endif()
以下の例ではUNIXのみ考慮しています。
コンパイル済みライブラリ
例えばGMPライブラリを探すスクリプトは以下の通りになります。
# インクルードディレクトリのパスをgmp.hを頼りに検索する
find_path(GMP_INCLUDE_DIR gmp.h
# 検索するパス
PATHS
# 環境変数GMP_ROOTまたはGMP_INCLUDE_DIRが存在したらそこを検索
ENV GMP_ROOT
ENV GMP_INCLUDE_DIR
# CMakeの変数としてGMP_ROOTが定義されていたらそこを検索
${GMP_ROOT}
/usr
/usr/local
PATH_SUFFIXES
include
)
# ライブラリへのパスをライブラリ名を元に検索する
find_library(GMP_LIBRARY
NAMES
gmp
PATHS
ENV GMP_ROOT
ENV GMP_LIB_DIR
${GMP_ROOT}
/usr
/usr/local
PATH_SUFFIXES
lib
)
# advancedモードでない限り変数の存在を隠す
mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARY)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(GMP
REQUIRED_VARS
GMP_INCLUDE_DIR
GMP_LIBRARY
)
# GMPが見つかり、かつGMP::GMPが定義されていない場合
if(GMP_FOUND AND NOT TARGET GMP::GMP)
# GMP::GMPというターゲット名でGMPライブラリを定義
# UNKNOWN = STATIC/SHAREDかはまだ不明
# IMPORTED = このプロジェクトに属さないターゲット
add_library(GMP::GMP UNKNOWN IMPORTED)
set_target_properties(GMP::GMP PROPERTIES
# C言語、C++なら"CXX"とする
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${GMP_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${GMP_INCLUDE_DIR}"
)
endif()
ヘッダーのみのライブラリ
例えばEigenライブラリを探すスクリプトは以下のとおりです。
(CMakeを使ってEigenライブラリをインストールすると、Eigen3Config.cmakeが自動作成されるので、本当は作る必要はありません。)
find_path(EIGEN_INCLUDE_DIR Eigen/Core
PATHS
ENV EIGEN_ROOT
ENV EIGEN_INCLUDE_DIR
${EIGEN_ROOT}
/usr
/usr/local
PATH_SUFFIXES
include
)
mark_as_advanced(EIGEN_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Eigen
REQUIRED_VARS EIGEN_INCLUDE_DIR
)
if(Eigen_FOUND AND NOT TARGET Eigen::Eigen)
# EigenはヘッダーのみのライブラリなのでINTERFACEキーワードを指定する
add_library(Eigen::Eigen INTERFACE IMPORTED)
set_target_properties(Eigen::Eigen PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${EIGEN_INCLUDE_DIR}"
)
# set_target_propertiesのかわりにtarget_include_directoriesを使ってもOK
# target_include_directories(Eigen::Eigen INTERFACE ${EIGEN_INCLUDE_DIR})
endif()