FetchContentは、CMakeのconfigure時に外部ライブラリのソースを取得して、そのままビルド対象へ組み込むための標準モジュールです。ここではFetchContent_DeclareとFetchContent_MakeAvailableの最小構成から、主要オプションの対応バージョン、Gitとアーカイブのどちらで取るか、GoogleTestの組み込み例、CMake 3.30で書き方が変わった箇所、CMake 4系で依存側が止まるケースまでを扱います。コードと出力は、2026年10月時点の最新安定版CMake 4.4.4(10月2日公開)で実際に動かしたものです。
まとめ
結論から言うと、いま新規に書くならFetchContent_Declare()で宣言し、FetchContent_MakeAvailable()で取り込む、この2つだけを使います。FetchContent_GetProperties()とFetchContent_Populate(<name>)を組み合わせる古いパターンは、CMake 3.30で非推奨になり、cmake_minimum_required(VERSION 3.30)以上を宣言したプロジェクトでは構成エラーで停止します。
押さえておきたい要点は次のとおりです。
- モジュール自体はCMake 3.11から標準搭載ですが、
FetchContent_MakeAvailable()は3.14で追加されたため、最小要求バージョンは3.14以上にします。 - 同じ名前で複数回宣言した場合は最初の宣言が採用されます。
GIT_TAGはコミットハッシュで固定するのが原則ですが、GIT_SHALLOWと併用する場合はタグ名かブランチ名しか使えません。- 配布用のアーカイブがある依存は、
URLとURL_HASHで取得できます。本文のgoogletest比較では、展開後のソースディレクトリ容量は完全なクローンの約4分の1でした。信頼できる配布元のハッシュを指定すれば、配布物の変更も検出できます。 - CMake 4系では、自分のプロジェクトが新しくても、取得した依存が設定するポリシーバージョンが3.5未満だとそこで停止します。CMake公式ドキュメントの例にあるgoogletestの指定をそのまま写すと、ちょうどこれに当たります。最小要求バージョンが3.5未満でも、VERSIONの範囲指定などでポリシーバージョンを3.5以上にしていれば、この互換性エラーにはなりません。
次の節では、FetchContentとExternalProjectの取得タイミングを比較します。
FetchContentが担う役割とExternalProject・add_subdirectoryとの違い
FetchContentはCMake 3.11で追加されたモジュールで、include(FetchContent)の1行で使えます。追加のインストールは不要で、仕様の一次情報はCMake公式のFetchContentモジュールのドキュメントです。特徴は取得のタイミングにあり、ExternalProjectがビルド時にダウンロードするのに対し、FetchContentはconfigure時に取得を完了させます。configureの段階でソースが手元にあるため、そのままadd_subdirectory()で本体のビルドに組み込め、依存ライブラリが定義するCMakeターゲットへtarget_link_libraries()できます。
| 方式 | 取得タイミング | ターゲットの参照 | ソースの管理 |
|---|---|---|---|
| add_subdirectory | 取得しない | できる | 自分でvendoringまたはsubmodule |
| ExternalProject_Add | ビルド時 | できない | 自動 |
| FetchContent | configure時 | できる | 自動 |
ExternalProjectでターゲットを直接参照できないのは、ExternalProject_Add()が外部プロジェクトのCMakeターゲットを親側へ自動では定義しないからです。取得もビルドも自分のビルド時に走るため、configureの時点で参照できる相手がいません。逆に言うと、依存ライブラリを独立した構成・ビルド・インストールのサイクルで回したい場合はExternalProjectが向きます。FetchContentは内部でExternalProjectのダウンロード・更新・パッチの各ステップを使うため、GIT_REPOSITORYやURL_HASHといった取得系オプションの書き方はExternalProject_Addと共通です。ただしconfigure・build・install・testに関わるオプションは明示的に無効化されており、渡しても捨てられます。
FetchContentを選ばない方がよい場面
FetchContentは依存をソースのまま取り込むので、新しいビルドディレクトリを作るたびに依存も一から取得してコンパイルします。googletest v1.18.0の場合、手元(Intel Core i9-9880H・-j1)でのビルドはgmock込みで17.75秒、gmockを切って12.72秒かかりました。依存のビルド時間はライブラリごとに異なります。依存のコンパイルがCIの所要時間を圧迫している場合や、ビルド済みバイナリを社内で配りたい組織では、パッケージマネージャーで事前ビルドしたものをfind_package()で使う方が速く確実です。
両者は排他ではありません。CMake公式の依存関係ガイドは、サードパーティのパッケージマネージャーを「依存プロバイダ」としてfind_package()とFetchContentの要求に割り込ませる仕組みを用意しており、その設定ファイルをCMAKE_PROJECT_TOP_LEVEL_INCLUDESで読み込ませる方法をパッケージマネージャー向けの推奨としています。プロジェクト側はFetchContentで宣言しておき、取得方法は使う人が選ぶ、という分担ができます。
最小構成:DeclareとMakeAvailableの2段構え
argparse(コマンドライン引数の解析ライブラリ)を取り込む、実際に動く最小のCMakeLists.txtは次の形です。同じディレクトリに後掲のmain.cppを置きます。宣言と取り込みを分けるのがこのモジュールの基本設計で、すべての依存を先に宣言してから、まとめてFetchContent_MakeAvailable()に渡します。
cmake_minimum_required(VERSION 3.14)
project(app CXX)
include(FetchContent)
FetchContent_Declare(
argparse
GIT_REPOSITORY https://github.com/p-ranav/argparse.git
GIT_TAG 3eda91b2e1ce7d569f84ba295507c4cd8fd96910 # v3.2
)
FetchContent_MakeAvailable(argparse)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE argparse::argparse)
#include <argparse/argparse.hpp>
#include <iostream>
int main(int argc, char* argv[]) {
argparse::ArgumentParser p("app");
p.add_argument("name");
p.parse_args(argc, argv);
std::cout << "hello " << p.get<std::string>("name") << "\n";
}
cmake -S . -B bとcmake --build bのあとb/app worldを実行すると「hello world」と表示されます。
取得したソースは既定で${CMAKE_BINARY_DIR}/_depsの下に置かれます。CMake 4.4.4で構成すると_deps/argparse-srcにソース、_deps/argparse-buildにビルドツリーが作られ、FetchContent_MakeAvailable()が返った時点でargparse_SOURCE_DIRとargparse_BINARY_DIRが呼び出し側のスコープに定義されます。この配置先を変えたい場合はFETCHCONTENT_BASE_DIRで指定しますが、Windowsのパス長制限に当たりやすくなるため、変えるならビルドツリー直下の短いパスに留めます。
最小要求バージョンの下限は3.11ではなく3.14
FetchContentモジュール自体は3.11で入りましたが、FetchContent_MakeAvailable()が追加されたのは3.14です。cmake_minimum_required(VERSION 3.11)と書いたままFetchContent_MakeAvailable()を呼ぶプロジェクトを、CMake 3.11から3.13の環境で構成すると、モジュールのincludeは通るのにコマンドが見つからずに失敗します。手元の新しいCMakeでは通ってしまうため見落としやすい箇所です。使うコマンドに合わせて3.14以上、後述のポリシー挙動まで固定するなら3.30以上を宣言します。
同じ名前を2回宣言したときに採用される側
依存の詳細は「最初に記録したものが勝つ」方式で管理されます。プロジェクト階層のどこで宣言されたかは関係なく、同じ<name>に対する2回目以降のFetchContent_Declare()は無視されます。手元で、42を返す版のコミットハッシュと99を返す版のコミットハッシュを続けて宣言し、CMake 4.4.4でビルドして実行したところ、出力は42、つまり1つ目の宣言のハッシュがチェックアウトされていました。
FetchContent_Declare(mydep GIT_REPOSITORY ... GIT_TAG f4f3701...) # これが採用される
FetchContent_Declare(mydep GIT_REPOSITORY ... GIT_TAG b1910e5...) # 無視される
FetchContent_MakeAvailable(mydep)
この仕様は、親プロジェクトが子プロジェクトの依存バージョンを上書きするための仕組みです。裏を返すと、依存ライブラリが内部でFetchContentを使って別の依存を引き込んでいる場合、自分より先にその依存を宣言されると自分の指定が効きません。共有される可能性のある依存は、FetchContent_MakeAvailable()を1回でも呼ぶ前に全部宣言しておくのが安全です。
FetchContent_Declareの主要オプションと対応バージョン
宣言時に渡せるオプションは、ExternalProject_Add由来の取得系オプションと、FetchContent固有のオプションに分かれます。固有のものは追加されたバージョンがばらついているため、対応表で確認してから使います。
| オプション | 追加バージョン | 効果 |
|---|---|---|
| SOURCE_SUBDIR | 3.18 | add_subdirectory対象を配下の別ディレクトリにする |
| FIND_PACKAGE_ARGS | 3.24 | find_packageを先に試し、引数を追加する |
| OVERRIDE_FIND_PACKAGE | 3.24 | find_package呼び出しをFetchContentへ迂回させる |
| SYSTEM | 3.25 | 追加されるサブディレクトリをシステム扱いにする |
| EXCLUDE_FROM_ALL | 3.28 | 依存のターゲットをALLから外す |
SYSTEMとEXCLUDE_FROM_ALLは実務での効きが大きいオプションです。SYSTEMを付けると、依存が公開するインクルードディレクトリが利用側のコンパイル時にシステムインクルード扱いになります。GCCやClangはシステムヘッダ由来の警告を抑えるため、依存側のヘッダから出る警告で自分のビルドログが埋まらなくなります。EXCLUDE_FROM_ALLは依存のターゲットをALLターゲットから外すため、テスト用ライブラリのサンプルやベンチマークまで毎回ビルドされる状況を避けられます。どちらも古いCMakeでは単に無視されるわけではなく未知の引数として扱われる可能性があるため、最小要求バージョンと揃えて使います。
なおCMAKE_TLS_VERIFY・CMAKE_TLS_CAINFO・CMAKE_NETRC・CMAKE_NETRC_FILEがFetchContentの取得オプションの既定値として効くようになったのは3.22からです。それ以前のバージョンではこれらの変数はFetchContentから無視されるため、社内プロキシやカスタムCAを使う環境では宣言側に直接オプションを書く必要があります。
GIT_TAGの指定とGIT_SHALLOWを併用したときの制限
GIT_TAGにはブランチ名・タグ名・コミットハッシュのいずれも書けますが、公式ドキュメントはコミットハッシュを推奨しています。理由は2つあり、ローカルのクローンに該当コミットが既にある場合は再構成のたびのgit fetchが不要になること、そして自分のプロジェクトの履歴が依存の特定時点に確実に紐づくことです。ブランチ名やタグ名は指す先が動くため、過去のコミットをチェックアウトしてもビルドが再現しません。指定を省略した場合の既定値はmasterで、リポジトリの既定ブランチ名ではない点にも注意します。
気をつけたいのはGIT_SHALLOWとの併用です。ExternalProjectモジュールのドキュメントのGIT_TAGの項は「If GIT_SHALLOW is enabled then GIT_TAG works only with branch names and tags. A commit hash is not allowed.」と明記しており、仕様としてコミットハッシュは指定できません。やっかいなのは、この禁止が常にエラーとして現れるわけではない点です。CMakeが生成するクローン用スクリプトを読むと、実行されるコマンドは次の形でした。
git clone --no-checkout --depth 1 --no-single-branch <url> <dir>
git checkout <GIT_TAGの値> --
--depth 1 --no-single-branchは、全ブランチと全タグの先端コミットだけを取ってきます。そのため、指定したハッシュがどれかのブランチかタグの先端を指している間はチェックアウトが通ってしまい、仕様に反していることに気づけません。CMake 4.4.4でargparseを対象に確かめると、タグv3.2とv3.1が指すハッシュはいずれも構成が成功し、どのタグにも紐づかない中間コミット(2024年5月30日)のハッシュを指定した場合だけ次のエラーで停止しました。
fatal: unable to read tree (e462ab980c0852bd1df2ee1f4ec81826246b6f21)
CMake Error at b/CMakeFiles/fc-tmp/argparse/argparse-gitclone.cmake:66 (message):
Failed to checkout tag: 'e462ab980c0852bd1df2ee1f4ec81826246b6f21'
通っているように見えても、それは浅いクローンにたまたまそのコミットが含まれていただけで、サポートされた指定ではありません。上流がタグを付け替えれば同じ記述が翌日には落ちます。GIT_SHALLOW TRUEを使うならドキュメントどおりタグ名かブランチ名で書き、コミットハッシュで固定したいならGIT_SHALLOWを外して完全なクローンを取ります。浅いクローンで参照が届かなくなる同種のつまずきは、CIのチェックアウト設定でも起きます。GitHub Actionsでの取得深さの扱いはactions/checkoutの既定変更と浅いクローンの壊れ方で整理しています。
CIでクローンが一時的に落ちるときの再試行回数
CMake 4.4で、git cloneの再試行を制御するCMAKE_EP_GIT_CLONE_RETRY_COUNTとCMAKE_EP_GIT_CLONE_RETRY_DELAYが追加されました(CMake 4.4のリリースノート)。既定は再試行2回・待ち時間0秒で、最初の1回は数えないため合計3回まで試します。存在しないリポジトリを指定し、回数2・待ち1秒で構成すると、出力は次のようになりました。
-- Retry #1, waiting 1 seconds before next attempt...
-- Retry #2, waiting 1 seconds before next attempt...
Had to git clone more than once: 3 times.
CMake Error at b/CMakeFiles/fc-tmp/nope/nope-gitclone.cmake:55 (message):
Failed to clone repository:
この2つはCMakeLists.txtに書く変数ではありません。ドキュメントは「This variable should not be set by a project, it is intended for the user to set.」と明記しており、ホスティング側のレート制限に当たりやすいCIでだけcmake -DCMAKE_EP_GIT_CLONE_RETRY_COUNT=4 -DCMAKE_EP_GIT_CLONE_RETRY_DELAY=10のようにコマンドラインから渡します。待ち時間を0のまま回数だけ増やしても、レート制限には同じ拒否が返るだけです。
URLでアーカイブを取得する書き方とURL_HASHの検証
依存がリリースごとにソースのアーカイブを配布しているなら、Gitを経由せずURLで取る方が軽く済みます。googletest v1.18.0を3通りで取得し、configureの所要時間と_deps/googletest-srcの大きさを比べました(同じマシンで1回ずつ計測)。
| 取得方法 | configure時間 | ソースの大きさ | うち.git |
|---|---|---|---|
| GIT_TAG(コミットハッシュ) | 7.17秒 | 19MB | 15MB |
| GIT_TAG(タグ)+GIT_SHALLOW | 5.06秒 | 7.3MB | 2.8MB |
| URL+URL_HASH(リリースのtar.gz) | 3.97秒 | 4.5MB | なし |
時間は回線に左右されますが、大きさの差は履歴の有無で決まります。ハッシュで固定した完全なクローンは全履歴を持つため、ソース本体の3倍以上を.gitが占めました。宣言は次の形で、googletestのREADMEもURL指定の例を載せています。
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/releases/download/v1.18.0/googletest-1.18.0.tar.gz
URL_HASH SHA256=6e3191c1455468b3fc35a417fb565c1c5071aee1b7e7f85e30cf48a98d37d8b5
)
URL_HASHは省略できますが、付けないと配布物が差し替えられても気づけません。値はダウンロードしたファイルをshasum -a 256(Linuxはsha256sum)にかけて得ます。GitHubのリリースに添付されたファイルなら、gh api repos/google/googletest/releases/tags/v1.18.0が返すdigest欄にも同じ値が載っていました。
GitHubがタグやコミットから自動生成するarchive/…zipを使う場合は、URLの書き方で値が変わる点に注意します。同じv1.18.0のコミットでも、タグ名のURLとコミットハッシュのURLで取ったzipはSHA256が一致しませんでした。展開したときの最上位ディレクトリ名がURLごとに違うためです。リリースに添付ファイルがあるならそちらを使い、無ければ一度ダウンロードして値を確かめてから書きます。
DOWNLOAD_EXTRACT_TIMESTAMPの警告(CMP0135)
cmake_minimum_required()が3.24未満のプロジェクトでURLを使うと、configure時に次の作者向け警告が出ます。
CMake Warning (author) at .../Modules/FetchContent.cmake:1386 (message):
The DOWNLOAD_EXTRACT_TIMESTAMP option was not given and policy CMP0135 is
not set. The policy's OLD behavior will be used.
原因はポリシーCMP0135です。OLDでは展開したファイルのタイムスタンプがアーカイブ内の日時のまま残るため、URLを新しい版に変えても依存側のファイルが「古い」と判定され、再ビルドされないことがあります。最小要求バージョンを3.24以上にすればNEW(展開した時刻を付ける)になり警告は消えます。
最小要求バージョンを上げられない場合は宣言にDOWNLOAD_EXTRACT_TIMESTAMP FALSEを足しますが、ここに罠があります。警告文の続きは「specify the DOWNLOAD_EXTRACT_TIMESTAMP option with a value of true」と読めるのに、ExternalProjectのドキュメントはTRUEを「アーカイブ内の日時を保つ」、FALSEを「展開した時刻を付ける」と定め、特別な理由が無ければFALSEを使うよう勧めています。CMake 4.4.4で同じアーカイブを両方の値で展開すると、TRUEではCMakeLists.txtの更新日時がアーカイブ内の2026年8月11日のまま、FALSEでは展開した当日の日時になりました。警告はどちらの値でも消えるので、警告文の言い回しに従ってTRUEを書くと、警告が消えたまま再ビルド漏れの原因だけが残ります。なおこのオプション自体が3.24で追加されたものなので、実行するCMakeが3.23以前の環境まで支えるなら、宣言をif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)で分けます。
ハッシュ不一致の原因を確認する詳細ログ
URL_HASHの値を誤ると、CMake 4.4.4の既定の出力は次のメッセージだけで、ハッシュの話だと分かりません。しかも止まるまでに同じURLを6回ダウンロードしていました。
CMake Error at b/CMakeFiles/fc-stamp/googletest/download-googletest.cmake:163 (message):
Each download failed!
理由は詳細ログにだけ出ます。cmake -S . -B b --log-level=VERBOSEで構成し直すと、期待値と実際の値が並びます。
-- SHA256 hash of
.../b/_deps/googletest-tmp/googletest-1.18.0.tar.gz
does not match expected value
expected: '0000000000000000000000000000000000000000000000000000000000000000'
actual: '6e3191c1455468b3fc35a417fb565c1c5071aee1b7e7f85e30cf48a98d37d8b5'
-- Hash mismatch, removing...
同じ宣言をcmake_minimum_required(VERSION 3.14)の下で構成すると、サブビルドを使う旧実装(CMP0168がOLD)になり、この不一致の行は既定の出力にも表示されました。表示が消えるのは3.30以上を宣言したプロジェクトです。そのままではネットワーク障害と見分けがつかないので、URLで取る依存が落ちたらまず--log-level=VERBOSEを付けて構成し直します。
FetchContent_MakeAvailableが内部で行う処理の順序
FetchContent_MakeAvailable()は「取得してadd_subdirectory()する」だけのコマンドではありません。依存ごとに次の順序で判定が入ります。
まずFETCHCONTENT_SOURCE_DIR_<大文字名>が設定されていれば、以降の取得判定はすべて飛ばしてそのディレクトリを使います。設定されていない場合は、依存プロバイダが登録されていればそちらへ処理を委譲し、次に許可されていればfind_package()を試します。ここまでで解決しなければ宣言された詳細に従って取得し、最後にソースの最上位にCMakeLists.txtがあればadd_subdirectory()で本体のビルドへ加えます。
最上位にCMakeLists.txtが無くてもエラーにはならず、単にサブディレクトリの追加が省略されます。これはヘッダやアセットを既知の場所に配置するだけの依存を扱うための挙動です。逆に、リポジトリの最上位ではなく配下のディレクトリを取り込みたい場合はSOURCE_SUBDIRに相対パスを指定します。存在しないディレクトリを指してもよく、これは最上位のCMakeLists.txtを意図的に取り込ませない目的にも使えます。
取得済みかどうかを自分で調べたい場合はFetchContent_GetProperties()で<小文字名>_POPULATED・<小文字名>_SOURCE_DIR・<小文字名>_BINARY_DIRを取り出せますが、FetchContent_MakeAvailable()を使っている限り必要になる場面はほとんどありません。
依存のビルドオプションを確実に切るCACHE INTERNAL指定
依存ライブラリのテストやサンプルまで毎回ビルドされるのを止めたいときは、FetchContent_MakeAvailable()より前に変数を設定します。このときCACHE INTERNALを付けるかどうかで結果が変わります。公式ドキュメントのprotobufの例もset(protobuf_BUILD_TESTS OFF CACHE INTERNAL "")と書いており、ポリシーCMP0077との衝突を避けるためと理由が添えられています。
set(mydep_BUILD_TESTS OFF CACHE INTERNAL "") # 効く
# set(mydep_BUILD_TESTS OFF) # 依存側の option() に戻されることがある
FetchContent_MakeAvailable(mydep)
CMP0077は3.13で追加されたポリシーで、NEWではoption()が同名の通常変数を尊重して何もしません。ところがこのポリシーの値を決めるのは、親ではなく依存側のcmake_minimum_required()です。CMake 4.4.4で、cmake_minimum_required(VERSION 3.10)とoption(MYDEP_BUILD_TESTS "build tests" ON)を持つ依存に対し親からset(MYDEP_BUILD_TESTS OFF)を渡したところ、依存側が表示した値はONに戻っていました。同じ依存にCACHE INTERNAL付きで渡すとOFFのまま通ります。依存が古いCMakeを要求している限り、通常のset()は当てになりません。オプション名は依存ごとに違うので、対象リポジトリのCMakeLists.txtをoption(で検索して確かめます。
FetchContent_Populateの単一引数形式はCMake 3.30で非推奨
ネット上のサンプルで今も多く見かけるのが、次の書き方です。FetchContent_MakeAvailable()が存在しなかった3.13以前に使われていたパターンで、CMake 3.30でこの形のFetchContent_Populate()は非推奨になりました(ポリシーCMP0169)。
# 非推奨。いま書くべきではない
FetchContent_GetProperties(mydep)
if(NOT mydep_POPULATED)
FetchContent_Populate(mydep)
add_subdirectory(${mydep_SOURCE_DIR} ${mydep_BINARY_DIR})
endif()
厄介なのは、警告で済むかエラーで止まるかがcmake_minimum_required()の値で変わる点です。CMake 4.4.4で、最小要求バージョンだけを変えた同一のコードを構成すると、cmake_minimum_required(VERSION 3.14)では作者向け警告が出るだけでconfigureは成功し、cmake_minimum_required(VERSION 3.30)では次のエラーで停止しました。
CMake Error at .../Modules/FetchContent.cmake:1966 (message):
Calling FetchContent_Populate(mydep) is deprecated, call
FetchContent_MakeAvailable(mydep) instead. Policy CMP0169 can be set to
OLD to allow FetchContent_Populate(mydep) to be called directly for now,
but the ability to call it with declared details will be removed completely
in a future version.
つまり最小要求バージョンを3.30以上へ引き上げた瞬間にビルドが壊れます。置き換えはFetchContent_MakeAvailable(mydep)の1行で済むので、警告の段階で直しておきます。この旧パターンが廃止された理由は、SYSTEMやEXCLUDE_FROM_ALLのように宣言側で指定したオプションを、プロジェクト側で書くadd_subdirectory()へ伝える手段が無いためです。依存プロバイダの仕組みにも対応できません。
なおFetchContent_Populate()自体が消えるわけではなく、名前に続けて取得方法まで書き下す形は残ります。ただしこちらはCMakeスクリプトモードで独自の仕組みを実装するための用途で、通常のプロジェクトで使うものではありません。
もう1点、3.30では実装方式も変わりました。ポリシーCMP0168がNEWのとき、FetchContentは別プロジェクトのサブビルドを経由せず、メインプロジェクトのconfigureから直接ダウンロードと更新を行います。手元で確認すると、cmake_minimum_required(VERSION 3.14)のときは_deps配下に<name>-subbuildが作られ、3.30では作られませんでした。この違いに伴い、NEW側ではQUIETオプションとFETCHCONTENT_QUIET変数、LOG_*系オプション、PREFIX・TMP_DIR・STAMP_DIR・LOG_DIR・DOWNLOAD_DIRが無視されます。出力の抑制はCMAKE_MESSAGE_LOG_LEVELやコマンドラインの--log-levelで制御します。
find_packageを先に試して取得へフォールバックする構成
CIでは毎回ソースから取得したいが、開発者のマシンにライブラリが入っていればそれを使いたい、という要求には3.24で入った2つのオプションが対応します。
FIND_PACKAGE_ARGSを付けると、FetchContent_MakeAvailable()はまずfind_package()を試し、見つからなければ宣言どおりに取得します。キーワードの後ろに書いた内容がそのままfind_package()の引数に足されるため、パッケージ名がリポジトリ名と違う場合に対応できます。他のオプションはすべてFIND_PACKAGE_ARGSより前に書く必要があります。
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG 063de7e9578f82b369302001269680b4b1553359 # v1.18.0
FIND_PACKAGE_ARGS NAMES GTest
)
FetchContent_MakeAvailable(googletest)
ここでREQUIREDを渡さないようにします。find_package()が必ず成功しなければならなくなり、見つからなかったときの取得へフォールバックできなくなるためです。プロジェクト側で書かずに利用者側で切り替えたい場合は、変数FETCHCONTENT_TRY_FIND_PACKAGE_MODEを使います。既定はOPT_INで、FIND_PACKAGE_ARGSを書いた依存についてだけfind_package()を試します。ALWAYSにすると全依存で試し、NEVERにするとFIND_PACKAGE_ARGSがあっても試しません。この変数はFetchContent_Declare()を呼んだ時点の値が使われる点が独特で、FetchContent_MakeAvailable()の直前で書き換えても効きません。
逆方向がOVERRIDE_FIND_PACKAGEです。これを付けて宣言すると、以降のfind_package(<name>)がFetchContentでの取得に迂回されます。プロジェクト内の既存コードがfind_package()で依存を探している場合に、その呼び出しを書き換えずにソースからのビルドへ寄せられます。FIND_PACKAGE_ARGSとは同時に指定できません。
GoogleTestを取り込んでctestでテストを動かす例
テストフレームワークの取り込みは、FetchContentの利用例の一つです。テスト対象は次のtest.cppで、テストを2件持ちます。
#include <gtest/gtest.h>
int add(int a, int b) { return a + b; }
TEST(Add, Basic) { EXPECT_EQ(add(2, 3), 5); }
TEST(Add, Negative) { EXPECT_EQ(add(-2, -3), -5); }
同じディレクトリに置き、取得からテスト実行まで通したCMakeLists.txtが次のものです。
cmake_minimum_required(VERSION 3.30)
project(t CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/releases/download/v1.18.0/googletest-1.18.0.tar.gz
URL_HASH SHA256=6e3191c1455468b3fc35a417fb565c1c5071aee1b7e7f85e30cf48a98d37d8b5
)
set(BUILD_GMOCK OFF CACHE INTERNAL "")
set(INSTALL_GTEST OFF CACHE INTERNAL "")
# Windows(MSVC)でランタイムの不一致を避ける。googletestのREADMEの指定
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(t test.cpp)
target_link_libraries(t PRIVATE GTest::gtest_main)
include(GoogleTest)
gtest_discover_tests(t)
cmake -S . -B b、cmake --build bのあとbでctestを実行すると、gtest_discover_tests()がテストケースを1件ずつ登録しているため「100% tests passed out of 2」と表示されました。
各行には理由があります。googletestのREADMEはビルドにC++17対応の環境を求めているため、CMAKE_CXX_STANDARDを17にします。BUILD_GMOCKは既定でONなので、gMockを使わないなら切ります。ビルド時間は17.75秒から12.72秒に縮みました。gtest_force_shared_crtは、Visual Studioの新規プロジェクトがCランタイムを動的にリンクするのに対しgoogletestは静的にリンクするため起きるLNK2038エラーを避ける指定です。macOSやLinuxでは影響しません。
古いサンプルのgoogletest指定がCMake 4系で止まる理由
見落としやすいのは、CMake公式のFetchContentドキュメント自体が、2026年10月時点の最新版(latest)でもgoogletestをGIT_TAG 703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0で宣言する例を載せている点です。このコミットは2019年のrelease-1.10.0で、最上位のCMakeLists.txtがcmake_minimum_required(VERSION 2.8.8)を宣言しています。CMake 4.4.4でそのまま構成すると、後半の「CMake 4系で依存側のconfigureが止まる場合の切り分け」で扱う互換性削除に当たって停止しました。
HEAD is now at 703bd9ca Googletest export
CMake Error at b/_deps/googletest-src/CMakeLists.txt:4 (cmake_minimum_required):
Compatibility with CMake < 3.5 has been removed from CMake.
googletestはv1.18.0(2026年8月10日公開)でcmake_minimum_required(VERSION 3.16)まで上がっているので、写してきたハッシュはリリースタグの値に置き換えます。
通信を止める3つの変数の使い分けとローカルソースへの差し替え
オフライン環境やCIのキャッシュ運用では、取得そのものを抑えたい場面があります。制御用の変数は用途が微妙に違うので、使い分けを整理します。
| 変数 | 既定 | 抑制する範囲 |
|---|---|---|
| FETCHCONTENT_SOURCE_DIR_<大文字名> | 未設定 | その依存の取得と更新をすべて省略 |
| FETCHCONTENT_UPDATES_DISCONNECTED | OFF | git・hgの更新ステップの通信のみ |
| FETCHCONTENT_FULLY_DISCONNECTED | OFF | ダウンロードと更新の全体 |
開発中の依存を手元で書き換えながら試したいときはFETCHCONTENT_SOURCE_DIR_<大文字名>が使えます。名前部分は大文字にする点に注意してください。手元で、GIT_REPOSITORYに到達できないURLを書いた状態でcmake -S . -B b -DFETCHCONTENT_SOURCE_DIR_MYDEP=/tmp/fctest/depを実行したところ、通信を一切試みずに構成が成功し、mydep_SOURCE_DIRは指定したディレクトリを指しました。取得先の既定位置にあるソースを直接編集すると、宣言内容が変わったタイミングで消えることがあるため、依存に手を入れるならこの変数を使うのが正解です。
FETCHCONTENT_UPDATES_DISCONNECTEDはより穏やかな制御で、gitとhgの更新ステップだけリモートへ接続しなくなります。ダウンロードステップは対象外なので、未取得の依存は通常どおり取得されます。ローカルに既にあるタグやコミットへの切り替えは成功し、手元に無いハッシュへの切り替えは失敗します。依存ごとに切り分けたい場合はFETCHCONTENT_UPDATES_DISCONNECTED_<大文字名>を使い、めったに変わらない依存だけ更新を止める運用ができます。
FETCHCONTENT_FULLY_DISCONNECTEDはダウンロードも更新も行いません。これは初回構成でネットワークを遮断するための変数ではなく、一度取得が済んだ後にconfigureを速くするためのものです。CMake 3.30で追加されたポリシーCMP0170がNEW(3.30以上を宣言したプロジェクト)の場合、この変数がTRUEなのにソースディレクトリが存在しなければ致命的エラーで停止します。CMP0170が未設定の場合は警告を出してOLDの動作を使います。以前は取得が黙って飛ばされて原因の追いにくいエラーになっていたため、挙動としては改善です。初回からネットワークを遮断したい場合は、依存プロバイダを設定してローカルの内容から解決させます。
宣言の段階でローカルディレクトリを依存にする書き方
社内の別リポジトリを決まった場所にチェックアウトしておき、それを常に使いたいなら、利用者ごとの上書きではなく宣言そのものにローカルのパスを書けます。取得方法を書かずにSOURCE_DIRだけを渡す形です。
FetchContent_Declare(mydep SOURCE_DIR ${CMAKE_SOURCE_DIR}/../mydep)
FetchContent_MakeAvailable(mydep)
CMake 4.4.4で試すと、通信は行われず指定したディレクトリがそのままadd_subdirectory()され、_depsにはmydep-buildとmydep-tmpだけが作られました。ExternalProjectのドキュメントも、取得方法が無い場合のSOURCE_DIRは展開済みの既存ディレクトリを指すものと定めています。
逆に、GIT_REPOSITORYやURLとSOURCE_DIRを一緒に書くのは危険です。同じドキュメントは、URL方式以外では「All other download methods silently discard any previous contents of the source directory.」としており、手元で編集中のディレクトリをSOURCE_DIRに指定すると、取得のたびに中身が警告なしで消されます。編集中のソースを差し込むなら、前述のFETCHCONTENT_SOURCE_DIR_<大文字名>を使います。
CMakeを組み込み開発のツールチェーンと組み合わせて使う場合の環境構築は、STM32CubeIDE for VS Code拡張でのビルドとデバッグにまとめています。
CMake 4系で依存側のconfigureが止まる場合の切り分け
2025年3月27日に公開されたCMake 4.0で、3.5より前との互換性が削除されました。CMake 4.0のリリースノートの記述は「Compatibility with versions of CMake older than 3.5 has been removed. Calls to cmake_minimum_required() or cmake_policy() that set the policy version to an older value now issue an error.」です。ここで効いてくるのが、自分のプロジェクトを新しく書いていても、FetchContentが取ってくる依存のCMakeLists.txtが古ければそちらで止まるという点です。FetchContentは依存のソースをそのままadd_subdirectory()するため、依存側のcmake_minimum_required()がそのまま自分のconfigureに影響します。
手元でcmake_minimum_required(VERSION 3.0)を宣言した依存をFetchContentで取り込み、CMake 4.4.4で構成すると次のエラーで停止しました。指されているのが_deps/mydep-src/CMakeLists.txt、つまり自分が書いていないファイルなので、原因の見当がつきにくいのが厄介なところです。
CMake Error at b/_deps/mydep-src/CMakeLists.txt:1 (cmake_minimum_required):
Compatibility with CMake < 3.5 has been removed from CMake.
Update the VERSION argument <min> value. Or, use the <min>...<max> syntax
to tell CMake that the project requires at least <min> but has been updated
to work with policies introduced by <max> or earlier.
Or, add -DCMAKE_POLICY_VERSION_MINIMUM=3.5 to try configuring anyway.
逃げ道はエラーメッセージが示すとおりCMAKE_POLICY_VERSION_MINIMUMです。CMake 4.0で追加された変数で、対象プロジェクトのコードに触れずに最低ポリシーバージョンを外から与えます。-DCMAKE_POLICY_VERSION_MINIMUM=3.5でキャッシュに入れる方法、同名の環境変数で新しいビルドツリーに自動で効かせる方法、そしてプロジェクト側でFetchContent_MakeAvailable()の直前に通常変数として設定する方法があり、CMake 4.4.4ではいずれもconfigureが通りました。依存1つに絞って当てるなら3つ目です。前章のgoogletest release-1.10.0にこの指定を当てると、cmake_minimum_requiredは非推奨の警告に変わって通過しました。ただし1.10.0にはGTest::gtest_mainという名前のターゲットが無く(別名ターゲットは1.11.0で追加)、今度はリンク先が見つからずに止まります。古い依存を延命させると別の箇所で詰まるので、先に版を上げるのが筋です。
set(CMAKE_POLICY_VERSION_MINIMUM 3.5) # この依存のためだけに効かせる
FetchContent_MakeAvailable(mydep)
unset(CMAKE_POLICY_VERSION_MINIMUM)
ただしこれを恒久策にすべきではありません。CMAKE_POLICY_VERSION_MINIMUMの公式ドキュメントはこの変数を「まだ更新されていないプロジェクトを外から構成してみるため」のものと位置づけ、プロジェクトが自分のポリシーバージョンを設定する目的で書くことは明確に否定しています。それにはcmake_minimum_required(VERSION)を使えという指示です。実務での順番は、依存のGIT_TAGをCMake 4系に対応した版へ上げるのが先、上げられない場合だけ上のように依存ごとに限定して当て、あわせて上流へ報告します。環境変数でCI全体に効かせる運用は、別の依存が抱える同種の問題まで見えなくするため避けます。
よくある質問
FetchContentとExternalProjectはどちらを使えばよいですか?
依存ライブラリのCMakeターゲットにtarget_link_libraries()でリンクしたいならFetchContentです。ExternalProjectはビルド時にダウンロードするため、configure時点では依存のターゲットが存在せず参照できません。依存を独立して構成・ビルド・インストールしたい場合や、CMakeプロジェクトではないものをビルドさせたい場合はExternalProjectが向きます。
FetchContent_MakeAvailableとadd_subdirectoryは何が違いますか?
FetchContent_MakeAvailable()は取得を済ませたうえで、内部的にadd_subdirectory()を呼びます。add_subdirectory()単体はソースが既に手元にある前提で、取得は行いません。またソースの最上位にCMakeLists.txtが無い場合、FetchContent_MakeAvailable()はサブディレクトリの追加を省略するだけでエラーにはなりませんが、add_subdirectory()を直接呼ぶとエラーになります。
FetchContent_Populateはもう使えないのですか?
宣言済みの名前だけを渡すFetchContent_Populate(<name>)の形が非推奨です。CMake 3.30以降をcmake_minimum_required()で宣言しているとエラーで停止し、それより前のバージョンを宣言している場合は警告が出ます。名前に続けてGIT_REPOSITORYなどの取得方法まで書き下す形は残っていますが、CMakeスクリプトモードでの利用が想定された用途です。
取得したソースはどこに置かれますか?
既定では${CMAKE_BINARY_DIR}/_depsの下に<小文字名>-srcと<小文字名>-buildが作られます。CMake 3.29以前の実装、またはポリシーCMP0168がOLDのときは<小文字名>-subbuildも作られます。配置先はFETCHCONTENT_BASE_DIRで変更できますが、Windowsのパス長制限に当たりやすいため短いパスにします。
GIT_TAGを省略するとどうなりますか?
既定値はmasterです。リポジトリの既定ブランチ名が使われるわけではないため、既定ブランチがmainで、masterという参照が存在しないリポジトリでは取得に失敗します。再現性の面からも、リリースタグが指すコミットハッシュを明示するのが安全です。