FetchContent

2025-06-06

CMakeにおける「FetchContent」とは何か?

これまでのCMakeでは、外部ライブラリを使う場合、以下のいずれかの方法を取る必要がありました。

  1. システムインストールされたライブラリを使う
    find_package() などでシステムにインストールされているライブラリを探す。この場合、ビルド環境ごとにライブラリのインストールが必要になる。
  2. 外部ライブラリをサブモジュールとしてリポジトリに含める
    Gitのサブモジュールなどを使って、依存ライブラリのソースコードを自分のリポジトリ内に含める。
  3. 外部ライブラリのビルド済みバイナリをダウンロードして使う
    ビルド済みライブラリをダウンロードし、そのパスをCMakeに教える。

FetchContentは、これらの課題を解決し、プロジェクトのビルドプロセスの一部として依存関係のソースコードを自動的に取得し、そのままビルドに含めるという、より現代的なアプローチを提供します。

FetchContentの主な特徴と利点

  • 様々な取得方法に対応
    Gitリポジトリ、URLからのアーカイブダウンロードなど、複数の取得方法をサポートしています。
  • 組み込みと使用が容易
    ダウンロードされた依存関係は、通常のCMakeのサブディレクトリやターゲットとしてプロジェクトに組み込むことができます。
  • ビルド環境のポータビリティ向上
    依存関係が自動的に取得されるため、異なる環境間でのビルド設定が容易になります。開発者全員が同じバージョンの依存関係を使用することを保証できます。
  • シンプルな依存関係管理
    プロジェクトのCMakeLists.txtファイル内で、どの依存関係が必要かを記述するだけで済みます。
  • 依存関係の自動ダウンロード
    GitHubなどのリモートリポジトリから、指定したバージョンのソースコードを自動的にダウンロードします。

FetchContentの基本的な使い方

FetchContentを使用するには、通常以下のステップを踏みます。

  1. FetchContentモジュールのインクルード
    まず、FetchContentモジュールをプロジェクトにインクルードします。

    include(FetchContent)
    
  2. 依存関係の定義とフェッチ
    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 hostFailed to connect to ... といったネットワーク関連のエラー。
  • FetchContent_PopulateFetchContent_MakeAvailable 実行時に、Gitリポジトリへの接続エラーやダウンロード失敗のメッセージが表示される。

原因

  • ファイアウォールや企業ネットワークのプロキシ設定が原因でアクセスがブロックされている。
  • 指定されたリポジトリのURLが間違っている、または存在しない。
  • ビルドマシンがインターネットに接続されていない。

トラブルシューティング

  1. インターネット接続の確認
    まず、ビルドマシンがインターネットに接続できているかを確認します。ブラウザで指定のリポジトリURLにアクセスできるか試してください。
  2. リポジトリURLの確認
    FetchContent_Declare で指定した GIT_REPOSITORYURL が正しいか、タイプミスがないかを確認します。
  3. プロキシ設定
    企業ネットワークなどプロキシ経由でインターネットに接続している場合、CMakeがプロキシ設定を認識しているか確認してください。CMakeは通常、環境変数 (http_proxy, https_proxy, all_proxy など) を参照します。
    • Windowsの場合
      システムの環境変数に設定するか、CMakeを起動するシェルで一時的に設定します。
    • Linux/macOSの場合
      シェルの環境変数として設定します。
    export HTTP_PROXY="http://proxy.example.com:8080"
    export HTTPS_PROXY="http://proxy.example.com:8080"
    
    または、CMakeのビルドコマンドに CMAKE_ARGS を使って渡すことも検討できます。
  4. Gitの確認
    Gitが正しくインストールされ、パスが通っているか確認します。git clone <repository_url> を手動で実行して、問題なくクローンできるか試すのも有効です。

指定したタグ/コミットハッシュ/ブランチが見つからない

エラーの症状

  • Reference not found のようなエラー。
  • GIT_TAG で指定したタグやコミットハッシュ、ブランチが見つからないというGitのエラーメッセージ。

原因

  • そのタグやブランチが削除された、または名前が変更された。
  • タイプミス。
  • FetchContent_Declare で指定した GIT_TAG が、実際のリポジトリに存在しない。

トラブルシューティング

  1. タグ/コミット/ブランチの確認
    Gitリポジトリ(GitHubなどのWebインターフェースや git branch -a, git tag コマンド)で、指定した GIT_TAG が本当に存在するかどうかを確認します。
  2. 大文字小文字の区別
    タグやブランチ名は大文字小文字を区別する場合があるため、正確に入力されているか確認します。
  3. コミットハッシュの利用
    タグが安定しない場合や、より厳密なバージョン管理が必要な場合は、特定のコミットハッシュ (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プロジェクトである。

トラブルシューティング

  1. ダウンロードコンテンツの構造確認
    ダウンロードされるリポジトリの構造を調べます。
    • ルートに CMakeLists.txt がある場合
      通常通り add_subdirectory(${MY_LIB_SOURCE_DIR}) で問題ありません。
    • サブディレクトリに CMakeLists.txt がある場合
      FetchContent_DeclareSOURCE_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_libraryadd_executable で追加する必要があります。
  2. 変数名の確認
    FetchContent_MakeAvailable(MyLib) の後、ダウンロードされたソースディレクトリのパスは $${MyLib}_SOURCE_DIR という変数に格納されます。この変数名が正しく使われているか確認してください。

依存関係の依存関係(Transitive Dependencies)の競合

エラーの症状

  • 同じ名前のターゲットが複数回定義されるというエラー。
  • 「Multiple definition」エラーや、リンク時のシンボル解決エラー。
  • 複数の FetchContent でダウンロードしたライブラリが、共通の別のライブラリに依存しており、そのバージョンが異なる場合にビルドエラーが発生する。

原因

  • add_subdirectory が同じ名前のターゲットを複数回定義しようとする。
  • 両方のライブラリが、ライブラリCを静的に(または特定の仕方で)リンクしており、シンボル競合が発生する。
  • FetchContent で取得したライブラリAとBが、それぞれ異なるバージョンのライブラリCに依存している。

トラブルシューティング

  1. 依存グラフの分析
    どのライブラリがどのライブラリに依存しているかを把握します。
  2. 単一バージョンの強制
    競合する共通の依存関係を、トップレベルの FetchContent で一度だけ取得するようにし、他のライブラリがそれを再利用するように設定します。これは最も複雑な解決策の一つで、それぞれのライブラリの CMakeLists.txt が再利用可能な設計になっている必要があります。
  3. EXTERNAL_PROJECTsの使用検討
    複雑な推移的依存関係を持つ場合、FetchContent よりも ExternalProject_Add の方が適している場合があります。ExternalProject_Add は、依存関係を完全に独立したプロジェクトとしてビルドし、インストールすることで、衝突を回避しやすくなります。
  4. 名前空間の利用
    可能であれば、ライブラリが適切な名前空間を使用していることを確認し、シンボル競合を軽減します。
  5. CMakeのポリシー
    CMP0048CMP0077 のようなCMakeのポリシーが、ターゲット名やインターフェースの動作に影響を与える場合があります。必要に応じてポリシー設定を確認・調整します。

キャッシュの問題

エラーの症状

  • Gitリポジトリのタグを変更したのに、古いバージョンのコードがフェッチされる。
  • FetchContent_Declare の設定を変更したのに、ダウンロードされるソースコードが古いまま。

原因

  • Gitのキャッシュが残っている。
  • CMakeが以前ダウンロードしたコンテンツをキャッシュしており、再ダウンロードされない。

トラブルシューティング

  1. CMakeキャッシュのクリア
    最も一般的な解決策です。
    • ビルドディレクトリ全体を削除し、最初からやり直します。
    • または、CMakeCache.txtCMakeFiles/ ディレクトリを削除します。
  2. FetchContentキャッシュの削除
    FetchContent がダウンロードしたコンテンツは、通常 _deps ディレクトリ(またはユーザー指定のディレクトリ)に格納されます。このディレクトリの中身を手動で削除することで、強制的に再ダウンロードさせることができます。
    • 例: your_build_directory/_deps/my_library-src など
  3. FetchContent_Populate の OVERRIDE_FIND_PACKAGE / PREVENT_FETCH
    • PREVENT_FETCH を使うと、既にダウンロード済みの場合はフェッチをスキップします。意図的に常に最新を取得したい場合はこのオプションを使わないか、キャッシュをクリアする必要があります。
    • FetchContent_DeclareUPDATE_DISCONNECTED TRUE などを設定すると、Gitの fetch コマンドが失敗した場合でもエラーを無視し、以前にクローンされたものを使い続けることができます。これは開発時には便利ですが、新しい変更を常に取得したい場合には注意が必要です。

CMakeのバージョンが古すぎる

エラーの症状

  • FetchContent_DeclareFetchContent_MakeAvailable が見つからない、または認識されないというエラー。

原因

  • FetchContent モジュールは CMake 3.11 以降で導入されました。それより古いバージョンのCMakeを使用している。
  1. 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 # コマンドライン引数を渡して実行

この例では、CLI11FetchContentで取得し、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_MakeAvailablemy_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サブモジュール)

仕組み

  1. git submodule add <repository_url> <path> コマンドでサブモジュールを追加します。
  2. 親リポジトリに .gitmodules ファイルが作成され、サブモジュールの情報(URLとパス)が記録されます。
  3. クローン後、git submodule update --init --recursive でサブモジュールのコンテンツをダウンロードします。
  4. CMakeでは、通常 add_subdirectory(<path_to_submodule>) を使って、サブモジュールのCMakeLists.txtをプロジェクトに組み込みます。

利点

  • Gitネイティブ
    Gitユーザーには馴染み深い方法。
  • 厳密なバージョン管理
    親リポジトリがサブモジュールの特定のコミットを指すため、依存関係のバージョンが厳密に固定される。
  • オフラインビルド
    一度クローンすれば、インターネット接続なしでビルドできる。

欠点

  • FetchContentとの違い
    FetchContentはビルド時に自動的にフェッチするのに対し、サブモジュールはgit clone後に手動でgit submodule updateが必要。
  • レポジトリの肥大化
    多くのサブモジュールがあると、親リポジトリのクローンが遅くなる場合がある。
  • Gitの知識
    サブモジュールの操作にはGitの知識が必要で、初心者には戸惑うことがある。
  • 手動更新
    サブモジュールのバージョンを更新するには、git submodule update --remotegit pull などのGitコマンドを明示的に実行し、親リポジトリの変更をコミットする必要がある。

使いどころ

  • Gitのワークフローに慣れているチーム。
  • 依存関係のバージョンを非常に厳密に管理したい場合。
  • インターネット接続が常に利用可能でない環境。

ExternalProject_Add (ExternalProjectモジュール)

仕組み

  1. include(ExternalProject) でモジュールをインクルードします。
  2. ExternalProject_Add() コマンドを使って、外部プロジェクトのソース取得方法(Git、URLダウンロードなど)、ビルドコマンド、インストールコマンドなどを詳細に設定します。
  3. 外部プロジェクトは、現在のCMakeプロジェクトとは独立したステップとして実行されます。そのビルド結果(ライブラリやヘッダ)は、target_link_librariestarget_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 (事前にインストールされたライブラリの使用)

仕組み

  1. find_package(<PackageName> [version] [REQUIRED|OPTIONAL] [COMPONENTS ...]) を呼び出します。
  2. CMakeは、システムパスや環境変数 (CMAKE_PREFIX_PATHなど) を検索して、<PackageName>Config.cmakeFind<PackageName>.cmake といったファイルを探します。
  3. 見つかった場合、ライブラリのインクルードディレクトリ、ライブラリファイル、リンクオプションなどが設定され、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.txtconanfile.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