藻ログ

都会でOLをしています

cmakeゆとり用

cmakeがこわい

自分は永らくMakefileを書いていて,自分で書くような小〜中規模のプロジェクトではそれで十分だと感じていました.
spin.atomicobject.com
cmakeについては,本来Makefile1枚で済む薄いプロジェクトで大量に中間ファイルや長いMakefileを生成したり,cmake由来の問題でビルドのデバッグが複雑化するなどの想像から永らく敬遠していました.
しかし,最近は複数ターゲットのビルドを手軽にやりたい等の理由でcmakeを利用したいことが増えてきました.

cmake

cmake tutorial (official)

公式が参考になりますが,いきなり複雑すぎる問題があるのでもっとうっすいうっすい用例からみていきたい思います.
CMake Tutorial | CMake

documents

公式が一番良いです.
https://cmake.org/cmake/help/v3.3/
自分はcmakeから検索してます. 詳細はman読んで下さい.

$ cmake --help-command add_executable
$ cmake --help-module FindPkgConfig

cmakeの基本

最も単純な例.

proj_root
├── build/
├── main.cpp
└── CMakeLists.txt

CMakeListsを以下のように記述します.

# CMakeLists.txt
cmake_minimum_required(VERSION 3.3)
add_executable(main main.cpp)

cmake_minimum_required で利用したいcmakeのバージョンを指定し*1
add_executable でソースと,コンパイル先のオブジェクト名を指定します.

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               source1 [source2 ...])

ビルドはbuild/ディレクトリ以下で行います.

$ mkdir build
$ cd build
$ cmake ..
$ make

cmakeを実行すると,build/以下にMakefileができるのであとはいつものようにmakeすれば良いです.生成物は全てcmake を実行したディレクトリ以下に配置されます.

build
├── CMakeCache.txt
├── CMakeFiles/
├── Makefile
├── cmake_install.cmake
└── main

cmakeのオプション

プロジェクト名

プロジェクト名と言語を指定できる.プロジェクト名を指定しておくと,別プロジェクトからの読込が可能になります.*2

project(MyProject CXX)

コンパイラ,フラグの設定

set 命令で様々なフラグを設定できるので,コンパイル時のフラグなどはこれで設定します.

set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_CXX_FLAGS "-fPIC -O3 -g -Wall -Wextra")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

インクルードディレクトリの指定

Makefile-include 命令や,コンパイラでの -I オプションは include_directories で代用します.

include_directories(/path/to/includes)

ライブラリのリンク

コンパイラでの -L オプションによるディレクトリ指定は link_directories に相当し,リンクはtarget_link_libraries で行います.

link_directories(/path/to/boost)
target_link_libraries(main -lboost_system -lboost_filesystem)

link_directories は記述行以降に設定されたターゲットにのみ有効であることに注意.

CMakeLists.txt

以上をまとめると大体こんな感じになります.ただこの規模だと正直Makefileを書く手間とあまり変わりません.

# CMakeLists.txt
cmake_minimum_required(VERSION 3.3)

project(MyProject CXX)

set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_CXX_FLAGS "-fPIC -O3 -g -Wall -Wextra")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

set(BOOST_INCLUDE_PATH /usr/local/opt/boost/include)
set(BOOST_LIBRARY_PATH /usr/local/opt/boost/lib)
set(BOOST_LIBS "-lboost_filesystem -lboost_system")

include_directories(${BOOST_INCLUDE_PATH}) # -I
link_directories(${BOOST_LIBRARY_PATH})  # -L

add_executable(main main.cpp)
target_link_libraries(main ${BOOST_LIBS})  # -l

find_packageモジュールの利用

自分がMakefile を書いていたときは pkg-config を利用したり手でパスを指定したりしていましたが,cmakeではある程度ライブラリを賢く探してくれる仕組みがあります.
find_package — CMake 3.0.2 Documentation

$ cmake --help-module-list | less

すれば利用できるリストが確認でき,Find${LIB} というモジュールがあるものは,find_package(${LIB} REQUIRED) のように記述するだけで呼び出すことができます.

find_package で boost をみつける

boostを利用するには,FindBoostモジュールが利用できます.
FindBoost — CMake 3.0.2 Documentation
上で書いた例を find_package を利用して書き換えると以下のようになります.こうすると,-lboost_system, -lboost_filesystem を勝手に${Boost_LIBRARIES} へ格納してくれます.詳細はドキュメントを参照してください.

find_package(Boost COMPONENTS system filesystem REQUIRED)
include_directories(${Boost_INCLUDE_DIR})
link_directories(${Boost_LIBRARY_DIR})

add_executable(main main.cpp)
target_link_libraries( main ${Boost_LIBRARIES} )

pkg-config で OpenCVをみつける

pkg-configをcmakeから呼び出すこともできます.ここではOpenCVを例にみてみます. いやOpenCVはfind_packageで直接見つけることもできますが.
当然普通に pkg-config が支えて opencv.pc ファイルが pkg-config の検索パスにある必要があります.
FindPkgConfig — CMake 3.0.2 Documentation

