Help us understand the problem. What is going on with this article?

[CMake] ライブラリを自動的に探すFind<package>.cmakeのテンプレート

More than 1 year has passed since last update.

はじめに

修正:<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>.cmakegithubにアップしていますので、自由に利用してください。現在利用可能なパッケージは以下の通りです。

  • 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ライブラリを探すスクリプトは以下の通りになります。

FindGMP.cmake
# インクルードディレクトリのパスを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が自動作成されるので、本当は作る必要はありません。)

FindEigen.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()
shohirose
2015年8月からテキサス大学オースティン校の石油工学科博士課程に留学し、2019年12月にPhDを取得しました。現在は東京に住んで東京本社で働いています。専門は貯留層工学、フラクチャー力学、数値計算あたりです。プログラミング言語はc++, matlab, pythonを主に使用しています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした