87
108

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C/C++プロジェクトをCMakeでビルドする

Posted at

CMake

CMakeは、C/C++プロジェクト向け、マルチプラットフォームのビルドツールです。
僕が初めてプログラミングに触りC/C++を勉強していた当時、難しそうという雰囲気から避けてしまっていたツールでもありました。しかし、現在はPythonライブラリ開発で高速化のためにpybindを用いて処理をC++化したり、大学でのロボコンではOpenCV、ROSなどを用いた大きめのプロジェクト開発をしたりするため、CMakeをガッツリ利用しています。

CMakeは10年以上前から開発されているツールですが、現在も、これから先も使い続けられるツールだと思います。今回はQiitaの「Qiita 10周年記念イベント - 10年後のために今勉強しておきたい技術」というテーマで、難しそうと思っていた過去の自分にも分かるように、そして今からCMakeを勉強しようと思っている人の資料となるように、という思いを込めて投稿しようと思います。

プロジェクトにCMakeを導入する時には、ガイドラインをまとめてくれている記事に目を通した上で、レファレンスを適宜参照することを推奨する。

  • CMake Reference Documentation
    • 利用しようとしているコマンドはそのCMakeのバージョンで利用できるのかはドキュメントとよく照らし合わせるようにしたほうが良い。新しいバージョンでは利用しやすいように機能が増えていることもあり、それはドキュメントを読めば書いてあり、古いバージョンを利用する場合にはどうするかも書いてある1
  • CMakeスクリプトを作成する際のガイドライン - Qiita
    • 参考文献も合わせて、眺めておいて、何が良くないとされているのかは知っておくと良い

環境

ちなみに、CMake 3.13.0のリリースは2018年11月20日。

$ cmake --version
cmake version 3.13.4

対象とするプロジェクトの構造

今回は、ライブラリをビルドし、ヘッダファイルと共に適切な場所に配置するようなプロジェクトを作成する。
また、このライブラリをリンクさせるソースコードのサンプルも任意で生成出来るようにする。

ここでは以下のようなmylibライブラリのプロジェクト構造を作成したとして進める。

  • include : ライブラリのヘッダファイル
    • #include "mylib/mylib.h"としてインクルードすることを想定している
  • lib : ライブラリのヘッダファイルに対応するソースファイル
    • include/mylib/mylib.hlib/mylib/mylib.cが対応する
  • examples : mylibライブラリを利用したソースコードのサンプル
.
├── CMakeLists.txt
├── include
│  └── mylib
│     └── mylib.h
├── lib
│  └── mylib
│     ├── CMakeLists.txt
│     └── mylib.c
└── examples
   ├── CMakeLists.txt
   └── main.c

基本的なビルド方法

基本的なビルド方法としては、以下のような流れを想定している。
後ほど、#高度なビルド方法で、ビルドする際に利用できる重要なオプションについて触れる。

$ mkdir build
$ cd build
$ cmake .. 
$ make #ライブラリのビルド
$ make examples #ライブラリを利用したサンプルをビルドしてバイナリを生成する

ライブラリをインストールする際には、CMAKE_INSTALL_PREFIXを定義する。詳細は# ビルドしたライブラリのインストール方法で書いている。

$ cmake .. -DCMAKE_INSTALL_PREFIX='$HOME/.local'
$ make install

オブジェクトファイル*.oやライブラリ、バイナリなどの成果物を削除する場合はcleanターゲットが用意されている。

$ make clean

ビルド環境を作成しなおす場合、大抵の場合はcmake ...のコマンドで作り直しが出来る。
しかし、オプションをつけている場合、cmake ...のコマンドでは消えない場合があるので、何か変だと思った場合は、一度buildフォルダを削除してやり直すことも良い。

$ rm -rf build
$ mkdir build
...

CMakeのデバッグ方法

手順を追っている時に、詰まった時の助けになるであろうデバッグ方法をいくつか紹介する。

CMakeがキャッシュしている変数の値を表示する

$ cmake .. -LAH

キャッシュしている変数を-L、ADVANCEDに設定された変数も表示するときは-A、変数名についている説明を表示するなら-Hをつける

cmake(1) - CMake Documentation #Generate a Project Buildsystem-L[A][H]の項目を参照。

traceする

