Edited at

CMakeの使い方(その1)


はじめに

2月のはじめ頃、CMakeを使ってビルドを自動化しようと思い、色々ググってCMakeLists.txtを作り試行錯誤したのですが、その時は結局断念しました。というのも、abcというステップがあるとすると、ググッて得られる解説はabcのaやbが抜けていきなりcから解説されたものが多く、初心者にはきつかったからです。特にcmake.orgのチュートリアルはまさにその典型で、チュートリアルのStep 1からして長過ぎて、本当に何が必要なのかわかりにくい・・・orz

ところが、今学期受けているHigh Performance Computingという講義で行われたCMakeの使い方の解説がわかり易すぎて、あっという間に自作ライブラリをCMakeでコンパイルすることができるようになり、CMakeの便利さに感動してしまいました。私と同じくCMake初心者という方にこの感動を共有すべく、ここに使い方をまとめておきます。私自身はc++を使っているので、下記ではc++の場合として説明しています。OSはUbuntu 16.04LTS、コンパイラはg++ 5.4.0です。

ここでは自分でコマンドラインからビルドする場合とCMakeの場合を対比させて説明しています。こうすると、CMakeの各コマンドとの対応がわかり、理解しやすいとおもいました。

もしこうした方がいいよとか、こういう使い方もあるよという場合はコメントを頂ければ幸いです。

2018年3月8日:

その2を作成しました。こんなに長いものを投稿したのは初めてです(^^;)


ステップ1:実行ファイルを作成


コマンドラインからビルド

まずg++コンパイラを使ったビルドの基本を確認します。こちらのサイトがコンパクトにまとめられていてわかりやすいかなと思います。詳細はgcc.gnu.orgのoutput optionsoption summaryを読んで勉強しましょう。

まず下記のような簡単なソースコードがあるとします。

---/

|-main.cpp
|-hello.hpp
|-hello.cpp


main.cpp

#include "hello.hpp"


int main() {
hello();
}


hello.hpp

#ifndef HELLO_H

#define HELLO_H

void hello();

#endif



hello.cpp

#include <iostream>

#include "hello.hpp"

void hello() {
std::cout << "Hello!" << std::endl;
}

まずこれを普通にコマンドを打ち込んでビルドします。

> g++ -c main.cpp hello.cpp   # ソースファイルをコンパイルしてオブジェクトファイル(main.o, hello.o)を生成

> g++ -o a.out main.o hello.o # オブジェクトファイルをリンクしてa.outという実行ファイルを生成

するとディレクトリの中は次のようになっているはずです。

---/

|-a.out
|-main.cpp
|-main.o
|-hello.hpp
|-hello.cpp
|-hello.o

このa.outを実行すればHello!と表示されます。

> ./a.out

Hello!

あるいは次のようにすると、オブジェクトファイルは生成されず、直接実行ファイルが作られます。

> g++ main.cpp hello.cpp


CMakeを使ってみる

では先程の最も簡単な例をCMakeを使ってビルドしてみましょう。CMakeを使ったビルドの設定はCMakeLists.txtというテキストファイルに記述します。ディレクトリの中身は次のようになります。

---/

|-CMakeLists.txt
|-main.cpp
|-hello.hpp
|-hello.cpp

CMakeLists.txtの内容は次のようになります。


CMakeLists.txt

# CMakeのバージョンを設定

cmake_minimum_required(VERSION 2.8)
# プロジェクト名と使用する言語を設定
project(test_cmake CXX)
# a.outという実行ファイルをmain.cppとhello.cppから作成
add_executable(a.out main.cpp hello.cpp)

CMakeを使ってビルドするときは、次のように必ずソースディレクトリとは別にビルド専用のディレクトリを作成し、その中でビルドします。

> mkdir build

> cd build
> cmake ..
> make

最終的なディレクトリ構成は以下のようになっているはずです。

---/

|-CMakeLists.txt
|-main.cpp
|-hello.hpp
|-hello.cpp
|-build/
|-a.out
|...(その他色々)

このようなCMakeLists.txtの設定の場合、実行ファイルが直接生成され、途中のオブジェクトファイルは生成されません。

さて、ここで最終的に得られるディレクトリの中身を比べてみましょう。普通にコマンドラインからビルドした場合、ソースコードとオブジェクトファイルおよび実行ファイルが混在し、元の状態に戻すのが面倒です。ところがCMakeを使った場合、ビルド結果は全てbuildディレクトリ内に保存されているため、buildディレクトリを削除するだけで容易に元の状態に戻すことができます。これはout-of-sourceビルドと呼ばれています。こちらの記事(CMake : out-of-sourceビルドで幸せになる)で詳しく解説されています。

とりあえず夜遅いので今日はここまで。明日ステップ2を追加します。


ステップ2:静的・共有ライブラリを作成


コマンドラインからビルド

さて、ライブラリを作っている場合は、静的・共有ライブラリ(*.a, *.so)を作成しておき、必要に応じて実行ファイルにリンクすることになります。まずこの作業をコマンドを打ち込んでやってみましょう。

