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
RUNTIME DESTINATION bin
)
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] ...)
ターゲットのプロパティを追加する:
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
C++
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
POSITION_INDEPENDENT_CODE ON
WINDOWS_EXPORT_ALL_SYMBOLS ON
)
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>} |
Linuxでは lib64
が標準的だが、Linuxbrewで使うには lib
にする必要がある。
if(${CMAKE_INSTALL_PREFIX} MATCHES linuxbrew)
set(CMAKE_INSTALL_LIBDIR lib)
endif()
CMakePackageConfigHelpers
https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html
他のプロジェクトから以下のように利用されるライブラリを作りたい。
外部プロジェクトであることを明確にするため
名前空間::ターゲット
という形でリンクするのが筋:
project(otherproject CXX)
find_package(mylib)
# add_subdirectory(mylib)
target_link_libraries(othertarget PRIVATE mylib::mylib)
${CMAKE_INSTALL_PREFIX}/share/
らへんにconfigファイルを送り込む必要がある。
install(TARGETS)
の中で EXPORT
のためのターゲット定義し、
install(EXPORT)
でその設定を行う:
project(mylib
VERSION 0.1.0
LANGUAGES CXX)
# ...
install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}-config
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(EXPORT ${PROJECT_NAME}-config
DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}
NAMESPACE ${PROJECT_NAME}::
)
バージョン情報も同じところに送り込む:
set(VERSION_CONFIG ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${VERSION_CONFIG} COMPATIBILITY AnyNewerVersion
)
install(FILES ${VERSION_CONFIG}
DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}
)
find_package()
から使うにはここまでの設定で十分だが、
add_subdirectory()
からでも同じ形で使えるようにするため、
ALIAS
を設定しておいたほうがいい:
add_library(${PROJECT_NAME} SHARED)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
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
set(Boost_NO_BOOST_CMAKE ON)
find_package(Boost REQUIRED COMPONENTS filesystem)
target_link_libraries(mytarget PRIVATE Boost::filesystem)
ヘッダーだけでいい場合は Boost::boost
ターゲットをリンクする。
探索パスを追加するには BOOST_ROOT
を設定する。
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だった。
-DCMAKE_XXX=YYY
- コマンドラインから変数を設定する。
-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)
, Ubuntu 24.04 noble - 3.24:
FetchContent_Declare(... FIND_PACKAGE_ARGS)
- 3.22: Ubuntu 22.04 jammy
- 3.20:
cmake_path()
- 3.16: Ubuntu 20.04 focal
- 3.15:
cmake --install
- 3.13:
cmake -S . -B build
- 3.11:
include(FetchContent)
- 3.8:
cxx_std_17