CMakeのincludeコマンドを超えたモジュール管理:代替方法による高度なテクニック


CMake の include() コマンドは、別の CMake ファイルを読み込み、その内容を実行するための機能を提供します。これは、プロジェクト内のモジュールやライブラリを整理し、コードを再利用する際に役立ちます。

構文

include(path/to/file.cmake [OPTIONAL])
  • OPTIONAL: ファイルが存在しない場合にエラーを出さないオプション
  • path/to/file.cmake: 読み込む CMake ファイルのパス

動作

include() コマンドは、指定された CMake ファイルを読み込み、その内容を実行します。読み込まれたファイル内の変数やマクロは、呼び出し元のファイルで使用できます。

# main.cmake

# my_module.cmake を読み込む
include(my_module.cmake)

# my_module.cmake で定義された変数を使用する
add_executable(my_program main.cpp)
target_link_libraries(my_program my_lib)

モジュールとファイル

CMake は、読み込まれるファイルの種類を区別します。

  • ファイル: 拡張子が .cmake 以外のファイル
  • モジュール: 拡張子が .cmake のファイル

モジュールの場合は、include() コマンドに拡張子を省略できます。

# main.cmake

# my_module.cmake を読み込む
include(my_module)

検索パス

CMake は、読み込むファイルを探すために以下の場所を検索します。

  1. 指定されたファイルのパス
  2. CMAKE_MODULE_PATH 変数で指定されたディレクトリ
  3. CMake の組み込みモジュールディレクトリ

CMAKE_MODULE_PATH 変数は、複数のディレクトリをセミコロン (;) で区切って指定できます。

set(CMAKE_MODULE_PATH "/path/to/modules;/other/path/to/modules")

利点

include() コマンドを使用する利点は次のとおりです。

  • 依存関係の管理: include() コマンドを使用して、プロジェクトの依存関係を管理できます。
  • コードの整理: モジュールを使用して、プロジェクトのコードを整理し、可読性を向上させることができます。
  • コードの再利用: モジュールを別々に作成することで、コードを複数のプロジェクトで再利用できます。

注意点

include() コマンドを使用する際に注意すべき点がいくつかあります。

  • スコープ: 読み込まれたファイル内の変数やマクロは、呼び出し元のファイルのスコープ内で使用できます。
  • 循環参照: 読み込まれるファイル内で呼び出し元のファイルを直接または間接的に読み込むと、循環参照が発生します。


モジュールの例

my_module.cmake

# my_module.cmake

# モジュール名を定義する
set(MY_MODULE_NAME MyModule)

# モジュールバージョンを定義する
set(MY_MODULE_VERSION 1.0.0)

# モジュールに必要なライブラリを定義する
set(MY_MODULE_REQUIRED_LIBRARIES MyLib)

# モジュールのターゲットを定義する
add_library(MyModule STATIC ${MY_MODULE_SOURCES})
target_include_directories(MyModule PUBLIC include)
target_link_libraries(MyModule ${MY_MODULE_REQUIRED_LIBRARIES})

main.cmake

# main.cmake

# my_module.cmake を読み込む
include(my_module)

# MyModule モジュールを使用する
message(STATUS "MyModule モジュール名: ${MY_MODULE_NAME}")
message(STATUS "MyModule モジュールバージョン: ${MY_MODULE_VERSION}")

# MyModule モジュールにリンクする
add_executable(my_program main.cpp)
target_link_libraries(my_program MyModule)

実行方法

  1. my_module.cmakemain.cmake を同じディレクトリに保存します。
  2. 以下のコマンドを実行してビルドします。
cmake .
make
  1. 生成された実行ファイル my_program を実行します。
./my_program

出力

MyModule モジュール名: MyModule
MyModule モジュールバージョン: 1.0.0

この例では、utils.cmake というファイルを作成し、そのファイル内の関数を使用します。

utils.cmake

# utils.cmake

# ファイル名を定義する
set(UTILS_FILE_NAME utils.cmake)

# ファイルバージョンを定義する
set(UTILS_FILE_VERSION 1.0.0)

# 文字列を大文字に変換する関数
function(my_toupper str result)
    string(uppercase ${str} ${result})
endfunction()

main.cmake

# main.cmake

# utils.cmake を読み込む
include(utils.cmake)

# utils.cmake で定義された変数を使用する
message(STATUS "Utils ファイル名: ${UTILS_FILE_NAME}")
message(STATUS "Utils ファイルバージョン: ${UTILS_FILE_VERSION}")

# utils.cmake で定義された関数を使用する
string(TOLOWER "Hello, World!" temp)
my_toupper(temp toupper)
message(STATUS "大文字に変換された文字列: ${toupper}")

実行方法

  1. utils.cmakemain.cmake を同じディレクトリに保存します。
  2. 以下のコマンドを実行してビルドします。
cmake .
make
  1. 生成された実行ファイル my_program を実行します。
./my_program
Utils ファイル名: utils.cmake
Utils ファイルバージョン: 1.0.0
大文字に変換された文字列: HELLO, WORLD!


サブディレクトリ

プロジェクトをサブディレクトリ構造に整理し、各ディレクトリにCMakeLists.txtファイルを配置することで、「include」コマンドの代わりにサブディレクトリを利用することができます。この方法の利点は、コードを論理的に整理し、依存関係を明確にできることです。一方、欠点としては、ファイルシステムの階層が深くなり、プロジェクトの全体像が把握しにくくなる可能性がある点が挙げられます。


project(myproject)

add_subdirectory(src)

src/CMakeLists.txt

add_executable(myprogram main.cpp)
target_link_libraries(myprogram MyLib)

ターゲット依存関係

ターゲット依存関係を利用することで、「include」コマンドを使わずに別のターゲットのビルドを依存関係として指定することができます。この方法の利点は、ビルドの順序を明確に制御できることです。一方、欠点としては、依存関係が複雑になると、コードの理解が難しくなる可能性がある点が挙げられます。


add_executable(myprogram main.cpp)
target_link_libraries(myprogram MyLib)

add_executable(mylibrary library.cpp)

add_dependencies(myprogram mylibrary)

カスタムモジュール


# mymodule/CMakeLists.txt

set(MYMODULE_SOURCES library.cpp)
set(MYMODULE_LIBRARIES MyLib)

add_library(MyModule STATIC ${MYMODULE_SOURCES})
target_include_directories(MyModule PUBLIC include)
target_link_libraries(MyModule ${MYMODULE_LIBRARIES})
# main.cmake

add_subdirectory(mymodule)

add_executable(myprogram main.cpp)
target_link_libraries(myprogram MyModule)

最適な方法の選択

  • ターゲット依存関係
    ビルドの順序を明確に制御したい場合、または複雑な依存関係を持つプロジェクトの場合
  • サブディレクトリ
    コードを論理的に整理したい場合、または中規模から大規模なプロジェクトの場合

上記以外にも、以下の方法で「include」コマンドの機能を代替することができます。

  • CMakeユーティリティ
    CMakeが提供するユーティリティ関数を利用して、ファイルパスやマクロの定義などを取得する場合
  • find_packageコマンド
    外部ライブラリやモジュールを自動的に検出して利用する場合

これらの方法は、より高度な機能を提供しますが、習得には時間がかかる場合があります。必要に応じて、これらの方法も検討してみてください。