LoginSignup
1120

More than 1 year has passed since last update.

CMakeの使い方(その1)

Last updated at Posted at 2018-03-06

はじめに

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の使い方(その2)
CMakeの使い方(その3)

参考資料

本記事を書くにあたりCMakeの公式ドキュメントを参照しました。
その他にはいろいろなYoutubeの動画を参考にしています。
また私自身は購入していませんが、Professional CMake: A Practical Guideという本はCMakeのco-maintainerの一人が書いた本だそうで、より高度な内容を勉強したい場合はこちらがよさそうです。

ステップ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 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.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 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
    |...(その他色々)

共有ライブラリを作成

CMakeLists.txt
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/SHAREDadd_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ではサブディレクトリの登録をします。

/CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
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を実行してビルドすることができます。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1120