ステップ1の例にgood_morning.hppgood_morning.cppというファイルが加わったと考えます。

---/

|-main.cpp
|-hello.hpp
|-hello.cpp
|-good_morning.hpp
|-good_morning.cpp


good_morning.hpp

#ifndef GOOD_MORNING_H

#define GOOD_MORNING_H

void good_morning();

#endif



good_morning.cpp

#include <iostream>

#include "good_morning.hpp"

void good_morning() {
std::cout << "Good morning!" << std::endl;
}

main.cppを次のように修正します。


main

#include "hello.hpp"

#include "good_morning.hpp"

int main () {
hello();
good_morning();
}


静的ライブラリを作成

hello.cppgood_morning.cppをコンパイルしてオブジェクトファイルを生成したあと、静的ライブラリlibgreetings.aを作成し、main.cppをコンパイルする際にリンクして実行ファイルを作成します。

> g++ -c hello.cpp good_morning.cpp             # オブジェクトファイル(hello.o, good_morning.o)の作成

> ar rvs libgreetings.a hello.o good_morning.o # 静的ライブラリ(libgreetings.a)の作成
> g++ main.cpp libgreetings.a # main.cppをコンパイルしてlibgreetings.aとリンクし実行ファイルa.outを作成

最終的なディレクトリ構成は以下のようになります。

---\

|-a.out
|-main.cpp
|-hello.hpp
|-hello.cpp
|-hello.o
|-good_morning.hpp
|-good_morning.cpp
|-good_morning.o
|-libgreetings.a


共有ライブラリを作成

hello.cppgood_morning.cppをコンパイルしてオブジェクトファイルを生成したあと、共有ライブラリlibgreetings.soを作成し、main.cppをコンパイルする際にリンクして実行ファイルを作成します。

> g++ -fPIC -c hello.cpp good_morning.cpp                 # オブジェクトファイル(hello.o, good_morning.o)の作成

> g++ -shared hello.o good_morning.o -o libgreetings.so # 共有ライブラリ(libgreetings.so)の作成
> g++ main.cpp -L. -lgreetings -Xlinker -rpath -Xlinker . # main.cppをコンパイルしてlibgreetings.soとリンクし実行ファイルa.outを作成

最終的なディレクトリ構成は以下のようになります。

---\

|-a.out
|-main.cpp
|-hello.hpp
|-hello.cpp
|-hello.o
|-good_morning.hpp
|-good_morning.cpp
|-good_morning.o
|-libgreetings.so

どちらのa.outを実行しても

> ./a.out

Hello!
Good morning!

と表示されるはずです。


CMakeを使ってみる

さて、同じことをCMakeを使ってやってみましょう。ステップ1と同じく、CMakeLists.txtを加えます。

---/

|-CMakeLists.txt
|-main.cpp
|-hello.hpp
|-hello.cpp
|-good_morning.hpp
|-good_morning.cpp

CMakeLists.txtの内容は次のようになります。


静的ライブラリを作成


CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

project(test_cmake CXX)
# hello.cppとgood_morning.cppをコンパイルして静的ライブラリlibgreetings.aを作成
add_library(greetings STATIC hello.cpp good_morning.cpp)
# a.outという実行ファイルをmain.cppから作成
add_executable(a.out main.cpp)
# a.outを作成する際にlibgreetings.aをリンク
target_link_libraries(a.out greetings)

ステップ1と同様にbuildディレクトリを作りビルドすると、ディレクトリの構成は以下のようになるはずです。

---/

|-CMakeLists.txt
|-main.cpp
|-hello.hpp
|-hello.cpp
|-good_morning.hpp
|-good_morning.cpp
|-build/
|-a.out
|-libgreetings.a
|...(その他色々)


共有ライブラリを作成


CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

project(test_cmake CXX)
# hello.cppとgood_morning.cppをコンパイルして共有ライブラリlibgreetings.soを作成
add_library(greetings SHARED hello.cpp good_morning.cpp)
add_executable(a.out main.cpp)
# a.outを作成する際にlibgreetings.soをリンク
target_link_libraries(a.out greetings)

buildディレクトリを作りビルドすると、ディレクトリの構成は以下のようになるはずです。

---/

|-CMakeLists.txt
|-main.cpp
|-hello.hpp
|-hello.cpp
|-good_morning.hpp
|-good_morning.cpp
|-build/
|-a.out
|-libgreetings.so
|...(その他色々)

CMakeを使ってビルドする場合、静的・共有ライブラリを作成するときの違いはたったの一語(STATIC/SHARED)です

add_library(greetings [SHARED|STATIC] hello.cpp good_morning.cpp)

またコマンドラインでビルドする場合は、特に共有ライブラリをリンクするときにリンクオプションが色々面倒なのですが、CMakeを使うと全部自動でやってくれます。素晴らしい。


静的・共有ライブラリの指定について(中級者以上)

前節ではSTATIC/SHAREDadd_libraryに直接指定しましたが実際は無指定にするべきです。

add_library(greetings hello.cpp good_morning.cpp)

