CMake ビルドを効率化!target_sources() を活用したソース管理テクニック
target_sources()
コマンドは、CMakeのビルドシステムにおいて、特定のターゲット(実行可能ファイル、ライブラリなど)にソースファイルを関連付けるために使用されます。簡単に言えば、「この実行可能ファイルやライブラリをビルドするには、これらのソースファイルが必要だよ」とCMakeに伝える役割を果たします。
基本的な構文
target_sources(<ターゲット名>
<PRIVATE|PUBLIC|INTERFACE>
<ソースファイル1>
<ソースファイル2>
...
)
各要素の説明
-
<ソースファイル1>
,<ソースファイル2>
, ...: ターゲットに関連付けたいソースファイルのパスをリスト形式で指定します。相対パスまたは絶対パスを使用できます。CMakeLists.txt ファイルが存在するディレクトリからの相対パスが一般的です。 -
<PRIVATE|PUBLIC|INTERFACE>
: これは、ソースファイルの可視性と依存関係を制御するためのキーワードです。PRIVATE
: 指定されたソースファイルは、このターゲットの内部実装として扱われます。他のターゲットはこのソースファイルに直接アクセスしたり、このソースファイルから間接的な影響を受けたりすることはありません。通常、実行可能ファイルの.c
,.cpp
ファイルなどがこれに該当します。PUBLIC
: 指定されたソースファイルは、このターゲットのパブリックインターフェースの一部として扱われます。このターゲットにリンクする他のターゲットは、このターゲットのヘッダーファイル(通常は.h
や.hpp
ファイル)の定義を利用できます。また、このターゲットのコンパイラフラグや定義なども、リンク先のターゲットに伝播する可能性があります。共有ライブラリのヘッダーファイルなどがこれに該当することが多いです。INTERFACE
: 指定されたソースファイルは、ターゲット自体のビルドには直接使用されませんが、このターゲットにリンクする他のターゲットに対してインターフェース情報(例えば、インクルードディレクトリ、コンパイラフラグ、定義など)を提供するために使用されます。ヘッダーオンリーライブラリや、他のターゲットに影響を与える設定のみを持つターゲットなどで使用されます。ソースファイル自体は通常指定されません(または、プレースホルダー的なファイルが指定されることがあります)。
-
<ターゲット名>
: ソースファイルを関連付けたいターゲットの名前を指定します。これはadd_executable()
やadd_library()
コマンドで定義したターゲットの名前です。
target_sources() の役割と利点
- インターフェースの提供:
PUBLIC
やINTERFACE
を使用することで、ライブラリなどのインターフェースを他のターゲットに適切に公開できます。 - 可視性の制御:
PRIVATE
,PUBLIC
,INTERFACE
キーワードを使用することで、ターゲット間の依存関係を明確に定義し、意図しない依存関係の発生を防ぐことができます。これにより、よりモジュール化された、保守しやすいプロジェクトを構築できます。 - 依存関係の追跡: CMakeは
target_sources()
で指定されたソースファイルの変更を監視し、必要に応じてターゲットを再ビルドします。 - ソースファイルの管理: プロジェクトで使用するすべてのソースファイルをCMakeに明示的に伝えることで、ビルドプロセスを正しく管理できます。
使用例
# 実行可能ファイル 'my_app' を定義
add_executable(my_app main.cpp utils.cpp)
# ライブラリ 'my_lib' を定義
add_library(my_lib STATIC my_lib.cpp my_lib.h)
# 'my_app' ターゲットにソースファイルを追加 (PRIVATE)
target_sources(my_app
PRIVATE
main.cpp
utils.cpp
)
# 'my_lib' ターゲットにソースファイルを追加 (PUBLIC ヘッダーファイルを含む)
target_sources(my_lib
PRIVATE
my_lib.cpp
PUBLIC
my_lib.h
)
# 'another_app' が 'my_lib' にリンクする場合、'my_lib.h' の定義を利用できる
add_executable(another_app another_main.cpp)
target_link_libraries(another_app my_lib)
この例では、my_app
は main.cpp
と utils.cpp
を内部実装として使用し、my_lib
は my_lib.cpp
を内部実装とし、my_lib.h
をパブリックインターフェースとして公開しています。another_app
が my_lib
にリンクすると、my_lib.h
の内容を利用できます。
ファイルが見つからない (File not found)
- トラブルシューティング
- 指定したファイルパスを再度確認し、スペルミスや大文字・小文字の間違いがないか確認する。
- ファイルが実際にその場所に存在するか確認する。
- 相対パスを使用している場合は、CMakeLists.txt ファイルからの相対的な位置関係が正しいか確認する。必要であれば、絶対パスを使用してみる。
- CMake の設定 (configure) を実行するディレクトリが意図した場所であるか確認する。
- 原因
target_sources()
で指定したソースファイルのパスが間違っている。- ファイルが実際に指定された場所に存在しない。
- 相対パスを使用している場合に、CMakeLists.txt ファイルからの相対位置が間違っている。
- エラー内容
CMake の構成時に、「指定されたファイルが見つかりません」というエラーメッセージが表示される。
同じソースファイルが複数のターゲットに追加されている
- 原因
同じ.c
や.cpp
ファイルを複数の実行可能ファイルやライブラリにPRIVATE
として追加している。 - エラー内容
ビルド時に、シンボルの重複などのリンカエラーが発生する可能性がある。CMake 自体は警告を出す場合と出さない場合がある。
ヘッダーファイルが PRIVATE として追加されている
- トラブルシューティング
- ヘッダーファイルは通常、
target_sources()
ではなく、必要に応じてinstall(FILES ... DESTINATION include)
などでインストール先を指定し、他のターゲットからはtarget_include_directories()
などでインクルードパスを追加します。 - もしヘッダーファイルがターゲットのビルドに必要な内部的なもので、外部に公開する必要がない場合は
PRIVATE
でも構いませんが、通常は公開されるべきものです。
- ヘッダーファイルは通常、
- 原因
ヘッダーファイル (.h
,.hpp
) をPRIVATE
としてtarget_sources()
に追加している。PRIVATE
は内部実装のソースファイルに対して使用するもので、ヘッダーファイルは通常、公開されるインターフェースの一部です。 - エラー内容
他のターゲットがこのターゲットのヘッダーファイルの内容を利用しようとしても、コンパイラがヘッダーファイルを見つけられないというエラーが発生する。
PUBLIC または INTERFACE の意図しない伝播
- トラブルシューティング
- 本当にそのヘッダーファイルが依存先のターゲットのインターフェースの一部であるべきか再検討する。内部実装の詳細を含むヘッダーファイルは
PRIVATE
に留めるべきです。 INTERFACE_INCLUDE_DIRECTORIES
などのより細かな制御を行うコマンドの使用を検討する。
- 本当にそのヘッダーファイルが依存先のターゲットのインターフェースの一部であるべきか再検討する。内部実装の詳細を含むヘッダーファイルは
- 原因
PUBLIC
やINTERFACE
は、依存するターゲットに影響を与えるため、どのファイルをこれらのキーワードで指定するかを慎重に検討する必要があります。 - エラー内容
あるライブラリのPUBLIC
またはINTERFACE
として追加したソースファイル(通常はヘッダーファイル)の変更が、意図しない依存関係を持つ他のターゲットの再ビルドを引き起こす。
ソースファイルの追加忘れ
- トラブルシューティング
- エラーメッセージからどのシンボルが未定義か特定し、そのシンボルが定義されているはずのソースファイルが
target_sources()
に含まれているか確認する。
- エラーメッセージからどのシンボルが未定義か特定し、そのシンボルが定義されているはずのソースファイルが
- 原因
ターゲットに必要なソースファイルがtarget_sources()
で指定されていない。 - エラー内容
ビルド時に、「未定義の参照」などのリンカエラーが発生する。
ビルドシステムがソースファイルの変更を認識しない
- トラブルシューティング
- 一度 CMake のキャッシュを削除して (ビルドディレクトリ内の
CMakeCache.txt
を削除するなど)、再度 CMake の構成を実行する。 target_sources()
でソースファイルが正しく指定されているか確認する。
- 一度 CMake のキャッシュを削除して (ビルドディレクトリ内の
- 原因
- CMake のキャッシュが古い情報を持っている可能性がある。
- ソースファイルが
target_sources()
に正しく登録されていない。
- エラー内容
ソースファイルを変更しても、CMake が再ビルドを行わない。
- ビルドディレクトリをクリーンアップする
古いビルド情報が残っている場合に、ビルドディレクトリを削除して再構築することで問題が解決することがあります。 - CMake のドキュメントを参照する
target_sources()
コマンドの詳細や関連するコマンドについて、CMake の公式ドキュメントを確認する。 - 簡単な例で試す
問題が複雑な場合に、最小限のコードで問題を再現できるかどうか試してみることで、原因を特定しやすくなります。 - CMake の出力メッセージを注意深く読む
エラーメッセージや警告メッセージには、問題の原因に関する重要な情報が含まれています。
例1: 単純な実行可能ファイル
# CMakeの最小バージョンを指定
cmake_minimum_required(VERSION 3.10)
# プロジェクト名を定義
project(SimpleApp)
# 実行可能ファイル 'my_app' を定義し、ソースファイルを関連付け
add_executable(my_app main.cpp utils.cpp)
# 'my_app' ターゲットにソースファイルを追加 (PRIVATE)
target_sources(my_app
PRIVATE
main.cpp
utils.cpp
)
説明
target_sources(my_app PRIVATE main.cpp utils.cpp)
: 同じターゲットmy_app
に対して、target_sources()
を使用して再度ソースファイルを指定しています。ここではPRIVATE
キーワードを使用しています。これは、main.cpp
とutils.cpp
がmy_app
の内部実装であり、他のターゲットからは直接的に利用されないことを意味します。add_executable(my_app main.cpp utils.cpp)
:my_app
という名前の実行可能ファイルを定義し、初期のソースファイルとしてmain.cpp
とutils.cpp
を指定しています。
例2: 静的ライブラリと実行可能ファイル
cmake_minimum_required(VERSION 3.10)
project(LibraryApp)
# 静的ライブラリ 'mylib' を定義
add_library(mylib STATIC mylib.cpp mylib.h)
# 'mylib' ターゲットにソースファイルを追加
target_sources(mylib
PRIVATE
mylib.cpp
PUBLIC
mylib.h
)
# 実行可能ファイル 'main_app' を定義
add_executable(main_app main.cpp)
# 'main_app' ターゲットにソースファイルを追加
target_sources(main_app
PRIVATE
main.cpp
)
# 'main_app' を 'mylib' にリンク
target_link_libraries(main_app mylib)
# 'mylib' のヘッダーファイルのインクルードディレクトリを追加
target_include_directories(main_app
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR} # mylib.h が存在するディレクトリ
)
説明
target_include_directories(main_app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
:main_app
がmylib.h
をインクルードできるように、ヘッダーファイルが存在するディレクトリをインクルードパスに追加しています。ここではPRIVATE
を使用しているので、このインクルードパスはmain_app
のビルドにのみ影響し、main_app
にリンクする他のターゲットには伝播しません。target_link_libraries(main_app mylib)
:main_app
がmylib
にリンクすることを指定しています。add_executable(main_app main.cpp)
とtarget_sources(main_app PRIVATE main.cpp)
: 実行可能ファイルmain_app
とその内部実装のソースファイルを定義しています。target_sources(mylib PRIVATE mylib.cpp PUBLIC mylib.h)
:mylib.cpp
はライブラリの内部実装なのでPRIVATE
、mylib.h
はライブラリの公開インターフェースなのでPUBLIC
として関連付けています。これにより、mylib
にリンクする他のターゲットはmylib.h
の内容を利用できます。add_library(mylib STATIC mylib.cpp mylib.h)
:mylib
という名前の静的ライブラリを定義し、初期のソースファイルとしてmylib.cpp
とmylib.h
を指定しています。
例3: ヘッダーオンリーライブラリ (INTERFACE)
cmake_minimum_required(VERSION 3.10)
project(HeaderOnlyLib)
# ヘッダーオンリーライブラリ 'header_lib' を定義 (ソースファイルは不要)
add_library(header_lib INTERFACE)
# インターフェースのインクルードディレクトリを設定
target_include_directories(header_lib
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/include
)
# 実行可能ファイル 'consumer_app' を定義
add_executable(consumer_app main.cpp)
# 'consumer_app' を 'header_lib' にリンク
target_link_libraries(consumer_app header_lib)
# 'consumer_app' のソースファイルを追加
target_sources(consumer_app
PRIVATE
main.cpp
)
# 'consumer_app' がヘッダーファイルを使用するためにインクルードディレクトリは不要
# なぜなら 'header_lib' の INTERFACE_INCLUDE_DIRECTORIES が伝播するから
target_link_libraries(consumer_app header_lib)
:consumer_app
をheader_lib
にリンクしています。これにより、header_lib
で設定されたインターフェース情報(この場合はインクルードディレクトリ)がconsumer_app
に伝播します。add_executable(consumer_app main.cpp)
とtarget_sources(consumer_app PRIVATE main.cpp)
: 実行可能ファイルとそのソースファイルを定義しています。target_include_directories(header_lib INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
:header_lib
のインターフェースとしてインクルードディレクトリを設定しています。INTERFACE
キーワードにより、header_lib
にリンクするすべてのターゲットは、このインクルードディレクトリを自動的に利用できるようになります。add_library(header_lib INTERFACE)
:header_lib
という名前のインターフェースライブラリを定義しています。INTERFACE
ライブラリは、自身は具体的なコードを持たず、主にコンパイラフラグやインクルードディレクトリなどの情報を他のターゲットに提供するために使用されます。
add_executable() および add_library() コマンドでの直接指定
最も基本的な代替方法は、add_executable()
や add_library()
コマンドの引数として直接ソースファイルを指定する方法です。
# 実行可能ファイルを定義し、同時にソースファイルを指定
add_executable(my_app main.cpp utils.cpp)
# 静的ライブラリを定義し、同時にソースファイルとヘッダーファイルを指定
add_library(mylib STATIC mylib.cpp mylib.h)
説明
- target_sources() との違い
- 可視性 (
PRIVATE
,PUBLIC
,INTERFACE
) を指定することはできません。デフォルトでは、これらのコマンドで指定されたソースファイルはターゲットのプライベートなものとして扱われます。 - 後からソースファイルを追加したり、可視性を変更したりする場合は、
target_sources()
を使用する必要があります。
- 可視性 (
- この方法は、小規模なプロジェクトや、ターゲットのソースファイルが比較的固定されている場合に簡潔に記述できます。
add_executable()
やadd_library()
コマンドは、ターゲットの作成と同時に、そのターゲットに含めるべきソースファイルを直接指定できます。
変数を利用したソースファイルのリスト管理
ソースファイルのリストをCMake変数に格納し、それを add_executable()
や add_library()
、または target_sources()
に渡す方法です。
# ソースファイルのリストを変数に格納
set(MY_APP_SOURCES
main.cpp
utils.cpp
)
# ヘッダーファイルのリストを変数に格納
set(MY_LIB_HEADERS
mylib.h
)
# ライブラリのソースファイルのリストを変数に格納
set(MY_LIB_SOURCES
mylib.cpp
${MY_LIB_HEADERS} # ヘッダーファイルもリストに含めることは可能ですが、通常は分けて管理します
)
# 変数を使って実行可能ファイルを定義
add_executable(my_app ${MY_APP_SOURCES})
# 変数を使ってライブラリを定義し、target_sources() で可視性を指定
add_library(mylib STATIC)
target_sources(mylib
PRIVATE
${MY_LIB_SOURCES}
PUBLIC
${MY_LIB_HEADERS}
)
説明
${変数名}
の形式で、変数の内容を展開してコマンドの引数として渡すことができます。set()
コマンドを使って、ソースファイルのパスをリスト形式で変数に格納しています。
ファイルセット (File Sets)
CMake 3.13 以降で導入されたファイルセットは、論理的なグループとしてファイルを管理し、ターゲットに関連付けるためのより構造化された方法を提供します。
cmake_minimum_required(VERSION 3.13)
project(FileSetsExample)
# ファイルセットを定義
file_set(HEADERS TYPE HEADERS FILES
include/mylib.h
VISIBILITY PUBLIC
)
file_set(SOURCES TYPE CXX_SOURCES FILES
src/mylib.cpp
src/main.cpp
VISIBILITY PRIVATE # デフォルトの可視性
)
# 静的ライブラリを定義し、ファイルセットを関連付け
add_library(mylib STATIC $<FILE_SET:HEADERS> $<FILE_SET:SOURCES>)
# 実行可能ファイルを定義し、ファイルセットを関連付け
add_executable(my_app $<FILE_SET:SOURCES>)
# 特定のターゲットに対して、ファイルセットの可視性を上書きすることも可能
target_sources(my_app
PRIVATE
$<FILE_SET:SOURCES> # ここでは PRIVATE として明示的に指定
)
# ヘッダーファイルのインクルードディレクトリを設定 (ファイルセットの可視性 PUBLIC を利用)
target_include_directories(mylib
PUBLIC
$<FILE_SET_TARGET:HEADERS> # ファイルセットに関連付けられたターゲットのパス
)
# 実行可能ファイルがライブラリにリンク
target_link_libraries(my_app mylib)
# 実行可能ファイルにインクルードディレクトリを追加 (PUBLIC なヘッダーを利用するため)
target_include_directories(my_app
PRIVATE # または PUBLIC/INTERFACE に応じて
include
)
説明
- 利点
- ファイルを論理的にグループ化できるため、大規模なプロジェクトでの管理が容易になります。
- ファイルの種類ごとに異なる扱いを定義できます(例:ヘッダーファイルは自動的にインクルードパスに追加するなど)。
- 可視性をファイルセットレベルで管理できます。
<FILE_SET_TARGET:ファイルセット名>
を使うと、ファイルセットに関連付けられたターゲットのパスなどを取得できます。<FILE_SET:ファイルセット名>
というジェネレーター式を使って、ターゲットにファイルセットを関連付けます。VISIBILITY
引数で、ファイルセットのデフォルトの可視性を設定できます。TYPE
引数でファイルの種類を指定し、FILES
引数で具体的なファイルをリストアップします。file_set()
コマンドを使用して、HEADERS
とCXX_SOURCES
という名前のファイルセットを定義しています。
ディレクトリ走査と file(GLOB) または file(GLOB_RECURSE)
cmake_minimum_required(VERSION 3.10)
project(GlobExample)
# 特定のディレクトリ内のすべての .cpp ファイルを検索
file(GLOB_RECURSE MY_SOURCES "src/*.cpp")
# 実行可能ファイルを定義し、検索されたソースファイルを関連付け
add_executable(my_app ${MY_SOURCES})
# 特定のディレクトリ内のすべてのヘッダーファイルを検索
file(GLOB MY_HEADERS "include/*.h")
# ライブラリを定義し、ソースファイルとヘッダーファイルを関連付け
add_library(mylib STATIC mylib.cpp ${MY_HEADERS}) # ヘッダーファイルは通常ソースとしてコンパイルされませんが、リストに含めることはできます
target_sources(mylib
PRIVATE
mylib.cpp
PUBLIC
${MY_HEADERS}
)
# ヘッダーファイルのインクルードディレクトリを追加
target_include_directories(mylib
PUBLIC
include
)
target_link_libraries(my_app mylib)
target_include_directories(my_app
PRIVATE
include
)
説明
- 注意点
GLOB
およびGLOB_RECURSE
は、CMake の構成時にファイルリストを生成します。新しいファイルが追加されたり、既存のファイルが削除されたりした場合、CMake の構成を再実行するまでビルドシステムは変更を認識しません。- 大規模なプロジェクトや、頻繁にファイルが追加・削除されるプロジェクトでは、明示的にファイルをリストアップする方法やファイルセットの使用が推奨されます。
GLOB
は、ファイルリストが比較的静的な場合に便利です。
- 検索されたファイルのリストを変数展開
${MY_SOURCES}
などを使って、add_executable()
やtarget_sources()
に渡します。 file(GLOB_RECURSE)
は指定されたパターンに一致するファイルを、指定されたディレクトリ以下のすべてのサブディレクトリから再帰的に検索します。file(GLOB)
は指定されたパターンに一致するファイル(サブディレクトリは除く)を検索し、その結果を変数に格納します。
target_sources()
はソースファイルをターゲットに関連付けるための主要なコマンドですが、
- ファイルセット
大規模プロジェクトでの構造化されたファイル管理と可視性制御に強力。 - 変数を利用したリスト管理
ソースリストの再利用や管理の簡略化に有効。 - add_executable() / add_library() で直接指定
小規模プロジェクトや初期定義に便利。