$ cmake .. --trace-source=CMakeLists.txt

CMakeLists.txtに記述された内容のどこを実行したのかをtraceできる。

$ cmake .. --trace

CMakeが自動的に生成して読み込んで実行したものも含めてtraceする。

また、それぞれで、変数が展開された状態でログ出力をしたい場合は--trace-expandが利用できる。

$ cmake .. --trace-source=CMakeLists.txt --trace-expand
$ cmake .. --trace --trace-expand

cmake(1) - CMake Documentation #Generate a Project Buildsystem--traceの項目辺りを参照。

makeした時のコンパイルされるコマンドを出力する

$ make VERBOSE=1 

gcc -I./include -o mylib.c.o -c mylib.cみたいなのを眺めたかったらVERBOSE=1をつける

プロジェクトルートのCMakeLists.txt

# 動作確認できたCMakeのバージョン
cmake_minimum_required(VERSION 3.13)

# プロジェクトの情報
project(mylib
    VERSION 1.0
    DESCRIPTION "mylib project"
    LANGUAGES C
)

# in-source ビルドガード
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
    message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt. ")
endif()

# 以下のフォルダにあるCMakeLists.txtも読み込んでビルドターゲットを生成する
add_subdirectory(lib/mylib)
add_subdirectory(examples EXCLUDE_FROM_ALL)

基本的にはこんな感じ。

cmakeのバージョン

cmake_minimum_required(VERSION 3.13)

まずは、CMakeのバージョンを設定しておく。ここには、動作確認出来ている最も低いバージョンを記載しておくべきで、動作を保証するための何よりも一番最初に設定しておくポリシーでもある。ここでは、今回利用したCMakeのバージョンが3.13だったので、このようにしている。

プロジェクトの情報

project(mylib
    VERSION 1.0
    DESCRIPTION "mylib project"
    LANGUAGES C
)

プロジェクトの情報をprojectに記載しておく。これによって、PROJECT_NAMEなど、いくつかの変数名がセットされる。
project - CMake Documentation

in-sourceビルドガード

# guard against in-source builds
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
    message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt. ")
endif()

この部分を説明する前に、まず、ビルドの方法には、in-sourceと呼ばれる方法とout-of-sourceと呼ばれる方法の2種類があるという話をしておく。
cmakeを使った方法として、以下のようにbuildフォルダを作成してからビルドする方法をよく見かけると思う。これはout-of-sourceと呼ばれるビルド方法で、out-of-sourceビルドをすることを強く推奨する

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

in-sourceの問題点はCMake : out-of-sourceビルドで幸せになる - Qiitaから引用すると、以下の通り。out-of-sourceを使った時はこれらの問題を解決でき、その話はQiitaの記事を参照してほしい。

# in-sourceビルド (cmake .) の何が問題なのか?
まずは、in-source ビルドのどこが問題なのか、確認します。

  • git でソース管理する際に、ビルド成果物や cmakeコマンドが生成する各種ファイルによってリポジトリが汚れしまい git statusした際に、本来追加すべきファイルを見落とす可能性がある
  • cmakeが生成したファイルをまとめて消したいときにディレクトリごとに CMakeCache.txtCMakeFiles ディレクトリを消すのが苦痛
  • Debugビルド、Releaseビルドを分けたい時に、ソースツリーを別々に用意する必要がある
    https://qiita.com/osamu0329/items/7de2b190df3cfb4ad0ca#in-sourceビルド-cmake--の何が問題なのか

さて、本題に戻るが、ここではin-sourceビルドガードを記述していた。
前述のような問題で開発者を悩ませないようにするための対策で、これはC++向けライブラリEigenのCMakeLists.txtで利用されていたin-sourceビルドガードをそのまま利用している。

subdirectoryを追加する

add_subdirectory(lib/mylib)
add_subdirectory(examples EXCLUDE_FROM_ALL)

プロジェクトルートに配置したCMakeLists.txt以外にも、lib/mylib以下やexamples以下にもCMakeLists.txtを配置している。
このように、CMakeLists.txtを配置したディレクトリをadd_subdirectoryに追加することで、それらをビルドの対象として読み込む事ができ、CMakeLists.txtを分割することが出来るものだと思えば良い。

