はじめに
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の各コマンドとの対応がわかり、理解しやすいとおもいました。
もしこうした方がいいよとか、こういう使い方もあるよという場合はコメントを頂ければ幸いです。
関連記事
参考資料
本記事を書くにあたりCMakeの公式ドキュメントを参照しました。
その他にはいろいろなYoutubeの動画を参考にしています。
また私自身は購入していませんが、Professional CMake: A Practical Guideという本はCMakeのco-maintainerの一人が書いた本だそうで、より高度な内容を勉強したい場合はこちらがよさそうです。
ステップ1:実行ファイルを作成
コマンドラインからビルド
まずg++コンパイラを使ったビルドの基本を確認します。こちらがコンパクトにまとめられていてわかりやすいかなと思います。詳細はgcc.gnu.orgのoutput optionsやoption summaryを読んで勉強しましょう。
まず下記のような簡単なソースコードがあるとします。
---/
|-main.cpp
|-hello.hpp
|-hello.cpp
#include "hello.hpp"
int main() {
hello();
}
#ifndef HELLO_H
#define HELLO_H
void hello();
#endif
#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
の内容は次のようになります。
# CMakeのバージョンを設定
cmake_minimum_required(VERSION 3.13)
# プロジェクト名と使用する言語を設定
project(test_cmake CXX)
# a.outという実行ファイルをmain.cppとhello.cppから作成
add_executable(a.out main.cpp hello.cpp)
CMakeを使ってビルドするときは、次のように必ずソースディレクトリとは別にビルド専用のディレクトリを作成し、その中でビルドします。
$ cmake -S . -B build
$ cmake --build build
最終的なディレクトリ構成は以下のようになっているはずです。
---/
|-CMakeLists.txt
|-main.cpp
|-hello.hpp
|-hello.cpp
|-build/
|-a.out
|...(その他色々)
このようなCMakeLists.txtの設定の場合、実行ファイルが生成され、途中のオブジェクトファイルは(その他色々)のディレクトリの中に生成されます。
さて、ここで最終的に得られるディレクトリの中身を比べてみましょう。普通にコマンドラインからビルドした場合、ソースコードとオブジェクトファイルおよび実行ファイルが混在し、元の状態に戻すのが面倒です。ところがCMakeを使った場合、ビルド結果は全てbuild
ディレクトリ内に保存されているため、build
ディレクトリを削除するだけで容易に元の状態に戻すことができます。これはout-of-sourceビルドと呼ばれています。こちらの記事(CMake : out-of-sourceビルドで幸せになる)で詳しく解説されています。
ステップ2:静的・共有ライブラリを作成
コマンドラインからビルド
さて、ライブラリを作っている場合は、静的・共有ライブラリ(*.a, *.so)を作成しておき、必要に応じて実行ファイルにリンクすることになります。まずこの作業をコマンドを打ち込んでやってみましょう。
ステップ1の例にgood_morning.hpp
、good_morning.cpp
というファイルが加わったと考えます。
---/
|-main.cpp
|-hello.hpp
|-hello.cpp
|-good_morning.hpp
|-good_morning.cpp
#ifndef GOOD_MORNING_H
#define GOOD_MORNING_H
void good_morning();
#endif
#include <iostream>
#include "good_morning.hpp"
void good_morning() {
std::cout << "Good morning!" << std::endl;
}
main.cpp
を次のように修正します。
#include "hello.hpp"
#include "good_morning.hpp"
int main () {
hello();
good_morning();
}
静的ライブラリを作成
hello.cpp
とgood_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.cpp
とgood_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
の内容は次のようになります。
静的ライブラリを作成
cmake_minimum_required(VERSION 3.13)
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
|...(その他色々)
共有ライブラリを作成
cmake_minimum_required(VERSION 3.13)
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
/SHARED
をadd_library
に直接指定しましたが無指定にするとどうなるのでしょうか。
add_library(greetings hello.cpp good_morning.cpp)
この場合、add_library
で指定されたターゲット(ここではgreetings)が共有または静的のどちらでコンパイルされるかは、グローバルオプションBUILD_SHARED_LIBS
のON/OFF(デフォルトはOFF)で決まります。CMakeを使ってビルドする際に次のように指定することが可能です。
$ cmake -S . -B build -DBUILD_SHARED_LIBS=ON
$ cmake --build build
一般的には、各ライブラリごとに静的・共有ライブラリのどちらでビルドするか選択できるオプションを作成しておくべきです。
# 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 -S . -B build -DGREETINGS_BUILD_SHARED_LIBS=ON
$ cmake --build build
ステップ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.cpp
| |--good_morning.cpp
|
|--test/
|--CMakeLists.txt
|--main.cpp
ルートディレクトリにあるCMakeLists.txt
ではサブディレクトリの登録をします。
cmake_minimum_required(VERSION 3.13)
project(test_cmake CXX)
# サブディレクトリを登録
add_subdirectory(src)
add_subdirectory(test)
/src/CMakeLists.txt
および/test/CMakeLists.txt
ではそれぞれのディレクトリにあるファイルのコンパイル方法を指定してあげます。
add_library(greetings
SHARED
hello.cpp
good_morning.cpp
)
# greetingライブラリのインクルードディレクトリを教えてあげる
# PROJECT_SOURCE_DIRはこのプロジェクトのルートディレクトリの絶対パス
target_include_directories(greetings
PUBLIC ${PROJECT_SOURCE_DIR}/include
)
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 | ||
PUBLIC | ||
INTERFACE |
この場合、インクルードディレクトリ${PROJECT_SOURCE_DIR}/include
はgreetings
のコンパイルだけでなく、a.out
のコンパイルにも必要なため、PUBLIC
を指定します。
INTERFACE
はヘッダーのみのライブラリに対して使います。
さてこれで準備は完了です。今までと同様にbuild
ディレクトリを作成してcmakeを実行してビルドすることができます。