CMake — Cross-platform Make
環境に合わせた Makefile を自動生成する。
似たようなことをする configure
スクリプトと比べて動作が高速で、
ライブラリの依存関係なども簡潔・柔軟に記述できる。
configure
ではそれを生成する開発者だけが
autotools を使うのに対して、
CMakeでは開発者と利用者の双方がCMakeをインストールして使う。
https://cmake.org/cmake/help/latest/
基本
CMakeLists.txt
を各ディレクトリに配置して、階層的に管理する。
プロジェクトのトップに置くものは、以下のようなコマンドで始める必要がある。
cmake_minimum_required(VERSION 3.15)
project(helloworld
VERSION 0.1.0
LANGUAGES CXX)
add_executable()
や add_library()
でビルドターゲットを作成し、
target_*()
でそれらの設定を整えて、
install()
でインストールする対象や行き先を指定する、というのが基本の流れ。
add_executable(a.out hello.cpp)
target_compile_options(a.out PRIVATE -Wall -Wextra -pedantic)
install(TARGETS a.out)
Commands
https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html
Scripting commands
configure_file(<input> <output> [COPYONLY] [@ONLY])
: ファイルの一部を置換しつつ複製する。 例えば@PROJECT_VERSION@
などを含むconfig.hpp.in
に値を埋め込んでconfig.hpp
を生成するとか。function(<name> [args...])
foreach(var IN LISTS list)
message(STATUS "Hello world!")
option(VARIABLE "Message" ON)
set(VARIABLE value)
cmake_path()
パス操作全般。 3.20 より古い環境ではget_filename_component()
。
Project commands
https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#project-commands
外部のCMakeプロジェクトを利用する:
find_package(<name> [version] [REQUIRED] ...)
: ビルド・インストール済みのを取り込む。add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
: インストール前のソースツリーを取り込む。
ターゲットを定義する:
add_executable(<name> [EXCLUDE_FROM_ALL] ...)
add_library(<name> [STATIC|SHARED|OBJECT] [EXCLUDE_FROM_ALL|IMPORTED] ...)
option Mac Linux Win description STATIC
.a
.a
.lib
オブジェクトをまとめて目次をつけたarchive。静的リンクで複製は生じるが最適化される。 SHARED
.dylib
.so
.dll
オブジェクトをまとめて実行可能ファイル一歩手前まで加工したもの。動的リンクでストレージにもメモリにも1つだけあればいいので効率はいいけどRPATHのお世話が必要。 OBJECT
.o
.o
.obj
コンパイル単位のオブジェクト。ライブラリを経由しないぶん STATIC
より効率的かと思いきや、リンク時に最適化されず、使わないコードまで保持される。使い道はライブラリを複数作りたい時くらい?
ターゲットのプロパティを追加する:
set_target_properties(<target> PROPERTIES p1 v1 p2 v2 ...)
target_compile_definitions(<target> <INTERFACE|PRIVATE|PUBLIC> ...)
target_compile_features(<target> <P|P|I> ...)
target_compile_options(<target> [BEFORE] <I|P|P> ...)
target_sources(<target> <I|P|P> ...)
target_include_directories(<target> [SYSTEM] [BEFORE] <I|P|P> ...)
: ソースツリー内とインストール先でヘッダーファイルの置き方が変わる場合などに使う。 外部からインポートしたパッケージのヘッダーにはこちらを使わず、次の関数を使う。target_link_libraries(<target> <I|P|P> ...)
: この関数でターゲット間の依存関係を繋げていくのがCMakeの肝。 共有ライブラリのオプション-L -l
だけではなく、ヘッダーのオプション-I
もお世話してくれる。- ターゲットなしの
include_directories()
link_directories()
link_libraries()
などはディレクトリ単位で影響が及ぶ亜種で、非推奨。
install(TARGETS)
install(<FILES|PROGRAMS>)
install(DIRECTORY)
install(EXPORT)
: 外部のCMakeから使いやすくするためのconfigをインストールする。 似て非なるexport()
はビルドツリーにあるものを使わせるための謎コマンド。
オプション
PRIVATE
- このターゲットをビルドするときだけ使い、これを利用するときには参照させない。 例えば「このプロジェクトのライブラリをビルドするにはBoostヘッダーが必要だけど、 これを利用するときにそれらのパスを知る必要はない」とか。
INTERFACE
- このターゲットでは使わないけど、これを利用するときには参照させる。 例えば、ヘッダーライブラリを作る場合とか。
PUBLIC
- このターゲットにもこれを利用するターゲットにも使う。使う場面あるかな?
EXCLUDE_FROM_ALL
make [all]
から外れて、明示的なターゲット指定でのみビルドされるようになる。
Variables
https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html
Variables that Provide Information
PROJECT_SOURCE_DIR
,PROJECT_BINARY_DIR
: 直近のproject()
におけるソースツリー、ビルドツリーの最上階。CMAKE_SOURCE_DIR
,CMAKE_BINARY_DIR
: ソースツリー、ビルドツリーの最上階。 他のプロジェクトからadd_subdirectory()
で使われる場合、PROJECT_*
よりも上になる。CMAKE_CURRENT_SOURCE_DIR
,CMAKE_CURRENT_BUILD_DIR
: ソースツリー、ビルドツリーにおける現在地。PROJECT_NAME
,PROJECT_VERSION
:project()
で設定したやつ。CMAKE_SKIP_RPATH
CMAKE_VERBOSE_MAKEFILE
: とりあえずON
Variables that Change Behavior
BUILD_SHARED_LIBS
: STATIC/SHAREDが明示されていないadd_library()
でどっちをビルドするか。CMAKE_BUILD_TYPE
: Debug, Release, RelWithDebInfo, MinSizeRel.CMAKE_INSTALL_PREFIX
: configureでの--prefix
に相当CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT
CMAKE_PREFIX_PATH
: パッケージやファイル探索find_*()
の候補パスを追加する
Variables that Describe the System
APPLE
, UNIX
, WIN32
Variables that Control the Build
CMAKE_BUILD_RPATH
CMAKE_BUILD_WITH_INSTALL_NAME_DIR
CMAKE_BUILD_WITH_INSTALL_RPATH
CMAKE_INSTALL_NAME_DIR
CMAKE_INSTALL_RPATH
CMAKE_INSTALL_RPATH_USE_LINK_PATH
CMAKE_MACOSX_RPATH
Environment variables
https://cmake.org/cmake/help/latest/manual/cmake-env-variables.7.html
変数として自動的に利用可能になったりはせず、
$ENV{VAR}
みたいな形で参照するのが基本。
ただし、一部の環境変数はCMakeの変数の初期値として採用される。
e.g., CMAKE_PREFIX_PATH
, CXX
, <PackageName>_ROOT
, etc.
何が渡っているかは cmake -E environment
で確認できる。
CMAKE_EXPORT_COMPILE_COMMANDS
- 定義しておくとコンフィグ時に
compile_commands.json
を生成してもらえる。 これで clangd にコンパイルオプションを伝えられる。 ソースファイルの親ディレクトリを辿るだけでなくbuild
という名のサブディレクトリも探してくれるので-B build
の慣習に従っていればコピーやシムリンクも不要。
C++
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_EXTENSIONS OFF)
target_compile_options(common PRIVATE
-Wall -Wextra -pedantic
$<$<STREQUAL:${CMAKE_SYSTEM_PROCESSOR},x86_64>:-march=native>
$<$<STREQUAL:${CMAKE_SYSTEM_PROCESSOR},arm64>:-march=armv8.3-a+sha3>
)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
cmake_print_variables(CMAKE_BUILD_TYPE)
set(CMAKE_CXX_FLAGS_DEV "-O2 -g")
CMAKE_CXX_*
のようなグローバル設定を使わず
target_*()
でターゲットごとに設定するのが今後の主流。
CMAKE_CXX_KNOWN_FEATURES
に cxx_std_17
などの便利なメタタグが導入されたのは CMake 3.8 から。
Predefined variable | default |
---|---|
CMAKE_CXX_FLAGS |
|
CMAKE_CXX_FLAGS_DEBUG |
-g |
CMAKE_CXX_FLAGS_MINSIZEREL |
-Os -DNDEBUG |
CMAKE_CXX_FLAGS_RELEASE |
-O3 -DNDEBUG |
CMAKE_CXX_FLAGS_RELWITHDEBINFO |
-O2 -g -DNDEBUG |
#ifndef NDEBUG
なコードを残しつつ、
そこそこ速くコンパイル&実行したい、
という組み合わせ -O2 -g
は用意されていないので自分で定義する。
CMAKE_CXX_FLAGS_???
を適当に作れば
-DCMAKE_BUILD_TYPE=???
をcase-insensitiveに解釈してもらえる。
Generator expressions
https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html
ビルド時の状態に応じてに変数を評価する仕組み。
コンフィグ時に評価される if()
とは使い方が異なる。
例えば、プロジェクト内のビルド時と、外部パッケージとして利用される時とで、 インクルードパスを使い分ける。
target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
コンフィグ時には評価前の文字列でしかないので、
if()
などの条件分岐にも使えないし、
cmake_print_variables()
や message()
しても中身は見えない。
実際にどんな値が入ったかを確かめるには
file(GENERATE OUTPUT <outfile> CONTENT <content>)
などでビルド時に書き出すことになる。
Modules
https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html
include()
や find_package()
から使う。
include(CMakePrintHelpers)
変数の名前と中身を表示してくれる。 名前を2回書かずに済む。 次の二つは等価:
cmake_print_variables(VAR)
message(STATUS VAR="${VAR}")
GNUInstallDirs
https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html
インストール先のディレクトリを指定するときの標準的な値を決めてくれる。
Variable | Value |
---|---|
CMAKE_INSTALL_BINDIR |
bin |
CMAKE_INSTALL_INCLUDEDIR |
include |
CMAKE_INSTALL_LIBDIR |
lib , lib64 |
CMAKE_INSTALL_DATADIR |
share |
CMAKE_INSTALL_FULL_<dir> |
${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_<dir>} |
CMakePackageConfigHelpers
- https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html
- https://cmake.org/cmake/help/latest/guide/importing-exporting/
他のプロジェクトから以下のように利用されるライブラリを作りたい。
外部から IMPORTED
されたターゲットであることを明確にするため
名前空間::ターゲット
という形でリンクするのが筋:
project(OtherProject CXX)
find_package(MyLib)
target_link_libraries(OtherTarget PRIVATE MyLib::MyLib)
こうやって使ってもらうためには *config.cmake
ファイルを
find_package()
の探索先
のどこかにインストールする必要がある。
選択肢があり過ぎて悩ましく、公式ドキュメントにも推奨などは書かれていないが、
識者のコメント
によれば次の二択で使い分ける方針が良さそう:
share/cmake/${PROJECT_NAME}
: header-only, architecture-independent.find_package()
は${CMAKE_INSTALL_DATADIR}
に追従せずshare
を読みにいく。${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
: コンパイル成果物を含む、architecture-dependent.
install(TARGETS)
の EXPORT
オプションでターゲットを関連づけ、
install(EXPORT)
でファイルのインストールを設定する:
project(MyLib
VERSION 0.1.0
LANGUAGES CXX)
# ...
install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}-config
)
set(config_destination ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
install(EXPORT ${PROJECT_NAME}-config
DESTINATION ${config_destination}
NAMESPACE ${PROJECT_NAME}::
)
バージョン情報も同じところに送り込む:
set(version_file ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(${version_file}
COMPATIBILITY AnyNewerVersion
)
install(FILES ${version_file}
DESTINATION ${config_destination}
)
find_package()
から使うにはここまでの設定で十分だが、
add_subdirectory()
からでも同じ形で使えるようにするため、
ALIAS
を設定しておいたほうがいい:
add_library(${PROJECT_NAME})
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
上記のように直接 EXPORT *-config
するのは簡易版。
ライブラリ利用時に何らかの処理を行いたい場合は、
同じ内容を EXPORT *-targets
のような名前で書き出しておき、
それを include()
する *-config.cmake
を次のようなコマンドで生成する。
configure_package_config_file(config.cmake.in ${PROJECT_NAME}-config.cmake
INSTALL_DESTINATION ${config_destination}
)
鋳型となる config.cmake.in
にはとりあえず次の3行を書く:
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/${CMAKE_FIND_PACKAGE_NAME}-targets.cmake")
check_required_components(${CMAKE_FIND_PACKAGE_NAME})
これだけだと直接 EXPORT *-config
するのとほとんど変わらないけど、
ほかにも好きな処理を書いてインストールできる、というのがミソ。
例えばzlibを使うライブラリを作って、その依存関係を伝播させたいとき、
次のような処理を書いておくと利用者側で find_package(ZLIB)
を書かなくてよくなる:
include(CMakeFindDependencyMacro)
find_dependency(ZLIB)
find_dependency()
は上流で指定された REQUIRED
や QUIET
などをうまく転送してくれる find_package()
ラッパー。
check_required_components()
は
configure_package_config_file()
が @PACKAGE_INIT@
のところに生成してくれる関数で、
サポート外のコンポーネントが指定された場合に <PackageName>_FOUND
をFalseにする。
具体的には、ユーザーから find_package(<PackageName> COMPONENTS compo)
とされたときに
<PackageName>_compo_FOUND
変数をチェックするだけなので、
例えば上記の2行に加えて次のように書くと、
zlibが見つかったら zlib
コンポーネントを提供する、という挙動になる:
set(${CMAKE_FIND_PACKAGE_NAME}_zlib_FOUND ${ZLIB_FOUND})
check_required_components()
はメッセージが不親切だったりしてイマイチなので、
NO_CHECK_REQUIRED_COMPONENTS_MACRO
オプションで作らせないようにして、
foreach(component ${${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS})
を自分で回してチェックするパターンもよく見かける。
公式ガイド
でも
pr0g/cmake-examples
でもそうしている。
コンポーネントの提供方法について調べると、 それぞれ異なる使用例みたいなものしか見つからなくて理解に苦労した。 しかし分かってみると要件は案外単純:
*config.cmake
内でユーザーから指定されたコンポーネントをチェックし、 メッセージを表示するなり何なりの応答する。選択肢は2つ。<PackageName>_<component>_FOUND
を定義してcheck_required_components()
に任せる。${<PackageName>_FIND_COMPONENTS}
を自前で回して処理する。
- 慣例的に、コンポーネントと同名のターゲットを名前空間内に定義して
EXPORT
する。
FetchContent
https://cmake.org/cmake/help/latest/module/FetchContent.html
外部ライブラリをコンフィグ時に取ってくる。 CMake 3.11 から。
include(FetchContent)
set(FETCHCONTENT_QUIET OFF)
cmake_print_variables(FETCHCONTENT_SOURCE_DIR_IGRAPH)
FetchContent_Declare(
igraph
GIT_REPOSITORY https://github.com/igraph/igraph.git
GIT_TAG ${PROJECT_VERSION}
GIT_SHALLOW ON
)
FetchContent_MakeAvailable(igraph)
cmake_print_variables(igraph_SOURCE_DIR, igraph_BINARY_DIR)
FetchContent_Declare()
- まずこれで依存関係を宣言する。 複数ある場合、先に全部宣言してからまとめてMakeAvailableを呼ぶのが推奨。
- ソースに関するオプションは ExternalProject とほぼ同じ。
FIND_PACKAGE_ARGS
: (3.24+)
EXCLUDE_FROM_ALL
: (3.28+)FetchContent_MakeAvailable()
- 宣言された依存ライブラリを利用可能な状態にする。(3.14+)
- これひとつを実行することが推奨されているが、各段階を手動で書くこともできる。
find_package()
を試みて、見つからなければ次に進む。(3.24+)FetchContent_GetProperties()
で過去にPopulateしたものがあるか確認。<lowercaseName>_POPULATED
が定義されていなければ次に進む。FetchContent_Populate()
でソースコードを取得する。FETCHCONTENT_SOURCE_DIR_<uppercaseName>
が定義されている場合はfetchせずそこにあるものを使う。 成功したら3つの変数をセットする:<lowercaseName>_POPULATED
<lowercaseName>_SOURCE_DIR
<lowercaseName>_BINARY_DIR
add_subdirectory()
でプロジェクトに取り込む。
似て非なる ExternalProject
はビルド時に実行されるので
add_subdirectory()
の対象にできず、
execute_process()
で git を直接叩くなどして凌いでいた。
FindThreads
https://cmake.org/cmake/help/latest/module/FindThreads.html
-lpthread
とか自分で書かない。
find_package(Threads)
target_link_libraries(MyTarget PRIVATE Threads::Threads)
FindBoost
https://cmake.org/cmake/help/latest/module/FindBoost.html
Boostの特別扱いはdeprecatedになった。
普通のCMakeパッケージとして探すには、
CONFIG
とか NO_MODULE
オプションを足して明示的にConfigモードを使う。
find_package(Boost CONFIG REQUIRED COMPONENTS context)
target_link_libraries(MyTarget PRIVATE Boost::context)
ヘッダーだけでいい場合は Boost::boost
をリンクする。
CTest
https://cmake.org/cmake/help/latest/module/CTest.html
include(CTest)
if(BUILD_TESTING)
add_subdirectory(test)
endif()
# test/CMakeLists.txt
add_executable(test-gene gene.cpp)
add_test(NAME gene COMMAND $<TARGET_FILE:test-gene>)
ctest -V
で実行。
一部のテストのみ実行したいときは -R <pattern>
で絞る。
include(CTest)
は勝手にCDashの設定をして
DartConfiguration.tcl
を生成する。
次のように書き換えればそのへんをスキップできる:
option(BUILD_TESTING "Build the testing tree." ON)
enable_testing()
CLI
cmake
https://cmake.org/cmake/help/latest/manual/cmake.1.html
ビルド用のディレクトリを別に作って out-of-source で実行するのが基本。
やり直したいときは、そのディレクトリごと消す。
3.0以降 cmake --target clean
はあるが、
CMakeのバージョンを上げたときなどcleanしたい場面で使えない。
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=${HOME}/local -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j2
cmake --install build
-S <dir>
- ソースツリーを指定する。
3.13から。それまではundocumentedで
-H<dir>
という形だった。 -B <dir>
- ビルドツリーを指定する。 3.13から。それまではundocumentedだった。
-D <var>=<value>
- コマンドラインから変数を設定する。
-G <generator-name>
- Makefile, Ninja, Xcode, etc.
- デフォルトでは
Makefile
が書き出されるのでmake && make install
と書いてもいいけど、 generator非依存のcmake --build
を使ったほうがいい。 cmake --install <dir>
が使えるのは3.15以降。 それまではcmake --build build --target install
と明示する必要があった。-E <command>
- シェルの違いを気にせず基本的なコマンドが使えるように。e.g.,
chdir <dir> <cmd>
make_directory <dir>
-L
- キャッシュされている変数をリストアップ。
H
をつけると説明文も。A
をつけるとadvancedな変数も。 見るだけなら-N
オプションと共に。
Versions
- 3.28:
FetchContent_Declare(... EXCLUDE_FROM_ALL)
, C++20 modules, Ubuntu 24.04 noble - 3.24:
FetchContent_Declare(... FIND_PACKAGE_ARGS)
,--fresh
- 3.23:
FILE_SET
- 3.22: Ubuntu 22.04 jammy
- 3.21:
PROJECT_IS_TOP_LEVEL
- 3.20:
cmake_path()
,cxx_std_23
- 3.19:
CMakePresets.json
- 3.17:
CMAKE_EXPORT_COMPILE_COMMANDS
- 3.16: Ubuntu 20.04 focal
- 3.15:
cmake --install
- 3.13:
cmake -S . -B build
,target_link_directories
,target_link_options
- 3.12:
cxx_std_20
, linkingOBJECT
libraries - 3.11:
include(FetchContent)
- 3.8:
cxx_std_17