add_subdirectory(examples EXCLUDE_FROM_ALL)のように、EXCLUDE_FROM_ALLという引数が与える事ができる。
これの効果を理解するために、CMakeを用いてビルドする手順をもう一度見てみる。

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

このmakeを実行する際に、ビルドターゲットを指定することが出来るのだが、ターゲットを指定しなかった場合はDEFAULT_TARGETとして指定されているターゲットが利用されることになる。
通常は、CMakeによって用意されるallDEFAULT_TARGETとしてセットされている2

CMakeでは、CMakeLists.txtにライブラリやバイナリの生成ルールを記述した場合、通常、それらはallターゲットに追加されることになる。allターゲットに追加したくない場合は、EXCLUDE_FROM_ALLを指定することで出来る。
今回のように、add_subdirectoryEXCLUDE_FROM_ALLを与えると、そのディレクトリ内で記述されている全ての生成ルールがallターゲットから除外される。もっと細かく、ライブラリやバイナリ単位でも可能である。

examplesallターゲットから除外した理由についても触れておくと、今回のmylibプロジェクトはライブラリを提供しているものを想定している。何らかのCLIツールのバイナリを生成するプロジェクトではない。
なので、ライブラリだけ生成できればOK。
ライブラリを利用したサンプルとしてexamplesのバイナリを生成するルールを用意しているけど、ライブラリを使いたいだけならバイナリを生成する必要は無く、ユーザが必要に応じて生成出来るようにしたい、という理由になる。3

では、次はそれぞれのディレクトリに配置されているCMakeLists.txtを見ていく。

lib/mylib/CMakeLists.txt

lib/mylibディレクトリには、ライブラリのヘッダファイルに対応するソースファイルが配置されている。
ここに配置されるCMakeLists.txtでは、ライブラリの生成ルール、インストール方法を記述している。

# mylibを共有ライブラリとして生成するためのオプションの定義
option(MYLIB_BUILD_SHARED_LIBS "build mylib as a shared library" OFF)

# オプション定義によって生成するライブラリの種類(SHARED/STATIC)を切り替える部分
if (MYLIB_BUILD_SHARED_LIBS)
    add_library(mylib SHARED)
else()
    add_library(mylib STATIC)
endif()

# mylibを生成するために必要なソースファイル
target_sources(mylib
    PRIVATE # mylib.cはmylibライブラリを生成するためだけに必要なのでPRIVATE
    mylib.c
)

# mylibを生成するために必要なヘッダファイルが配置されているディレクトリを指定する
target_include_directories(mylib
    # mylib.hなどはmylibライブラリを生成するのに加えて、
    # mylibライブラリを利用する他のバイナリなどでも利用するのでPUBLIC
    PUBLIC 
    ${PROJECT_SOURCE_DIR}/include
)

# GNUのディレクトリ構造で定義されたCMAKE_INSTALL_<dir>の変数を読み込む
include(GNUInstallDirs)
# install mylib library
install(TARGETS mylib
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
# install headers
install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
    FILES_MATCHING PATTERN "*.h"
)

mylibを共有ライブラリとして生成するためのオプションの定義

option(MYLIB_BUILD_SHARED_LIBS "build mylib as a shared library" OFF)

まず、前提として、ライブラリを静的ライブラリで生成するのか、共有ライブラリで生成するのかはユーザが指定出来るようにするべきである4。そして、それを指定するためのオプションとしてBUILD_SHARED_LIBSが既に用意されており、それがONOFFかを判断して切り替えてくれている。
しかし、これはプロジェクト全体に適用されてしまうため、通常はライブラリごとにオプションを用意する。

ここでは、mylibライブラリに対して、共有ライブラリとして生成するためのオプションとして、MYLIB_BUILD_SHARED_LIBS定義し、オプションの説明と、デフォルト値を与えておく5

このオプションの値を変更したい時は以下のように-Dオプションを利用する。

$ cmake .. -DMYLIB_BUILD_SHARED_LIBS=ON

ライブラリの生成ルール

if (MYLIB_BUILD_SHARED_LIBS)
    add_library(mylib SHARED)
else()
    add_library(mylib STATIC)
endif()

