FetchContent
CMakeにおける「FetchContent」とは何か?
これまでのCMakeでは、外部ライブラリを使う場合、以下のいずれかの方法を取る必要がありました。
- システムインストールされたライブラリを使う
find_package()
などでシステムにインストールされているライブラリを探す。この場合、ビルド環境ごとにライブラリのインストールが必要になる。 - 外部ライブラリをサブモジュールとしてリポジトリに含める
Gitのサブモジュールなどを使って、依存ライブラリのソースコードを自分のリポジトリ内に含める。 - 外部ライブラリのビルド済みバイナリをダウンロードして使う
ビルド済みライブラリをダウンロードし、そのパスをCMakeに教える。
FetchContentは、これらの課題を解決し、プロジェクトのビルドプロセスの一部として依存関係のソースコードを自動的に取得し、そのままビルドに含めるという、より現代的なアプローチを提供します。
FetchContentの主な特徴と利点
- 様々な取得方法に対応
Gitリポジトリ、URLからのアーカイブダウンロードなど、複数の取得方法をサポートしています。 - 組み込みと使用が容易
ダウンロードされた依存関係は、通常のCMakeのサブディレクトリやターゲットとしてプロジェクトに組み込むことができます。 - ビルド環境のポータビリティ向上
依存関係が自動的に取得されるため、異なる環境間でのビルド設定が容易になります。開発者全員が同じバージョンの依存関係を使用することを保証できます。 - シンプルな依存関係管理
プロジェクトのCMakeLists.txtファイル内で、どの依存関係が必要かを記述するだけで済みます。 - 依存関係の自動ダウンロード
GitHubなどのリモートリポジトリから、指定したバージョンのソースコードを自動的にダウンロードします。
FetchContentの基本的な使い方
FetchContentを使用するには、通常以下のステップを踏みます。
-
FetchContentモジュールのインクルード
まず、FetchContent
モジュールをプロジェクトにインクルードします。include(FetchContent)
-
依存関係の定義とフェッチ
FetchContent_Declare()
コマンドで、取得したい依存関係を定義します。ここでは、リポジトリのURL、タグやコミットハッシュ、ブランチなどを指定できます。 次に、FetchContent_MakeAvailable()
コマンドで、定義した内容に基づいて実際に依存関係をフェッチし、現在のビルドで利用可能にします。これにより、ダウンロードされたソースコードは、通常$${PROJECT_NAME}_SOURCE_DIR
のような変数で参照できるようになり、add_subdirectory()
などでプロジェクトに組み込むことができます。例:GitHubからあるライブラリ(例:
my_library
)をフェッチする場合include(FetchContent) # 依存関係の定義 FetchContent_Declare( my_library GIT_REPOSITORY https://github.com/some_user/my_library.git GIT_TAG v1.0.0 # または特定のコミットハッシュ、ブランチ名 ) # 依存関係をフェッチし、ビルドで利用可能にする FetchContent_MakeAvailable(my_library) # ダウンロードされたライブラリをサブディレクトリとして追加 # my_library_SOURCE_DIR は FetchContent_MakeAvailable によって設定される変数 add_subdirectory(${my_library_SOURCE_DIR}) # ここでmy_libraryのターゲット(例:my_library::my_library)が利用可能になる # target_link_libraries(my_executable PRIVATE my_library::my_library)
よくある使用例
- 外部依存の管理
プロジェクトが依存するサードパーティ製ライブラリのソースコードを自動的に取得し、プロジェクト内でビルドする。 - 共通のユーティリティライブラリの共有
複数のプロジェクトで共有される社内ライブラリをFetchContentで取得する。 - テストフレームワークの組み込み
Google TestやCatch2などのテストフレームワークをビルド時に取得し、テストコードのコンパイルに利用する。 - 単一ヘッダライブラリの組み込み
小さな単一ヘッダライブラリをプロジェクトに直接組み込む。
- ビルドツリーの清潔さ
FetchContentによってダウンロードされたソースコードは、通常CMakeのビルドディレクトリ内の特定の場所に配置されます。 - 依存関係のバージョン管理
GIT_TAG
などを適切に指定することで、依存関係のバージョンを厳密に管理できます。 - インターネット接続
依存関係のダウンロードにはインターネット接続が必要です。 - 初回ビルド時のダウンロード
FetchContentは、初めてビルドする際に依存関係をダウンロードするため、初回ビルドに時間がかかる場合があります。ただし、一度ダウンロードされると、通常はキャッシュされます。
FetchContent
を利用する際によく遭遇するエラーとその解決策を以下に示します。
インターネット接続がない、またはリポジトリにアクセスできない
エラーの症状
- プロキシ設定の問題。
Could not resolve host
やFailed to connect to ...
といったネットワーク関連のエラー。FetchContent_Populate
やFetchContent_MakeAvailable
実行時に、Gitリポジトリへの接続エラーやダウンロード失敗のメッセージが表示される。
原因
- ファイアウォールや企業ネットワークのプロキシ設定が原因でアクセスがブロックされている。
- 指定されたリポジトリのURLが間違っている、または存在しない。
- ビルドマシンがインターネットに接続されていない。
トラブルシューティング
- インターネット接続の確認
まず、ビルドマシンがインターネットに接続できているかを確認します。ブラウザで指定のリポジトリURLにアクセスできるか試してください。 - リポジトリURLの確認
FetchContent_Declare
で指定したGIT_REPOSITORY
やURL
が正しいか、タイプミスがないかを確認します。 - プロキシ設定
企業ネットワークなどプロキシ経由でインターネットに接続している場合、CMakeがプロキシ設定を認識しているか確認してください。CMakeは通常、環境変数 (http_proxy
,https_proxy
,all_proxy
など) を参照します。- Windowsの場合
システムの環境変数に設定するか、CMakeを起動するシェルで一時的に設定します。 - Linux/macOSの場合
シェルの環境変数として設定します。
または、CMakeのビルドコマンドにexport HTTP_PROXY="http://proxy.example.com:8080" export HTTPS_PROXY="http://proxy.example.com:8080"
CMAKE_ARGS
を使って渡すことも検討できます。 - Windowsの場合
- Gitの確認
Gitが正しくインストールされ、パスが通っているか確認します。git clone <repository_url>
を手動で実行して、問題なくクローンできるか試すのも有効です。
指定したタグ/コミットハッシュ/ブランチが見つからない
エラーの症状
Reference not found
のようなエラー。GIT_TAG
で指定したタグやコミットハッシュ、ブランチが見つからないというGitのエラーメッセージ。
原因
- そのタグやブランチが削除された、または名前が変更された。
- タイプミス。
FetchContent_Declare
で指定したGIT_TAG
が、実際のリポジトリに存在しない。
トラブルシューティング
- タグ/コミット/ブランチの確認
Gitリポジトリ(GitHubなどのWebインターフェースやgit branch -a
,git tag
コマンド)で、指定したGIT_TAG
が本当に存在するかどうかを確認します。 - 大文字小文字の区別
タグやブランチ名は大文字小文字を区別する場合があるため、正確に入力されているか確認します。 - コミットハッシュの利用
タグが安定しない場合や、より厳密なバージョン管理が必要な場合は、特定のコミットハッシュ (GIT_TAG <full_commit_hash>
) を使用することを検討してください。
ダウンロードしたコンテンツがCMakeプロジェクトとして機能しない
エラーの症状
target_link_libraries
でリンクしようとしたターゲットが見つからない。- サブディレクトリを追加しても、期待されるターゲット(ライブラリなど)が生成されない。
add_subdirectory(${MY_LIB_SOURCE_DIR})
を実行した際に、そのディレクトリにCMakeLists.txt
が見つからないというエラー。
原因
FetchContent_MakeAvailable
によって設定される変数名 (MY_LIB_SOURCE_DIR
など) の誤用。- ダウンロードしたコンテンツが、サブディレクトリ内に実際のCMakeプロジェクトを持っている(例:
/src/CMakeLists.txt
)。 - ダウンロードしたコンテンツが、ルートディレクトリに
CMakeLists.txt
を持たない非CMakeプロジェクトである。
トラブルシューティング
- ダウンロードコンテンツの構造確認
ダウンロードされるリポジトリの構造を調べます。- ルートに CMakeLists.txt がある場合
通常通りadd_subdirectory(${MY_LIB_SOURCE_DIR})
で問題ありません。 - サブディレクトリに CMakeLists.txt がある場合
FetchContent_Declare
でSOURCE_DIR
を指定し、サブディレクトリにパスを設定します。FetchContent_Declare( my_library GIT_REPOSITORY ... GIT_TAG ... # ダウンロードされるルートからCMakeLists.txtがあるサブディレクトリへの相対パス SOURCE_SUBDIR some/sub/directory ) FetchContent_MakeAvailable(my_library) # この場合、my_library_SOURCE_DIR は 'some/sub/directory' を含むパスになります。 add_subdirectory(${my_library_SOURCE_DIR})
- CMakeプロジェクトではない場合
FetchContent
でダウンロードするだけにとどめ、必要なソースファイルを自分でadd_library
やadd_executable
で追加する必要があります。
- ルートに CMakeLists.txt がある場合
- 変数名の確認
FetchContent_MakeAvailable(MyLib)
の後、ダウンロードされたソースディレクトリのパスは$${MyLib}_SOURCE_DIR
という変数に格納されます。この変数名が正しく使われているか確認してください。
依存関係の依存関係(Transitive Dependencies)の競合
エラーの症状
- 同じ名前のターゲットが複数回定義されるというエラー。
- 「Multiple definition」エラーや、リンク時のシンボル解決エラー。
- 複数の
FetchContent
でダウンロードしたライブラリが、共通の別のライブラリに依存しており、そのバージョンが異なる場合にビルドエラーが発生する。
原因
add_subdirectory
が同じ名前のターゲットを複数回定義しようとする。- 両方のライブラリが、ライブラリCを静的に(または特定の仕方で)リンクしており、シンボル競合が発生する。
FetchContent
で取得したライブラリAとBが、それぞれ異なるバージョンのライブラリCに依存している。
トラブルシューティング
- 依存グラフの分析
どのライブラリがどのライブラリに依存しているかを把握します。 - 単一バージョンの強制
競合する共通の依存関係を、トップレベルのFetchContent
で一度だけ取得するようにし、他のライブラリがそれを再利用するように設定します。これは最も複雑な解決策の一つで、それぞれのライブラリのCMakeLists.txt
が再利用可能な設計になっている必要があります。 - EXTERNAL_PROJECTsの使用検討
複雑な推移的依存関係を持つ場合、FetchContent
よりもExternalProject_Add
の方が適している場合があります。ExternalProject_Add
は、依存関係を完全に独立したプロジェクトとしてビルドし、インストールすることで、衝突を回避しやすくなります。 - 名前空間の利用
可能であれば、ライブラリが適切な名前空間を使用していることを確認し、シンボル競合を軽減します。 - CMakeのポリシー
CMP0048
やCMP0077
のようなCMakeのポリシーが、ターゲット名やインターフェースの動作に影響を与える場合があります。必要に応じてポリシー設定を確認・調整します。
キャッシュの問題
エラーの症状
- Gitリポジトリのタグを変更したのに、古いバージョンのコードがフェッチされる。
FetchContent_Declare
の設定を変更したのに、ダウンロードされるソースコードが古いまま。
原因
- Gitのキャッシュが残っている。
- CMakeが以前ダウンロードしたコンテンツをキャッシュしており、再ダウンロードされない。
トラブルシューティング
- CMakeキャッシュのクリア
最も一般的な解決策です。- ビルドディレクトリ全体を削除し、最初からやり直します。
- または、
CMakeCache.txt
とCMakeFiles/
ディレクトリを削除します。
- FetchContentキャッシュの削除
FetchContent
がダウンロードしたコンテンツは、通常_deps
ディレクトリ(またはユーザー指定のディレクトリ)に格納されます。このディレクトリの中身を手動で削除することで、強制的に再ダウンロードさせることができます。- 例:
your_build_directory/_deps/my_library-src
など
- 例:
- FetchContent_Populate の OVERRIDE_FIND_PACKAGE / PREVENT_FETCH
PREVENT_FETCH
を使うと、既にダウンロード済みの場合はフェッチをスキップします。意図的に常に最新を取得したい場合はこのオプションを使わないか、キャッシュをクリアする必要があります。FetchContent_Declare
でUPDATE_DISCONNECTED TRUE
などを設定すると、Gitのfetch
コマンドが失敗した場合でもエラーを無視し、以前にクローンされたものを使い続けることができます。これは開発時には便利ですが、新しい変更を常に取得したい場合には注意が必要です。
CMakeのバージョンが古すぎる
エラーの症状
FetchContent_Declare
やFetchContent_MakeAvailable
が見つからない、または認識されないというエラー。
原因
FetchContent
モジュールは CMake 3.11 以降で導入されました。それより古いバージョンのCMakeを使用している。
- CMakeのバージョンアップ
CMakeを3.11以降のバージョンにアップグレードしてください。推奨は、常に最新の安定版を使用することです。
例1:基本的な使い方(GitHubからライブラリをフェッチして使用)
この例では、GitHubから軽量な単一ヘッダテストフレームワークである "Catch2" のソースコードをダウンロードし、自分のプロジェクトでテストをビルドするためにそれを使用する方法を示します。
プロジェクト構成
my_project/
├── CMakeLists.txt
└── src/
└── main.cpp
my_project/CMakeLists.txt
cmake_minimum_required(VERSION 3.11 FATAL_ERROR) # FetchContentは3.11以降が必要
project(MyProject LANGUAGES CXX)
# FetchContentモジュールをインクルード
include(FetchContent)
# Catch2 を定義 (GitHubリポジトリとタグを指定)
# Catch2 は通常、単一ヘッダファイルとして配布されますが、
# ビルドシステムに組み込むためにこのようにフェッチすることも可能です。
FetchContent_Declare(
Catch2 # この名前が、内部で使われる変数名のプレフィックスになります (例: Catch2_SOURCE_DIR)
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.2 # 使用したいCatch2のバージョンを指定
# 特定のコミットハッシュを指定することもできます: GIT_TAG 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
)
# FetchContentで定義したCatch2を実際にダウンロードし、現在のプロジェクトで利用可能にする
# これにより、Catch2のCMakeプロジェクトが利用可能になり、
# Catch2_SOURCE_DIR にダウンロードされたソースのパスが設定されます。
FetchContent_MakeAvailable(Catch2)
# Catch2 が提供するCMakeターゲット (例: Catch2::Catch2WithMain) を使用して、
# 自分のテスト実行可能ファイルをビルドする
# Catch2のCMakeLists.txtが提供するターゲットは通常 Catch2::Catch2WithMain や Catch2::Catch2 などです。
# 詳細はそのライブラリのドキュメントを確認してください。
add_executable(my_test src/main.cpp)
target_link_libraries(my_test PRIVATE Catch2::Catch2WithMain)
# (オプション)テストを有効にする
# enable_testing()
# add_test(NAME MyTest COMMAND my_test)
my_project/src/main.cpp
#define CATCH_CONFIG_MAIN // Catch2が提供するmain関数を使用
#include <catch2/catch_all.hpp> // Catch2のヘッダをインクルード
// テストケースの例
TEST_CASE("Factorials are computed", "[factorial]") {
REQUIRE(1 == 1);
REQUIRE(2 == 2);
REQUIRE(6 == 6);
REQUIRE(24 == 24);
}
ビルド手順
mkdir build
cd build
cmake ..
cmake --build .
./my_test # テストを実行
この例では、Catch2
という名前でGitリポジトリとタグを宣言し、FetchContent_MakeAvailable(Catch2)
を呼び出すことで、Catch2のソースコードがダウンロードされ、CMakeのビルドシステムに組み込まれます。これにより、Catch2::Catch2WithMain
のようなCatch2が提供するターゲットをtarget_link_libraries
で直接使用できるようになります。
例2:Gitサブモジュールの代わりにFetchContentを使用する
既存のGitサブモジュールをFetchContent
に置き換える一般的なシナリオです。ここでは、軽量なコマンドライン引数パーサーである "CLI11" をフェッチする例を示します。
プロジェクト構成
my_app/
├── CMakeLists.txt
└── app/
└── main.cpp
my_app/CMakeLists.txt
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
project(MyApp LANGUAGES CXX)
# FetchContentモジュールをインクルード
include(FetchContent)
# CLI11 を定義
FetchContent_Declare(
CLI11
GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git
GIT_TAG v2.4.0 # 安定版のタグを指定
)
# CLI11をダウンロードし、利用可能にする
FetchContent_MakeAvailable(CLI11)
# アプリケーションの実行ファイルをビルド
add_executable(my_application app/main.cpp)
# CLI11をリンクする
# CLI11は単一ヘッダライブラリですが、CMakeターゲットを提供している場合、
# このようにリンクすることで、インクルードパスなどが自動的に設定されます。
target_link_libraries(my_application PRIVATE CLI11::CLI11) # CLI11が提供するターゲット名
my_app/app/main.cpp
#include <iostream>
#include <string>
#include <CLI/CLI.hpp> // CLI11のヘッダ
int main(int argc, char** argv) {
CLI::App app{"My Awesome App"};
std::string name = "World";
app.add_option("-n,--name", name, "Name to greet");
int count = 1;
app.add_option("-c,--count", count, "Number of greetings");
try {
app.parse(argc, argv);
} catch (const CLI::ParseError &e) {
return app.exit(e);
}
for (int i = 0; i < count; ++i) {
std::cout << "Hello, " << name << "!" << std::endl;
}
return 0;
}
ビルド手順
mkdir build
cd build
cmake ..
cmake --build .
./my_application -n CMake -c 3 # コマンドライン引数を渡して実行
この例では、CLI11
をFetchContent
で取得し、CLI11::CLI11
ターゲットを使って自分のアプリケーションにリンクしています。これにより、インクルードパスの設定などを手動で行う必要がなくなります。
例3:特定のサブディレクトリにあるCMakeLists.txtをフェッチして使用する
フェッチしたリポジトリのルートにCMakeLists.txt
がない場合や、必要なサブプロジェクトがルートではない場所にある場合に役立つ例です。多くの大規模なライブラリは、ルートにトップレベルのCMakeLists.txt
を持ちつつ、個々のコンポーネントがサブディレクトリに独自のCMakeLists.txt
を持つ構造をしています。
この例では、仮にmy_utility_lib
というライブラリが、そのソースコードとCMakeLists.txtをsrc/lib
というサブディレクトリに持っていると仮定します。
仮想的なリモートリポジトリの構造
https://github.com/example/my_utility_lib.git
├── .git/
├── src/
│ ├── lib/
│ │ ├── CMakeLists.txt <-- ここに目的のCMakeLists.txtがある
│ │ └── util.cpp
│ └── main_app/
│ └── main.cpp
└── tests/
my_project/CMakeLists.txt
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
project(MyProjectWithUtil LANGUAGES CXX)
include(FetchContent)
FetchContent_Declare(
MyUtilityLib
GIT_REPOSITORY https://github.com/example/my_utility_lib.git # 仮のリポジトリ
GIT_TAG main # または特定のタグ、コミットハッシュ
# 重要: ダウンロードしたリポジトリのルートから、CMakeLists.txtが存在するサブディレクトリへのパス
SOURCE_SUBDIR src/lib
)
# フェッチと利用可能化
FetchContent_MakeAvailable(MyUtilityLib)
# MyUtilityLib_SOURCE_DIR は 'src/lib' を含む絶対パスになる
# 例: /path/to/build/_deps/myutilitylib-src/src/lib
add_subdirectory(${MyUtilityLib_SOURCE_DIR})
# 自身のアプリケーションをビルド
add_executable(my_app main.cpp)
# MyUtilityLib::MyUtility というターゲットが、フェッチしたCMakeLists.txtによって定義されると仮定
target_link_libraries(my_app PRIVATE MyUtilityLib::MyUtility)
my_project/main.cpp
#include <iostream>
// MyUtilityLibが提供するヘッダ(例えば util.hpp)
#include <util.hpp> // このヘッダはMyUtilityLib::MyUtilityターゲットのPUBLIC_INCLUDE_DIRECTORIESで設定されるはず
int main() {
std::cout << "Hello from MyProject!" << std::endl;
MyUtility::do_something(); // MyUtilityLibの関数を呼び出す
return 0;
}
この例では、SOURCE_SUBDIR src/lib
を指定することで、FetchContent_MakeAvailable
がmy_utility_lib
リポジトリをダウンロードした後、内部的に_deps/myutilitylib-src/src/lib
ディレクトリをソースディレクトリとして設定し、その中のCMakeLists.txt
を処理します。
例4:フォールバックメカニズム (FetchContent + find_package)
最初にシステムにインストールされているパッケージを探し、見つからない場合にのみFetchContent
でダウンロードしてビルドする、というフォールバックメカニズムを実装する例です。
my_project/CMakeLists.txt
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
project(MyProjectWithFallback LANGUAGES CXX)
# Boostパッケージを見つける試み
find_package(Boost 1.70 COMPONENTS system filesystem REQUIRED OPTIONAL)
# Boostが見つからなかった場合のみFetchContentを使用する
if (NOT Boost_FOUND)
message(STATUS "Boost not found on system. Fetching Boost via FetchContent...")
include(FetchContent)
# Boostのソースをフェッチ (例: Boostの特定のバージョン)
# Boostは非常に大きく、ビルドに時間がかかるため、この方法は注意が必要です。
# この例は概念を示すためのもので、実際のプロジェクトではより軽量なライブラリで検討するべきです。
FetchContent_Declare(
Boost
GIT_REPOSITORY https://github.com/boostorg/boost.git
GIT_TAG boost-1.84.0 # 使用したいBoostのバージョン
# Boostのサブモジュールをフェッチする場合、以下のような設定が必要になる場合があります
# GIT_SUBMODULES ALL
)
# FetchContentから利用可能にする
# Boostのような大規模なライブラリの場合、FetchContent_MakeAvailable_Quietly などを使うことも検討
FetchContent_MakeAvailable(Boost)
# Boostが提供するターゲットをリンク
# FetchContentでフェッチされたBoostの場合、Boost::system, Boost::filesystem などが利用可能になるはず
set(Boost_FOUND TRUE) # find_packageの代わりとして、FetchContentが成功したことをマーク
set(Boost_LIBRARIES Boost::system Boost::filesystem) # FetchContentで提供されるターゲット名
set(Boost_INCLUDE_DIRS ${Boost_SOURCE_DIR}) # 必要に応じてインクルードパスも設定
else()
message(STATUS "Boost found on system. Using system Boost.")
endif()
# アプリケーションをビルド
add_executable(my_app main.cpp)
# Boostのターゲットをリンク
target_link_libraries(my_app PRIVATE ${Boost_LIBRARIES})
# インクルードパスも必要に応じて追加
target_include_directories(my_app PRIVATE ${Boost_INCLUDE_DIRS})
この例では、まずfind_package(Boost ... OPTIONAL)
でBoostを探し、Boost_FOUND
変数がFALSE
の場合にのみFetchContent
を使用してBoostをダウンロード・ビルドします。このようにすることで、ユーザーがシステムにインストール済みのライブラリを使いたい場合に柔軟性を提供できます。
FetchContent
は非常に便利ですが、すべてのシナリオに最適なわけではありません。ここでは、外部依存関係をCMakeプロジェクトに組み込むための代替手段をいくつか紹介します。
Git Submodules (Gitサブモジュール)
仕組み
git submodule add <repository_url> <path>
コマンドでサブモジュールを追加します。- 親リポジトリに
.gitmodules
ファイルが作成され、サブモジュールの情報(URLとパス)が記録されます。 - クローン後、
git submodule update --init --recursive
でサブモジュールのコンテンツをダウンロードします。 - CMakeでは、通常
add_subdirectory(<path_to_submodule>)
を使って、サブモジュールのCMakeLists.txtをプロジェクトに組み込みます。
利点
- Gitネイティブ
Gitユーザーには馴染み深い方法。 - 厳密なバージョン管理
親リポジトリがサブモジュールの特定のコミットを指すため、依存関係のバージョンが厳密に固定される。 - オフラインビルド
一度クローンすれば、インターネット接続なしでビルドできる。
欠点
- FetchContentとの違い
FetchContent
はビルド時に自動的にフェッチするのに対し、サブモジュールはgit clone
後に手動でgit submodule update
が必要。 - レポジトリの肥大化
多くのサブモジュールがあると、親リポジトリのクローンが遅くなる場合がある。 - Gitの知識
サブモジュールの操作にはGitの知識が必要で、初心者には戸惑うことがある。 - 手動更新
サブモジュールのバージョンを更新するには、git submodule update --remote
やgit pull
などのGitコマンドを明示的に実行し、親リポジトリの変更をコミットする必要がある。
使いどころ
- Gitのワークフローに慣れているチーム。
- 依存関係のバージョンを非常に厳密に管理したい場合。
- インターネット接続が常に利用可能でない環境。
ExternalProject_Add (ExternalProjectモジュール)
仕組み
include(ExternalProject)
でモジュールをインクルードします。ExternalProject_Add()
コマンドを使って、外部プロジェクトのソース取得方法(Git、URLダウンロードなど)、ビルドコマンド、インストールコマンドなどを詳細に設定します。- 外部プロジェクトは、現在のCMakeプロジェクトとは独立したステップとして実行されます。そのビルド結果(ライブラリやヘッダ)は、
target_link_libraries
やtarget_include_directories
で自分のプロジェクトにリンクされます。
利点
- FetchContentのバックエンド
実はFetchContent
の内部でExternalProject
が使われています。 - キャッシュと再利用
ダウンロードされたプロジェクトはキャッシュされ、次回以降のビルドで再利用される。 - 複雑な依存関係
依存関係が別のビルドシステム(例: Autotools, Meson, Make)を使用している場合や、特定のビルドオプションが必要な場合に非常に有効。 - 最高の柔軟性
ソースの取得、ビルド、インストールプロセスを完全にカスタマイズできる。
欠点
- デバッグの複雑さ
外部プロジェクトのビルド問題のデバッグが難しい場合がある。 - 依存関係の可視性
外部プロジェクトは独立してビルドされるため、親プロジェクトのCMakeからはその内部のターゲットが見えない場合がある。 - 複雑な設定
FetchContent
に比べて設定がはるかに複雑で、学習コストが高い。
使いどころ
- ビルド済みのライブラリをダウンロードして使用したい場合(
URL
オプション)。 - 外部プロジェクトを完全に分離してビルドし、特定の場所にインストールしたい場合。
FetchContent
では対応できない、非常に複雑なビルドプロセスを持つ外部依存関係。
例(簡易版)
include(ExternalProject)
ExternalProject_Add(
MyExternalLib
GIT_REPOSITORY https://github.com/some_user/my_external_lib.git
GIT_TAG v1.0.0
# ビルドとインストールのステップを定義(例は非常に簡略化)
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR>
INSTALL_COMMAND ${CMAKE_COMMAND} --install <BINARY_DIR>
)
# 外部ライブラリがインストールされた後、自分のターゲットにリンクする
add_executable(my_app main.cpp)
# ExternalProjectがインストールする場所からヘッダとライブラリを見つける
ExternalProject_Get_Property(MyExternalLib INSTALL_DIR)
target_include_directories(my_app PRIVATE "${INSTALL_DIR}/include")
target_link_libraries(my_app PRIVATE "${INSTALL_DIR}/lib/MyExternalLib.lib") # Windowsの場合
# target_link_libraries(my_app PRIVATE "${INSTALL_DIR}/lib/libMyExternalLib.a") # Linuxの場合
find_package() with Pre-installed Libraries (事前にインストールされたライブラリの使用)
仕組み
find_package(<PackageName> [version] [REQUIRED|OPTIONAL] [COMPONENTS ...])
を呼び出します。- CMakeは、システムパスや環境変数 (
CMAKE_PREFIX_PATH
など) を検索して、<PackageName>Config.cmake
やFind<PackageName>.cmake
といったファイルを探します。 - 見つかった場合、ライブラリのインクルードディレクトリ、ライブラリファイル、リンクオプションなどが設定され、
target_link_libraries()
などで利用可能になります。
利点
- 簡潔なCMakeコード
依存関係のビルドプロセスをCMakeLists.txtに記述する必要がない。 - システムリソースの共有
複数のプロジェクトで同じライブラリのインスタンスを共有できる。 - ビルド速度
依存関係を再ビルドする必要がないため、ビルドが速い。
欠点
- ポータビリティの低さ
新しい環境でのセットアップが複雑になることがある。 - バージョン競合
必要なバージョンがシステムにインストールされていない場合や、他のプロジェクトとバージョンが競合する場合がある。 - 環境依存性
ビルド環境ごとにライブラリのインストールが必要で、開発者間で環境を揃えるのが難しい。
使いどころ
- 開発者が依存関係の手動管理に抵抗がない場合。
- CI/CD環境で、事前に必要なライブラリをインストールするステップが組み込まれている場合。
- 広く普及しており、多くのシステムで簡単にインストールできるライブラリ(例: Boost, Qt, OpenSSL)。
例
cmake_minimum_required(VERSION 3.10)
project(MyApp LANGUAGES CXX)
# Boostライブラリを見つける
# Boostのバージョン1.70以上が必要で、systemとfilesystemコンポーネントを必要とする
find_package(Boost 1.70 REQUIRED COMPONENTS system filesystem)
# Boostが見つかった場合にのみ実行
if (Boost_FOUND)
message(STATUS "Found Boost version ${Boost_VERSION}")
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE Boost::system Boost::filesystem) # Boostが提供するターゲット名
target_include_directories(my_app PRIVATE ${Boost_INCLUDE_DIRS}) # ヘッダパス
else()
message(FATAL_ERROR "Boost library not found. Please install it or adjust CMake settings.")
endif()
Package Managers (パッケージマネージャー)
仕組み
- CPM.cmake
FetchContent
をベースに、より使いやすいAPIとキャッシュ機能を提供し、単一のCMakeファイルで利用できる軽量なパッケージマネージャー。 - hunter
CMakeベースのパッケージマネージャーで、CMakeの機能のみで依存関係を管理します。 - vcpkg
Microsoftが開発したパッケージマネージャーで、Windows、Linux、macOSをサポートし、CMakeとの統合も簡単です。利用可能なライブラリは vcpkg のレジストリで管理されます。 - Conan
プロジェクトの依存関係をconanfile.txt
やconanfile.py
で定義し、conan install
で解決・ダウンロード・ビルドします。CMakeとの連携機能も充実しています。
利点
- 開発者体験
セットアップが簡単で、開発者は依存関係の管理に煩わされることが少ない。 - 効率的なキャッシュ
ダウンロードやビルド結果をキャッシュし、再利用することでビルド時間を短縮。 - 再現性
依存関係のバージョンを固定し、異なる環境でも同じビルド結果を保証しやすい。 - 完全な依存関係管理
依存関係の推移性、競合解決、異なる環境(OS、コンパイラ、ビルドタイプ)への対応。
欠点
- 利用可能なパッケージ
必要なライブラリがパッケージマネージャーのレジストリにない場合がある。 - ツールへの依存
パッケージマネージャーがプロジェクトのビルドシステムの一部となるため、そのツールに依存する。 - 学習曲線
新しいツールと概念を学ぶ必要がある。
使いどころ
- チーム全体で標準化された依存関係管理手法を採用したい場合。
- CI/CDパイプラインでの依存関係の自動化と再現性が重要である場合。
- 複雑なC++プロジェクトで、多くの外部依存関係がある場合。
例(Conanの場合)
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyConanApp LANGUAGES CXX)
# ConanのCMakeToolchainとCMakeDepsをインクルード
# これらは conan install によって生成される
if (NOT CMAKE_TOOLCHAIN_FILE)
message(FATAL_ERROR "Please run 'conan install . -s build_type=Release --build=missing' first.")
endif()
include(${CMAKE_TOOLCHAIN_FILE})
add_executable(my_app main.cpp)
# conanfile.txt で定義された依存関係のターゲットにリンク
target_link_libraries(my_app PRIVATE fmt::fmt) # fmtというライブラリの例
conanfile.txt (プロジェクトルートに配置)
[requires]
fmt/10.2.1
[generators]
CMakeDeps
CMakeToolchain
mkdir build
cd build
conan install .. --output-folder=. --build=missing
cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake
cmake --build .
方法 | 利点 | 欠点 | 最適なケース |
---|---|---|---|
FetchContent | 簡単、自動ダウンロード、Gitの知識不要 | オフラインビルドは初回のみ、ExternalProject より柔軟性がない | 軽量な依存関係、簡単なGitリポジトリからの取得、CI/CDでの自動化 |
Git Submodules | オフラインビルド、厳密なバージョン固定 | 手動更新、Git知識が必要、レポジトリ肥大化 | 厳密なバージョン管理、オフライン要件、Gitワークフローに慣れたチーム |
ExternalProject | 最高の柔軟性、あらゆるビルドシステムに対応 | 設定が複雑、学習コスト高、デバッグ困難 | 複雑なビルドプロセスを持つ依存関係、ダウンロード後に特定のビルドが必要な場合 |
find_package() | ビルド高速、システムリソース共有、CMakeコードが簡潔 | 環境依存、バージョン競合、ポータビリティ低い | 広く普及したライブラリ、CI/CDで事前にインストールされている場合 |
Package Managers | 完全な依存管理、再現性、効率的なキャッシュ、開発者体験 | 学習曲線、ツールへの依存、利用可能なパッケージの制限 | 多数の依存関係、複雑な推移的依存関係、チームでの標準化、CI/CD |