CMakeのfind_package()で自作ライブラリをインストール可能にする

【第6回】CMakeで自作ライブラリをパッケージ化しよう!
〜Config / Targets / Version の生成〜

◎ 本記事は「CMake講座」シリーズの第5回です。
前回 -> 【第5回】CMakeで外部ライブラリを導入しよう!〜find_package / FetchContent / add_subdirectory / ExternalProject_Add〜
次回 ->【第7回】

○ シリーズ全体の構成はこちら: CMake講座トップページ

前回は、CMakeでライブラリをインストールして利用する方法を学びました。
今回は逆にライブラリの作成側、つまり、自作ライブラリを find_package() で読み込めるようにする「CMakeパッケージ化」 の方法を解説します。

CMakeパッケージとは、たとえば次のように書ける構成のことです。

find_package(MyLib CONFIG REQUIRED)
target_link_libraries(App PRIVATE MyLib::MyLib)


ここまで実現できると、他のプロジェクトから自作ライブラリを安全かつ簡単に再利用できます。
MyLibをどのようにしてパッケージ化するのかを順に見ていきましょう。


1. 目標と全体像

まずは、CMakeでライブラリをパッケージ化するときに必要となるファイル構成を整理します。

ファイル名役割
MyLibTargets.cmake実際のターゲット(MyLib::MyLib)が登録されたファイル
MyLibConfig.cmakefind_package() で最初に読み込まれる設定ファイル
MyLibConfigVersion.cmakeバージョン整合性チェック用ファイル

これらのファイルを生成し、正しくインストールできるようにすることで、
ライブラリの利用側は先ほどのように find_package() 経由で見つけることができます。


2. ディレクトリ構成

一般的には以下のような構成になることが多いです。

MyLib/
├── CMakeLists.txt
├── include/
│   └── MyLib/
│       └── MyLib.hpp
├── src/
│   └── MyLib.cpp
└── cmake/
    └── MyLibConfig.cmake.in

cmake/MyLibConfig.cmake.in は後述のConfigファイルを生成するテンプレートです。


3. ライブラリターゲットを定義する

まずは通常どおり自作ライブラリを定義します。
このとき、開発中とインストール後の両方で正しくインクルードできるように、BUILD_INTERFACEINSTALL_INTERFACE を設定しておきます。

cmake_minimum_required(VERSION 3.15)
project(MyLib VERSION 1.2.3 LANGUAGES CXX)

add_library(MyLib src/MyLib.cpp)

target_include_directories(MyLib
  PUBLIC 
   $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
   $<INSTALL_INTERFACE:include>
)
set_target_properties(MyLib PROPERTIES EXPORT_NAME MyLib)
  • BUILD_INTERFACE:開発中に使用するインクルードパス
  • INSTALL_INTERFACE:インストール後に使用するインクルードパス
  • EXPORT_NAME:外部公開時に使用されるターゲット名(最終的に MyLib::MyLib

4. install(TARGETS) と export() の設定

ビルドしたライブラリをインストール可能にし、CMakeがエクスポート情報を生成できるようにします。
以下はインストールの情報を登録するもので、この時点ではまだファイルは生成されないことに注意してください。

install(TARGETS MyLib
  EXPORT MyLibTargets
  ARCHIVE  DESTINATION lib
  LIBRARY  DESTINATION lib
  RUNTIME  DESTINATION bin
  INCLUDES DESTINATION include
)

EXPORT MyLibTargets は、ターゲット情報をまとめたセット名です。


5. install(EXPORT) で Targets.cmake を生成する

先ほどのターゲット情報をファイルとして出力します。

install(EXPORT MyLibTargets
  FILE         MyLibTargets.cmake
  NAMESPACE    MyLib::
  DESTINATION  lib/cmake/MyLib
)

これにより、lib/cmake/MyLib/MyLibTargets.cmake が生成され、MyLib::MyLib というターゲットを定義する実体ファイルが作られます。

install(EXPORT ...) が生成するのは Targets.cmake であり、Config.cmake ではありません。
 Config は後述のとおり Targets を読み込む “ラッパ” を用意します。


6. Config.cmake(ラッパ)を作成する

find_package() が最初に読み込むのは MyLibConfig.cmake です。
したがって、このConfig.cmakeがなければ、find_package() でライブラリを見つけることはできません。

このConfigファイルは、MyLibConfig.cmake.in というテンプレートを作成し、cmakeで生成するのが一般的な手法です。
テンプレートは、ライブラリ利用に必要な依存関係を整理したりと事前準備をするものもありますが、
ここでは以下のように Targets.cmake を読み込むだけの簡単なファイルとします。

# cmake/MyLibConfig.cmake.in
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")

このテンプレートを CMake に加工させて出力します。

include(CMakePackageConfigHelpers)

configure_package_config_file(
  cmake/MyLibConfig.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
  INSTALL_DESTINATION lib/cmake/MyLib
)

@PACKAGE_INIT@ などのマクロ展開を安全に行うため、CMakePackageConfigHelpers を利用します。


7. バージョン情報ファイルを生成する

find_package(MyLib CONFIG VERSION 1.2) のように指定された場合にバージョン整合性を確認できるようにします。

write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
  VERSION ${PROJECT_VERSION}
  COMPATIBILITY SameMajorVersion
)
  • SameMajorVersion:メジャーバージョンが一致すればOK
  • AnyNewerVersion:新しければOK(互換性ポリシーに合わせて選択)

8. Configファイル群をインストールする

生成した Config 関連ファイルをインストール対象にします。

install(FILES
  "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
  "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
  DESTINATION lib/cmake/MyLib
)

インストール結果の例:

<install_prefix>/
└── lib/
    └── cmake/
        └── MyLib/
            ├── MyLibConfig.cmake
            ├── MyLibConfigVersion.cmake
            └── MyLibTargets.cmake

9. 使用側のプロジェクトから利用する

これらの手順で、ついに他のプロジェクトから次のように利用できるようになりました!

cmake_minimum_required(VERSION 3.15)
project(App LANGUAGES CXX)

# 例: cmake -S . -B build -DCMAKE_PREFIX_PATH=/usr/local
find_package(MyLib CONFIG REQUIRED)

add_executable(App main.cpp)
target_link_libraries(App PRIVATE MyLib::MyLib)

CMAKE_PREFIX_PATH<install_prefix>(例:/usr/local)を含めると、CMakeが自動的に MyLibConfig.cmake を探して読み込みます。


10. まとめ

ファイル役割生成方法
MyLibTargets.cmakeインポートターゲットの実体install(EXPORT ...)(CMakeが生成)
MyLibConfig.cmakeラッパ(Targets を読み込む)configure_package_config_file()
MyLibConfigVersion.cmakeバージョン整合性の確認write_basic_package_version_file()

今回の構成は、実際にもよく利用される手法の最小構成になります。
まずはこの最小構成で確実に動かし、必要に応じて依存関係の記述やバージョニング方針を発展させていきましょう。

◇CMake基礎講座

(第5回:CMakeで外部ライブラリを導入しよう)
(第7回:) →

CMake講座トップページに戻る