前述のオプション定義によって生成するライブラリの種類を分岐させると同時に、ライブラリの生成ルールを記述している。
ライブラリをビルドするにはadd_libraryを用い、引数にはライブラリ名と、そのライブラリの種類を指定する。
SHAREDを指定すると共有ライブラリとしてlibmylib.soSTATICを指定すると静的ライブラリとしてlibmylib.aが生成される。
add_library #normal-libraries - CMake Documentation

mylibを生成するために必要なソースファイル

target_sources(mylib
    PRIVATE
    mylib.c
)

ライブラリの生成に必要なソースファイルの定義はadd_libraryでも出来るが、target_sourcesを利用して記述することも出来る6

target_sourcesにリストアップしたソースファイルやヘッダファイルには、PRIVATEPUBLICINTERFACEを指定出来る。それぞれの意味を完結に表した表をCMakeスクリプトを作成する際のガイドライン#PRIVATE/PUBLIC/INTERFACEを適切に使うから引用したものが以下の表である。

キーワード ターゲットが必要とする ターゲットに依存するターゲットが必要とする
PRIVATE :o: :x:
PUBLIC :o: :o:
INTERFACE :x: :o:

mylib.cmylibライブラリの生成に必要とする。
しかし、mylibをリンクして利用するターゲットでは成果物であるlibmylib.aやヘッダファイルが必要となるのであって、mylib.c自体は必要ではない。なのでここではPRIVATEが適切である。

今回、mylibライブラリのビルドに必要なソースファイルはmylib.cだけであったが、実際のライブラリでは多くのソースファイルに依存することになる。そういった場面では、*.cでパターンにマッチしたファイルを自動で収集させたい、という需要が出てくると思う。
自動で収集する機能は、file(GLOB ...)7aux_source_directory8が利用できる。

しかし、それらのドキュメントに記載されているように、自動で収集する機能を利用することは推奨していない
その内容をここでも記載しておくと、その理由は、これを利用した場合、CMakeは新しいソースファイルがいつ追加されたかを認識することが出来ないからである。
本来、新しいファイルが追加された場合にはCMakeLists.txtが編集されるため、CMakeがビルド環境を再生成してくれる。しかし、自動で収集させた場合、新しいファイルを追加してもCMakeLists.txtを編集しないため、CMakeはビルド環境を再生成するタイミングを検出することが出来なくなる。
結果、新しいファイルを追加した場合は手作業でビルド環境を再生成させることになる。ということらしい9

mylibを生成するために必要なヘッダファイルが配置されているディレクトリを指定する

target_include_directories(mylib
    PUBLIC 
    ${PROJECT_SOURCE_DIR}/include
)

mylib.hなどはmylibライブラリを生成するのに加えて、mylibライブラリをリンクする他のバイナリなどでも利用するのでPUBLICが適切。
先程、ファイルを自動で収集するのは良くないといいつつ、ここではディレクトリを指定してるのは良いのか?と思うかもしれない。これは各ソースファイル内で正しくincludeできるようにするための記述であり、ヘッダファイルの依存関係の解決をしているわけではない(たぶん。あと、依存関係は別でちゃんと解決されている)10
これは、gcc -I./include -c mylib.c -o mylib.oとするように、ソースファイル内でincludeしているファイルをどこから探すか?という情報を与える-Iオプションに対応している。

ここまで記述すれば、ライブラリのビルドが出来るようになっていると思う。#基本的なビルド方法の手順を利用して、試してみてもいいかも。

GNU Coding Standardsとして定義されているインストールディレクトリを利用する

include(GNUInstallDirs)

ここからは、インストール方法の記述になる。まずここでは、GNU Coding Standardsとして定義されているインストールディレクトリをCMAKE_INSTALL_<dir>の変数で利用できるように読み込んでいる。今回利用する変数は以下の3つで、具体的には以下のような値が環境に合わせてセットされる。

  • CMAKE_INSTALL_LIBDIR = lib or lib64 or lib/<multiarch-tuple> on Debian
  • CMAKE_INSTALL_INCLUDEDIR = include
  • CMAKE_INSTALL_PREFIX = /usr/local on UNIX or c:/Program Files/${PROJECT_NAME} on Windows.

GNUInstallDirs - CMake Documentation
CMAKE_INSTALL_PREFIX - CMake Documentation

ビルドしたライブラリのインストール方法

