cmakeの使い方
この文章はGitLab上で開発されており継続的に更新されていきます。
Contribution
この本は下記のようにGNU フリー文書利用許諾契約書の元で配布されています。 タイポの指摘や内容に関する不明瞭な点の指摘などがあればGitLab/issueに報告してください。
License
Copyright (C) 2020 Toshiki Teramura
この文書を、フリーソフトウェア財団発行の GNU フリー文書利用許諾契約書(バージョン1.2かそれ以降から一つを選択)が定める条件の下で複製、頒布、あるいは改変することを許可する。変更不可部分、表カバーテキスト、裏カバーテキストは存在しない。
cmake
cmakeはC++やFortranのプログラムでよく使用されているビルドツールの一つです。
cmakeで出来ること
- プログラムのビルドを行うためのスクリプトの生成
- cmake自体はプログラムのビルドを行うツール ではなく makeやVisual Studioのように実際にビルドコマンドを実行するプログラムの為のスクリプトを生成するためのツールです
- システムにインストールされているライブラリを検索し、コンパイルオプションを生成する
- 必要なライブラリをcmakeがインストールする事は出来ません
cmakeで出来ない事
- cmakeはPythonやNode.jsにおける
pip
やnpm
,yarn
に対応するものではありません。 - そもそもC++やFortranには配布用のパッケージ形式は無く、PyPIのようなレジストリもありません。
- cmakeは必要なライブラリをインストールする事は出来ません。インストールされているライブラリを探してくるだけです。
- ライブラリを探してくる方法は基本的にcmake本体に含まれるスクリプトにハードコードされています。
cmakeを使うべきか?
新しくC++のプロジェクトを始める
新規のC++プロジェクトにcmakeを使うのは良い考えですが、そもそも新たにC++プロジェクトを始めるのは良い考えではありません。 より環境の整った言語は多くあり、C++を使う事でこの本にあるような退屈な作業に多くの時間を取られることになります。
Autotoolsプロジェクト
Autotoolsはcmakeと同様にMakefileを生成するためのツールです。 既にAutotoolsで管理されているプロジェクトをcmakeに移行する場合、コストに見合う利益は得られない事が多いでしょう。
独自Makefile
多くのレガシーなプロジェクトでMakefileに足りない機構を実現するために独自の拡張が行われておりメンテナンス性を著しく下げています。 これらをcmakeに置き換える作業は非常に有益です。
Links
はじめてのcmake
ここではcmakeを使い始めるために小さいプロジェクトをビルドしてみましょう。 まず単一のC++ソースコードからなるプロジェクトを考えます:
${PROJECT_HOME}/
main.cpp
この時は以下のように設定を記述します
cmake_minimum_required(VERSION 3.0) # cmakeの最小バージョン
project("HelloCMake") # プロジェクトの名前
add_executable(Main main.cpp) # 実行ファイルを追加する
これをCMakeLists.txt
としてmain.cpp
と同じディレクトリにおきます。
このファイル名は特別で CMakeLists.txt な事に注意してください
${PROJECT_HOME}/
main.cpp
CMakeLists.txt
これで準備が出来ました。同じディレクトリでを次のコマンドを実行すれば実行ファイルMainが作成されます
cmake . # .を忘れずに
make
複数のソースとヘッダの場合
${PROJECT_HOME}/
main.cpp
mod.hpp
mod_func1.cpp
mod_func2.cpp
この場合も同じディレクトリに以下のCMakeLists.txt
を追加する。
cmake_minimum_required(VERSION 3.0)
project("HelloCMake")
add_executable(Main
main.cpp
mod_func1.cpp
mod_func2.cpp
)
add_executable
に引数を追加しました。cmakeのスクリプトではスペースが引数の区切り文字になります。
この時ヘッダーファイル mod.hpp
を追加する必要はありません。
ソースファイルが依存しているヘッダファイルはcmakeが自動的に検出して適切に依存関係を構築してくれます。
コンパイルフラグを指定する
常にコンパイル必要な場合はadd_definitions
を使う
cmake_minimum_required(VERSION 3.0)
project("HelloCMake")
add_executable(Main
main.cpp
mod_func1.cpp
mod_func2.cpp
)
Links
複数のディレクトリを管理する
ディレクトリが分かれている場合
${PROJECT_HOME}/
main.cpp
mod1.hpp
mod1/
func1.cpp
func2.cpp
mod2.hpp
mod2/
func1.cpp
func2.cpp
このくらいになってくるとcmakeによる管理の効果がでてくる。 この場合、
- 単一のCMakeLists.txtを使う方法
- 各ディレクトリにCMakeLists.txtを作る方法
がある。個人的には後者がおすすめである。
単一のCMakeLists.txtを用いる
${PROJECT_HOME}/
CMakeLists.txt <- new
main.cpp
mod1.hpp
mod1/
func1.cpp
func2.cpp
mod2.hpp
mod2/
func1.cpp
func2.cpp
上述のケースと同様にプロジェクトのトップにCMakeLists.txtを作り、
cmake_minimum_required(VERSION 2.8)
add_executable(Main
main.cpp
mod1/func1.cpp
mod1/func2.cpp
mod2/func1.cpp
mod2/func2.cpp
)
のようにする。上述のケースと本質的に同じである。
各ディレクトリにCMakeLists.txtを作る
${PROJECT_HOME}/
CMakeLists.txt <- new
main.cpp
mod1.hpp
mod1/
CMakeLists.txt <- new
func1.cpp
func2.cpp
mod2.hpp
mod2/
CMakeLists.txt <- new
func1.cpp
func2.cpp
cmake_minimum_required(VERSION 2.8)
add_subdirectory(mod1) # <- new
add_subdirectory(mod2) # <- new
add_executable(Main main.cpp)
target_link_libraries(Main Mod1 Mod2) # <- new
cmake_minimum_required(VERSION 2.8)
add_library(Mod1 STATIC
func1.cpp
func2.cpp
)
cmake_minimum_required(VERSION 2.8)
add_library(Mod2 STATIC
func1.cpp
func2.cpp
)
のようにする。少し手間かもしれないが、この方がモジュール化が分りやすいし増えてくると管理が楽。 cmakeでは静的ライブラリが簡単に作れ、さらにリンクも簡単。
add_library
STATICを付けると静的ライブラリを作る。
上の例だとUnix上ではmod1/libMod1.a
,mod2/libMod2.a
を作る。
target_link_libraries
ライブラリを実行ファイルにリンクする。
具体的にはフラグに-lMod1 -lMod2
が追加される形。
名前の解決はcmakeが行い、cmakeは自分で作ったライブラリの名前は
(ディレクトリ関係なく)グローバルに保持するので、
ここでmod1/Mod1
のようにする必要はない。
add_subdirectory
ディレクトリをcmakeの管理に追加する。
そのディレクトリにCMakeLists.txtが存在しないとエラーになる。
add_definitions
やset
による変数の定義は子ディレクトリには伝わりますが、
親ディレクトリには伝わらない。
Out-of-Source Build
ここまで簡単のために
cmake .
make
のようにビルドして来ましたがこれについて少し詳しく見ていきましょう。
cmake
コマンドは引数にディレクトリを取ります。
まずシェル上では.
は現在のディレクトリ $PWD
を指すので、
上のコマンドはcmake
に現在のディレクトリにあるCMakeLists.txt
を探しに行けという命令をしていることに成ります。
さて cmake .
を実行すると次のようなファイルが$PWD
に生成されているはずです
CMakeFiles/
Makefile
cmake_install.cmake
CMakeCache.txt
これらはcmakeが生成したビルド用の設定ファイル群で、ユーザーは中身を見る必要の無いものです。
これらをどこに出力するかはcmakeの-B
, --build
オプションで指定でき、デフォルトでcmakeコマンドを実行したディレクトリになります。
cmake . -B build
このようにCMakeLists.txt
を探すパス.
と設定を出力するパスbuild
を指定することが出来ます。
それぞれcmakeのスクリプト中では${CMAKE_SOURCE_DIR}
, ${CMAKE_BINARY_DIR}
として参照できます
Links
シェルスクリプトの実行
他のファイルを生成しない場合
ctagsやdoxygenのように、他のソースのコンパイルに関係ない物を生成するためには
add_custom_target
を使います:
add_custom_target(ctags ALL COMMAND "ctags" "-R" "-f" ".tags" "--languages=C++,python" "--exclude='CMake*'")
add_custom_target(document COMMAND "doxygen" "doc/Doxyfile")
COMMAND以下に実行したいシェルスクリプトを記述します。
ALL
を付けると毎回のmake
で自動的に実行されます。
ファイルを生成する場合
例えばプロジェクトKSEで使用するProtocol buffersのコードを生成したい場合、
KSE.pb.cc
とKSE.pb.h
が生成され、それをコンパイルする必要があります。
この場合はadd_custom_command
を使用します:
add_custom_command(
OUTPUT KSE.pb.cc KSE.pb.h
DEPENDS KSE.proto
COMMAND "protoc" "KSE.proto" "--cpp_out=." "--python_out=."
)
これによりターゲットにKSE.pb.cc
, KSE.pb.h
が追加され
make KSE.pb.cc
でKSE.pb.cc
が生成されます。
もちろん依存関係は自動で解決されますので、
add_library(KSE STATIC
logger.cpp
KSE.pb.cc
)
のようにある場合は勝手にKSE.pb.cc
を生成してくれます。
find_packageの動作を理解する
https://qiita.com/osamu0329/items/bd3d1e50edf37c277fa9 これが詳しい
オプション付きの関数を定義する
cmakeの組み込み関数にはオプション引数をとれるものがありますね。例えばinstallは
install(TARGETS targets... [EXPORT <export-name>]
[[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[NAMELINK_COMPONENT <component>]
[OPTIONAL] [EXCLUDE_FROM_ALL]
[NAMELINK_ONLY|NAMELINK_SKIP]
] [...]
[INCLUDES DESTINATION [<dir> ...]]
)
のようにたくさんのオプションを持ちます。これを自分で実装する話です。
cmakeの関数の引数
まずfunction
を使って関数を定義してみましょう:
function(myfunc arg)
message("ARG1 = ${arg}")
endfunction()
myfunc("a1")
$ cmake .
ARG1 = a1
...
名前を付けた引数は${arg}
のように参照できますね。実はcmakeは名前を付けなかった引数はすべて${ARGN}
に入れます
function(myfunc arg)
message("ARG1 = ${arg}")
message("ARGN = ${ARGN}")
endfunction()
myfunc("a1" "a2" 3)
${ARGN}
はリスト型になるので、表示するときは各成分が;
でつながれた形になります
$ cmake .
ARG1 = a1
ARGN = a2;3
...
cmake_parse_arguments
この${ARGN}
は手動でパースしないといけないのでしょうか?ここで登場するのがcmake_parse_argumentsです。
この関数はちょっとややこしい引数をとるのでドキュメントを読んでも分かりにくいです。サンプルをいくつか試してみましょう。
function(myfunc arg)
cmake_parse_arguments(MYFUNC "" "OUTPUT" "" ${ARGN})
message("ARG1 = ${arg}")
message("ARGN = ${ARGN}")
message("OUTPUT = ${MYFUNC_OUTPUT}")
endfunction()
myfunc("a1" "a2" 3 OUTPUT "/path/to/output")
これでOUTPUT
というオプションを持たせることが出来ます。
$ cmake .
ARG1 = a1
ARGN = a2;3;OUTPUT;/path/to/output
OUTPUT = /path/to/output
...
OUTPUT
は値を一つとるオプションです。関数に渡したオプションの値は${MYFUNC_OUTPUT}
として参照できます。このMYFUNC
はcmake_parse_argumentsの最初の引数で上げた文字列で、cmakeには名前空間が無いので関数名を付けて名前の衝突を回避します。
cmake_parse_argumentsの第2,3,4引数はそれぞれbool値のみを保持するフラグオプション、値を一つとるオプション、値を複数とるオプションです。
function(myfunc arg)
cmake_parse_arguments(MYFUNC "" "OUTPUT" "SOURCES" ${ARGN})
message("ARG1 = ${arg}")
message("ARGN = ${ARGN}")
message("OUTPUT = ${MYFUNC_OUTPUT}")
message("SOURCES = ${MYFUNC_SOURCES}")
endfunction()
myfunc("a1" "a2" 3
OUTPUT "/path/to/output"
SOURCES "vim" "emacs"
)
$ cmake .
ARG1 = a1
ARGN = a2;3;OUTPUT;/path/to/output;SOURCES;vim;emacs
OUTPUT = /path/to/output
SOURCES = vim;emacs
...
それぞれの引数にリストを上げると複数のオプションが定義できます
function(myfunc arg)
cmake_parse_arguments(MYFUNC "" "OUTPUT" "SOURCES;DEPENDENCIES" ${ARGN})
message("ARG1 = ${arg}")
message("ARGN = ${ARGN}")
message("OUTPUT = ${MYFUNC_OUTPUT}")
message("SOURCES = ${MYFUNC_SOURCES}")
message("DEPENDENCIES = ${MYFUNC_DEPENDENCIES}")
endfunction()
myfunc("a1" "a2" 3
OUTPUT "/path/to/output"
SOURCES "vim" "emacs"
DEPENDENCIES "glibc" "linux"
)
$ cmake .
ARG1 = a1
ARGN = a2;3;OUTPUT;/path/to/output;SOURCES;vim;emacs;DEPENDENCIES;glibc;linux
OUTPUT = /path/to/output
SOURCES = vim;emacs
DEPENDENCIES = glibc;linux
...
ccmake, cmake-gui
コンパイル毎に変えたい場合はccmake
コマンドあるいはcmake-gui
コマンドを使う
ccmake .
cmake-gui .
cmake
本体とは別パッケージになっている事が多いのでapt,yum,pacman等で検索する。
ccmake
を実行すると
CMAKE_BUILD_TYPE
CMAKE_INSTALL_PREFIX
の2つだけ表示される。変更したいのは
CMAKE_CXX_FLAGS
であって、これはt
を入力すると出現する。
ここで必要なフラグを調整する。
Git submodule
複数のプロジェクトで共通して必要なプロジェクトや、外部のライブラリを使用したいが、システムにインストールしたく無い場合の方法として、 Git Submoduleの機能を使う方法があります。
前提条件
C++プロジェクトprojに外部のC++プロジェクトsubを使用する場合を例に取って考えます。 上述のように以下の方法を実行するには
- projがcmakeで管理されている事
- proj, subがgitで管理されている事
- subがcmakeで管理されているか、ヘッダのみで構成されているテンプレートライブラリである事
が必要です。
手順
まずgitのリポジトリは
yourhost:repos/proj.git
anotherhost:repos/sub.git
のようになっているとします。各自の環境に合わせて読み替えてください。
アイデアは以下の通りです。
git submodule
を使用すれば外部のプロジェクトを取得できる- cmakeの
include_directories
マクロを使用すればgit submodule
で取得したライブラリを簡単にincludeできる - 外部プロジェクトがヘッダだけなら、それらをコンパイルする必要がないからincludeできれば十分
- 外部プロジェクトがcmakeで管理されていればそのツリーを親プロジェクトのツリーに取り込める
よって手順としては、
- projにおいて
git submodule
によりsubを取得する - projの
CMakeLists.txt
にinclude_directories
を記述する - subがcmake管理の場合は
add_subdirectory
マクロで取り込む
のみです。
例
proj/CMakeLists.txt
mod1/CMakeLists.txt
func1.cpp
func2.cpp
main.cpp
sub/
sub/mod1/func1.hpp
func2.hpp
mod2/func1.hpp
のような場合
...
include_directories(.)
...
としておけば例えばmod1/func1.cpp
においてincludeパスを指定する事なく:
add_library(mod1 STATIC
func1.cpp
func2.cpp
)
# -Iは特に指定しなくていい
#include "sub/mod2/func1.hpp"
...
のようにincludeする事が可能となります。
ctest
はじめてのctest
cpack
はじめてのcpack
scikit-build
scikit-buildというPythonの拡張モジュールをビルドするための補助ツールがあります。
Improved build system generator for CPython C, C++, Cython and Fortran extensions http://scikit-build.org
これを使ってcmakeでビルドされたプロジェクトをPythonの拡張モジュールとして配布する方法についてまとめます。まとめたものは以下にあります:
https://gitlab.com/termoshtt/skbuild-example
全体のディレクトリ構成は以下の通りです:
├── CMakeLists.txt
├── hello
│ ├── CMakeLists.txt
│ ├── _hello.cpp # これを拡張モジュールにコンパイル
│ ├── __init__.py
│ └── __main__.py # python -m hello で呼ばれたときに実行されるファイル
├── LICENSE
├── pyproject.toml
├── README.md
└── setup.py
setup.py
まずは通常のPythonのパッケージと同様にビルド用のスクリプトしてsetup.py
を用意しますが、setuptools
ではなく、skbuild
のsetup
を使います:
from skbuild import setup
setup(
name="hello",
version="1.2.3",
description="a minimal example package",
author="Toshiki Teramura <toshiki.teramura@gmail.com>",
license="MIT",
packages=["hello"],
)
pyproject.toml
依存関係は requirements.txt
ではなく公式のドキュメントに従って pyproject.toml
に記述します
[build-system]
requires = ["setuptools", "wheel", "scikit-build", "cmake", "ninja"]
最近はpyproject.tomlの制定によってsetup.py
を書かなくてもpip install
できるようになってきているらしいですが、今回はまだsetup.py
の拡張として提供されている機能を使用します。またPoetryにはscikit-buildがどうも対応してなさそう(?)なので今回は使いません。
CMakeLists.txt
元のコード(scikit-build/tests/samples/hello-cpp)がCMakeLists.txtを分割していたので分けていますが、特に意味はありません。順番に見ていきましょう。
cmake_minimum_required(VERSION 3.5.0)
project(hello)
find_package(PythonExtensions REQUIRED)
add_subdirectory(hello)
add_library(_hello MODULE _hello.cpp)
python_extension_module(_hello)
install(TARGETS _hello LIBRARY DESTINATION hello)
ポイントとしては PythonExtensions
を探してきている部分と、python_extension_module(_hello)
ですね。中身は調べられていないのですが、Pythonの拡張モジュールとして公開する際には必要のようです。
Build
setup関数がsetuptools
からskbuild
に切り替わっていますが、基本的な使い方は同じで、
python setup.py bdist_wheel
とすればwheelを作成してくれます。wheelはPEP 427で定義された配布形式で拡張子は *.whl
となります。whlファイルは実体としてはZIPアーカイブで、コンパイルされた共有ライブラリや他の静的なファイルを含めることができます。
上のコマンドで、dist/hello-1.2.3-cp37-cp37m-linux_x86_64.whl
のようなファイルが出来上がっているはずです。これはそのままインストールが可能です
pip install dist/hello-1.2.3-cp37-cp37m-linux_x86_64.whl --user
(--user
はお好みで)これでGlobalにインストールされたので 別のディレクトリに行って
$ python -m hello
Hello, World! :)
となると成功です。別のディレクトリに行かないと現在のディレクトリ以下にある hello
モジュールを読み込みますが、ここにはcmakeで生成された拡張モジュールがありません…(´・ω・`)
Links
- scikit-build document
- 正直これを読んでも分からん...(´・ω・`)
- tests/samples
- 上のリポジトリはここのhello-cppを拝借した