find_package(PkgConfig)
pkg_check_modules(OPENCV REQUIRED opencv)
include_directories(${OPENCV_INCLUDE_DIRS})
link_directories(${OPENCV_LIBRARY_DIRS})

add_executable(main main.cc)
target_link_libraries(main ${OPENCV_LIBRARIES})

ここでは,pkg_check_modules によって $ pkg-config (--cflags|--libs) opencv を行うように,ライブラリとインクルードパスの検索が行われ,find_package 時と同様に以下の変数に格納されます.

<PREFIX>_FOUND
<PREFIX>_LIBRARIES # -l
<PREFIX>_LIBRARY_DIRS # -L
<PREFIX>_LDFLAGS
<PREFIX>_LDFLAGS_OTHER
<PREFIX>_INCLUDE_DIRS # -I
<PREFIX>_CFLAGS
<PREFIX>_CFLAGS_OTHER

PREFIX は上の例では

pkg_check_modules(<PREFIX> [REQUIRED] [QUIET] <MODULE> [<MODULE>]*)
  checks for all the given modules

で,自由に決められます.詳しくはドキュメントを参照してください.

なんか変に抽象化されてデバッグが面倒になっただけな気がする

それはある
まあ環境依存にならないというメリットは...あるかも

複数ターゲットのプロジェクトを作る

なんか長くなってきた.
add_executable でターゲットを追加していけば複数ターゲットのビルドは簡単にできるのですが,1個のCMakeListsで色々しすぎるとわけがわからなくなってくるのでディレクトリ階層をまたいだCMakeLists の書き方をみてみます.

サブディレクトリ以下を管理対象に加える

以下の記述でサブディレクトリ以下も rootのディレクトリでcmakeしたときの管理対象にできます.

add_subdirectory(dir)

ディレクトリ階層を跨ぐcmake

以下のようなプロジェクトで,src1, src2 ごとにbuild/src1, build/src2 へターゲットを生成することを考えます.

proj_root
├── CMakeLists.txt
├── main.cpp
├── src1
│   ├── CMakeLists.txt
│   └── main.cpp
└── src2
    ├── CMakeLists.txt
    └── main.cpp

この場合,以下のように CMakeLists を記述すればルートディレクトリで cmake すると全部まとめてビルドされます.

CMakeLists.txt
cmake_minimum_required(VERSION 3.3)

find_package(Boost COMPONENTS system filesystem REQUIRED)
include_directories(${Boost_INCLUDE_DIR})
link_directories(${Boost_LIBRARY_DIR})
add_executable(main main.cpp)
target_link_libraries( main ${Boost_LIBRARIES} )

add_subdirectory(src1)
add_subdirectory(src2)
src1/CMakeLists.txt
add_executable(target1 main.cpp)
target_link_libraries(target1 ${Boost_LIBRARIES}) 
src2/CMakeLists.txt
add_executable(target2 main.cpp)
target_link_libraries(target2 ${Boost_LIBRARIES}) 

Vimからcmakeする

もっと良い方法があるかもしれないけどとりあえず以下のようにした.quickfixが使いたいんだけどmakeprgを書き換えるしかない?

augroup cpp
  au!
  autocmd FileType cpp ca make !(cd ./build && make)
  autocmd FileType cpp ca cmake !(cd ./build && cmake ..)
augroup END
追記 04/16

ca[bbrev] でのmappingだとnormalモード全体で展開される(つまり,/make と検索しようとするとmakeの中身がコマンドに展開されてしまう)ので,以下のようにすると良いのではと教えてもらいました.

cnoreabbrev <expr> make getcmdtype() ==# ':' ? '!(cd ./build && make)' : 'make'
cnoreabbrev <expr> cmake getcmdtype() ==# ':' ? '!(cd ./build && cmake)' : 'cmake'

自分は/をdenite.vimのsearchのmapに使ってたので気づかなかった...

" search
nnoremap <silent> / :<C-u>Denite -buffer-name=search -auto-resize line<CR>

quickfix使うのは以下にmappingしてみた.

nnoremap <silent> <Space>m :cexpr system('cd build && make')<CR> :copen<CR>
nnoremap <silent> <Space>c :!cd<Space>build;make<Space>clean<CR>

Ninja

cmake は -Gオプションでビルドシステムを指定できます.
Big Sky :: 高速なビルドシステム「ninja」
Ninja を使いたい場合は以下のようにして簡単に導入できます.

$ rm CMakeCache.txt
$ cmake .. -G Ninja
$ ninja

Makefileも書こう

なんだかんだデバッグするときには,普通にMakefileの知識があった方がいいとは思います

GNU Make 第3版

GNU Make 第3版

追記

更に発展した内容がまとまった本です.

CMake Cookbook: Building, testing, and packaging modular software with modern CMake

CMake Cookbook: Building, testing, and packaging modular software with modern CMake

cmake cookbook 掲載のレシピがまとまっていて参考になります.
github.com

*1:気づいたら最新は3.8までいってました.好きなのを使ってください

*2:例えば言語をC++とすればCの設定を見に行かなくなる恩恵がある