install(TARGETS mylib
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

ビルドしたライブラリのインストール方法を記述している。インストールするターゲットはmylibをビルドして出来た成果物であり、その成果物のタイプごとにインストール方法を記述できる。
今回の成果物としては、静的ライブラリ(ARCHIVE)か共有ライブラリ(LIBRARY)の場合がある。それらをインストールする作業としては、ファイルを適切な場所に配置ができれば良いので、DESTINATIONだけ指定しておけば良い。
配置場所としてDESTINATION${CMAKE_INSTALL_LIBDIR}を指定すると、${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}に配置されることになる。
変数を展開してみると、/usr/local/libとなるので、ライブラリは/usr/local/lib/libmylib.aと配置される。

/usr/local/に配置するにはroot権限が必要になるので11、その代わりとして$HOME/.local/がよく利用される。
そこで、${CMAKE_INSTALL_PREFIX}を上書きしてビルド環境を作成した上でmake installすると良い。

$ cmake .. -DCMAKE_INSTALL_PREFIX='$HOME/.local'
$ make install

install - CMake Documentation

ヘッダファイルのインストール

install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
    FILES_MATCHING PATTERN "*.h"
)

今回は、include内の全てのヘッダファイルをインストールする。この場合は、install(DIRECTORY ...)が利用できる。
ディレクトリは再帰的にファイルが検索される。
配置場所は、ライブラリの時同様に${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}に配置されることになるので、/usr/local/include/mylib/mylib.hのようにヘッダが配置される。
また、FILES_MATCHING PATTERN "*.h"で、ヘッダファイルだけ配置するようにしている。

includeの中の一部だけをインストールしたい場合、PUBLIC_HEADERにヘッダ一覧を指定しておいて、前述のinstall(TARGET mylib ...)でインストールする方法が使えると思う12

ここまでの記述で、ライブラリとヘッダファイルのインストールが出来るようになった。

examples/CMakeLists.txt

add_custom_target(examples)
add_dependencies(examples
    main
)

add_executable(main main.c)
target_link_libraries(main
    mylib
)

mylibライブラリを利用したサンプルをビルドするルールを記述している。

examplesターゲットを作成する

add_custom_target(examples)

makeコマンドでは、ターゲットを引数として与えることができた。
make examplesとすれば、examplesをターゲットとして指定したことになり、このコマンドでmylibライブラリを利用したサンプルを全てビルドするものを用意したい。
examplesターゲットのような独自のターゲットはadd_custom_targetを用いて作成できる。この時点ではまだ何も生成されないので、以下のadd_dependencies()で定義する。

examplesターゲットで生成したいものを定義する

add_custom_target(examples)
add_dependencies(examples
    main
)

今回は、サンプルとしてmainを用意しようとしていて、これをexamplesターゲットの依存関係としてadd_dependenciesを利用して定義する。
これによって、examplesターゲットを実行するためにはmainを生成する必要がある→mainが生成されていなければ生成する、という動作が実現できる。
今回はmainしか用意していないが、サンプルとして複数用意しており、それらもexamplesターゲットで生成されてほしい場合はadd_dependenciesmainに続けて記述していけば良い。

mainのビルド方法を定義する

add_executable(main main.c)

ライブラリの時はadd_libraryであったが、実行ファイルを生成する場合はadd_executableを指定する13
1つのソースファイルから1つの実行ファイルを生成する場合は、target_sourceに分離させず、add_executableにまとめて書いてしまう方が冗長にならなくて良いと思う。
add_executable - CMake Documentation

mainにmylibライブラリをリンクする

target_link_libraries(main
    mylib
)

main.cmylib.hをincludeしていたので、実行ファイルを生成する際にmylibライブラリをリンクする必要がある。
そのときにはtarget_link_librariesを利用する。
これによって、mainを生成する時に、mylibライブラリがまだビルドされていない場合は先にmylibライブラリをビルドするように、よしなにされる。
target_link_libraries - CMake Documentation

これで、make examplesでサンプルをビルドすることが出来るようになった。
#基本的なビルド方法に帰って、一通りの流れを試してみてほしい。

高度なビルド方法

コンパイラを指定する