この場合、add_libraryで指定されたターゲット(ここではgreetings)が共有または静的のどちらでコンパイルされるかは、グローバルオプションBUILD_SHARED_LIBSON/OFF(デフォルトはOFF)で決まります。CMakeを使ってビルドする際に次のように指定することが可能です。

cmake -DBUILD_SHARED_LIBS=ON ..

一般的には、各ライブラリごとに静的・共有ライブラリのどちらでビルドするか選択できるオプションを作成しておくべきです。

# GREETINGS_BUILD_SHARED_LIBSというオプションを作成。デフォルトをOFFに設定。

option(GREETINGS_BUILD_SHARED_LIBS "build greetings as a shared library" OFF)

if (GREETINGS_BUILD_SHARED_LIBS)
add_library(greetings SHARED hello.cpp good_morning.cpp)
else()
add_library(greetings STATIC hello.cpp good_morning.cpp)
endif()

こうすると先ほど同様にコマンドラインから指定することが可能です。

cmake -DGREETINGS_BUILD_SHARED_LIBS=ON ..


ステップ3:サブディレクトリにソースが分散している場合

さて、実際のライブラリにおいては、ステップ1・2のようにライブラリのルートディレクトリ直下にソースコードを一括でおいているなどということはありません。先ほどのような簡単なものなら別に構わないのですが、もっと大きなライブラリの場合はソースコードをその役割ごとに別々のディレクトリに置いておかないとわけがわからないことになります。

ステップ2の例のディレクトリ構成を下記のように見なおしたものを使って解説します。

---/

|
|--include/
| |--hello.hpp
| |--good_morning.hpp
|
|--src/
| |--hello.cpp
| |--good_morning.cpp
|
|--test/
|--main.cpp


コマンドラインからコンパイル

さて、これをコマンドラインからビルドしてみましょう。ルートディレクトリ直下にいるものとし、共有ライブラリを作成して実行ファイルにリンクするやり方をとります。

cd src

g++ -fPIC -c hello.cpp good_morning.cpp -I../include
g++ -shared *.o -o libgreetings.so
cd ../test
g++ main.cpp -I../include -L../src -lgreetings -Xlinker -rpath -Xlinker ../src

ステップ2と異なり、今度はLオプションに加えてIオプションでインクルードファイルを探すディレクトリを指定する必要があります。ステップ2でIオプションが必要なかったのは、カレントディレクトリはデフォルトでインクルードファイルを探す場所として認識されるからです。

ディレクトリ構成は以下のようになります。

---/

|
|--include/
| |--hello.hpp
| |--good_morning.hpp
|
|--src/
| |--hello.cpp
| |--hello.o
| |--good_morning.cpp
| |--good_morning.o
| |--libgreetings.so
|
|--test/
|--a.out
|--main.cpp


CMakeを使ってみる

さて、サブディレクトリが存在する場合、各ディレクトリごとにCMakeLists.txtを作ります。

---/

|--CMakeLists.txt
|
|--include/
| |--hello.hpp
| |--good_morning.hpp
|
|--src/
| |--CMakeLists.txt
| |--hello.hpp
| |--good_morning.hpp
|
|--test/
|--CMakeLists.txt
|--main.cpp

ルートディレクトリにあるCMakeLists.txtではサブディレクトリの登録をします。


/CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

project(test_cmake CXX)
# サブディレクトリを登録
add_subdirectory(src)
add_subdirectory(test)

/src/CMakeLists.txtおよび/test/CMakeLists.txtではそれぞれのディレクトリにあるファイルのコンパイル方法を指定してあげます。


/src/CMakeLists.txt

add_library(greetings

SHARED
hello.cpp
good_morning.cpp
)
# greetingライブラリのインクルードディレクトリを教えてあげる
# PROJECT_SOURCE_DIRはこのプロジェクトのルートディレクトリの絶対パス
target_include_directories(greetings
PUBLIC ${PROJECT_SOURCE_DIR}/include
)


/test/CMakeLists.txt

add_executable(a.out main.cpp)

# a.outをコンパイルする際にgreetingsをリンクする
target_link_libraries(a.out greetings)

add_libraryおよびadd_executableの最初の変数を「ターゲット」と呼びます。

キーワードPRIVATE/PUBLIC/INTERFACEの意味は、ターゲット(greetings)のみに必要(=PRIVATE)なのか、ターゲット(greetings)およびそれに依存するターゲット(a.out)にも必要(=PUBLIC)なのか、あるいはターゲットには必要ないがそれに依存するターゲットには必要(=INTERFACE)なのかを指します。

キーワード
ターゲットに必要
そのターゲットに依存するターゲットに必要

PRIVATE
:o:
:x:

PUBLIC
:o:
:o:

INTERFACE
:x:
:o:

この場合、インクルードディレクトリ${PROJECT_SOURCE_DIR}/includegreetingsのコンパイルだけでなく、a.outのコンパイルにも必要なため、PUBLICを指定します。

INTERFACEはヘッダーのみのライブラリに対して使います。

さてこれで準備は完了です。今までと同様にbuildディレクトリを作成してcmakeを実行してビルドすることができます。

次は色々なオプションの設定方法について書こうと思います。