MacではXCodeが持っているコンパイラやgccだと思っているclangがいたりするため、コンパイラを上手く指定したい場面がある。
しかし、CMakeLists.txt内でコンパイラを直接指定することは良くない。なぜなら、その部分が環境依存になってしまうため、異なる環境の差異を吸収出来なくなてしまうからである。
これはCMakeによって解決させる、もしくは、以下のようにCMakeを実行する際に、ユーザが適切にコンパイラを指定するべきである。

cmake .. -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ 

How can I make CMake use GCC instead of Clang on Mac OS X? - stack overflow

ビルドタイプにDebug,Releaseなどを指定する

gdbデバッグを行う目的で-gオプションをつけてビルドしたいときにはDebugでのビルドを行う。一方、Releaseは最適化した状態でビルドしたい時に用いる。
このDebugReleaseCMAKE_BUILD_TYPEに指定する。

# デバッグするために、-gオプションをつける
$ cmake .. -DCMAKE_BUILD_TYPE=Debug

# 最適化をするために、-O3オプションをつける
$ cmake .. -DCMAKE_BUILD_TYPE=Release

DebugReleaseは、大文字から始まるCamel caseを利用するようにした方が良い。CMAKE_BUILD_TYPE自体は大文字小文字でも関係なく正しく切り替わる。
しかし、CMakeLists.txtの記述によっては、${CMAKE_BUILD_TYPE}にセットされている値がDebugと一致するか、のように、大文字小文字を区別して利用している場合がある14

CMAKE_BUILD_TYPEのUPPER CASEにご用心 - Qiita

新しいバージョンのCMakeを使ってcmake --installを使う

CMake 3.13では${CMAKE_INSTALL_PREFIX}を上書きしてビルド環境を作成した上でmake installする必要があった。

$ cmake .. -DCMAKE_INSTALL_PREFIX='$HOME/.local'
$ make install

しかし、より新しいバージョン15のCMakeでは、この2行を以下の1行で実行出来るようになっている。

$ cmake --install . --prefix="$HOME/.local"

Step 4: Installing and Testing - CMake Documentation

その他

mylibを別のプロジェクトから利用したい

# Build mylib
include(FetchContent)
FetchContent_Declare(
    mylib
    GIT_REPOSITORY https://github.com/hoge/mylib.git
    GIT_TAG        origin/main
)

FetchContent_GetProperties(mylib)
if(NOT mylib_POPULATED)
    FetchContent_Populate(mylib)
    add_subdirectory(${mylib_SOURCE_DIR} ${mylib_BINARY_DIR})
endif()

add_subdirectory(src)

例えば、GitHubなどでmylibのプロジェクトが公開されているのであれば、CMakeからFetchContentを利用して取得する事ができる。

FetchContentを利用できるようにする

include(FetchContent)

includeする。

取得したいリポジトリの情報

FetchContent_Declare(
    mylib
    GIT_REPOSITORY https://github.com/hoge/mylib.git
    GIT_TAG        origin/main
)

ここに取得したいGitHubリポジトリの情報を記述する。ここではGIT_TAGにブランチ名を指定しているが、コミットハッシュやv1.0などのタグも利用できるようになっている。

2重で読み込んでしまうのを防ぐ

FetchContent_GetProperties(mylib)
if(NOT mylib_POPULATED)
    FetchContent_Populate(mylib)
    add_subdirectory(${mylib_SOURCE_DIR} ${mylib_BINARY_DIR})
endif()

FetchContent_GetProperties(<name>)で、<name>_POPULATED<name>_SOURCE_DIR<name>_BINARY_DIRの3つの変数を読み込んでいる。
これらの変数は、FetchContent_Populate()によって定義されるため、初回の読み込みではif(NOT mylib_POPULATED)の条件がtrueになるが、以降はfalseとなるので、2重で読み込んでしまうのを防ぐ事ができる。

注意することとしては、FetchContent_Populate()に渡した名前はlower caseに変更されmylib_POPULATEDmylib_SOURCE_DIRmylib_BINARY_DIRのようにprefixとして利用されることである16
FetchContent #command:fetchcontent_populate - CMake Documentation

ちなみに…、新しいCMakeのバージョンなら、以下の一行で出来るっぽい。

FetchContent_MakeAvailable(mylib)

FetchContent #overview - CMake Documentation

threadライブラリを利用する場合

# pthreadを使う
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)

add_executable(main main.c)
target_link_libraries(main
    Threads::Threads
)

threadライブラリをリンクしてmainをコンパイルしたい場合は、上記のようにすればいい。
こういった有名なライブラリに関しては、CMakeであればfind_packageを用いて簡単に読み込むことが出来るようになっている。
threadライブラリが見つかった場合はターゲットとしてThreads::Threadsが読み込まれるので、target_link_librariesThreads::Threadsを指定すればリンクできる。
また、コンパイル時に-pthreadオプションをつけたい場合はset(THREADS_PREFER_PTHREAD_FLAG ON)とすると良い17
FindThreads - CMake Documentation
cmake and libpthread - stack overflow

おわりに

柔軟に記述出来る反面、どう書くのが良いのか分かりにくいのがCMakeの難しいところで、そういったポイントを丁寧にカバーしてみました。
ここまでの手順を追えば、CMakeの基本的な使い方は十分だと思います。後はそのプロジェクト固有の処理や、CMakeはマルチプラットフォームですので、OSごとの分岐処理などを使いこなせれば最高だと思います。(ここが一番むずかしい…。)

最後になりましたが、:qiita:Qiita10周年おめでとうございます:cracker: :cracker: :cracker:
これからもよろしくお願いします:qiitan: :qiitan: :qiitan:

  1. 例えば、FetchContentを見てみると、FetchContent_MakeAvailable を使えば1つのコマンドで出来ると書いてあるが、「CMake 3.14以前をサポートするなら、次のようにする必要がある」という文面とともに、例が記載されている。FetchContent #command:fetchcontent_populate - CMake Documentation

  2. ようするに、makemake allとした時と同じ、ということ。生成されたMakefileを見てみると、default_target: mainという記述が見つかると思う。また、そのmainターゲットの詳細はmain: cmake_check_build_system ...に書かれている。

  3. 伝われ...!

  4. CMakeの使い方(その1)#静的共有ライブラリの指定について(中級者以上) - Qiitaに書いてあった

  5. CMakeが持っている変数の一覧を説明付きで表示するコマンドであるcmake .. -LHを叩いた時などに、オプションの説明が利用される。

  6. というかこうした方がメンテしやすそうだし、こうした方がいい。

  7. file - CMake Documentation #Filesystem

  8. aux_source_directory - CMake Documentation

  9. ビルド環境を再生成するタイミングを意識するのが嫌か、手作業でファイルを追加するのが嫌か、どっちを取るか?という話。これくらいなら、まだ手作業でファイルを追加する方が楽。めちゃめちゃ大きいプロジェクトだと実際どうしているか、はまだコードを眺めていないので、どうしているかは分からない。分かったら教えて。

  10. ちゃんと依存関係解決しないと、どのオブジェクトファイルを再生成する必要があるのか分からんはず。実は、ちゃんと依存関係が解決されていて、build/lib/mylib/CMakeFiles/mylib.dir/depend.makeを見てみると、その中に各オブジェクトファイルをtargetとして、そのprereqにヘッダファイルがリストアップされているのが分かると思う。g++はMake用の依存関係を出力できるので、g++ -MM *.cpp > depend.makeとして、depend.makeをincludeすることで利用できる。

  11. sudo make installのように、sudoをつけないとpermission deniedする。prefixを上書きする理由は、sudoを使うのを避けるためではなくて、単純に配置する場所を変えたかっただけ、という好みの話もある。

  12. install - CMake DocumentationPUBLIC_HEADERを使っているサンプルを見てほしい。

  13. 実行ファイルを生成するのは、いっちばん基本的な話になると思うんだけど、思った以上に最後になってしまった…。

  14. CMakeLists.txtの中でDEBUGと一致するか、で利用していたらどうするんだ、という話もあるかもしれないが、これの解決方法としては何かに統一しておくのが良いということになると思う。

  15. いつから使えるようになったの?

  16. 例えば、FetchContent_Populate(MyLib)としても、mylib_POPULATEDmylib_SOURCE_DIRmylib_BINARY_DIRが定義される。

  17. This variable has no effect if the system libraries provide the thread functions, i.e. when CMAKE_THREAD_LIBS_INIT will be empty.って書いてあるののは実はよく分かってない。

87
108
1

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
87
108

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?