CMakeを使えるようになろう!
-
2023年11月27日
はじめに
こんにちは!
新卒クライアントエンジニアのKと申します。
あるツールをビルドする際、MacではXcodeを使用していたのですが、Linuxでは使えないとのことでCMakeでビルドすることになりました。
私はCMakeについては何も知らなかったので、覚えるのに一苦労しました。
同じようにCMake初心者の方の助けになれば嬉しいです、よかったら参考にしてください。
CMakeとは
CMakeはコンパイラに依存しないビルド自動化ツールです。
OSによって用意しないといけないプロジェクトをこれ一つで作成し、ビルドすることができます。
基本的な使用方法
CMakeListsの作成
CMakeでビルドを行うには、プロジェクトディレクトリと同じパスにCMakeLists.txtを作成する必要があります。
プロジェクト作成で最低限必要なものは以下となります。
# CMakeのバージョン
cmake_minimum_required(VERSION 3.6)
# アプリ名をセット
set(APP_NAME main)
project(${APP_NAME})
# ソースコード追加
set(MAIN_SOURCE Main.cpp)
# 実行ファイル作成
add_executable(${APP_NAME} ${MAIN_SOURCE})
# 実行ファイルの場所を設定
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/build/bin)
ビルド前に
ビルドの前にCMakeListsを使ってCMakeプロジェクトを作成します。
プロジェクトルートで
$ mkdir build && cd build
を打って、ビルド用のディレクトリを作成します。そして、ディレクトリに入ったら、
$ cmake ..
を打ち、プロジェクトを作成します。
CMakeListsを対象にしているので親ディレクトリを指定しています。
Unix系ですと、これでMakefileが作成されます。
ビルド方法
そのまま同じディレクトリ内で、
$ cmake --build .
を打ち、ビルドします。
実行方法
build/binに実行ファイルが作成されたので、
$ cd bin
$ ./main
で実行しましょう。
複数のディレクトリを扱う場合
複数のディレクトリの構成例です。
.
├── Main.cpp
└── sub
├── Sub.cpp
└── Sub.hpp
上のようなsubディレクトリにあるsub.cppなどのソースコードも一緒にコンパイルしてみましょう。
一つのCMakeListsを使用する場合
先ほどのCMakeListsを使ってsub.cppも追加してみましょう。
以下のような構成図になるかと思います。
.
├── CMakeLists.txt
├── Main.cpp
└── sub
├── Sub.cpp
└── Sub.hpp
# CMakeのバージョン
cmake_minimum_required(VERSION 3.6)
# アプリ名をセット
set(APP_NAME main)
project(${APP_NAME})
# ソースコード追加
set(MAIN_SOURCE Main.cpp)
set(SUB_SOURCE sub/Sub.cpp)
# 実行ファイル作成
add_executable(${APP_NAME} ${MAIN_SOURCE} ${SUB_SOURCE})
# 実行ファイルの場所を設定
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/build/bin)
add_subdirectoryを使用する場合
プロジェクト以下のディレクトリにあるソースコードもコンパイルしたいとき、一つのCMakeLists.txtでも可能ですが、ソースが大きくなって見づらいですね。
そんな時はadd_subdirectryを使用して、プロジェクト以下のディレクトリごとにCMakeLists.txtを分けることができます。
以下が例となります。
.
├── CMakeLists.txt
├── Main.cpp
└── sub
├── CMakeLists.txt
├── Sub.cpp
└── Sub.hpp
# CMakeのバージョン
cmake_minimum_required(VERSION 3.6)
# アプリ名をセット
set(APP_NAME main)
project(${APP_NAME})
## ソースコード追加
set(MAIN_SOURCE Main.cpp)
# subディレクトリにあるCMakeListsを読み込む
add_subdirectory(sub)
# 実行ファイル作成
add_executable(${APP_NAME} ${MAIN_SOURCE})
# 実行ファイルの場所を設定
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/build/bin)
# ライブラリのリンク
target_link_libraries(${APP_NAME} PRIVATE sub)
add_subdirectoryで任意のディレクトリのパスを指定しています。
これによって、指定のディレクトリのCMakeListsのプロジェクトが作成され、使用可能になります。
また、subのCMakeListsでは親のCMakeListsで宣言した変数を使用することができます。
subターゲットをライブラリとしてリンクすることで、mainターゲットからSubのソースコードを参照できるようになります。
# CMakeのバージョン
cmake_minimum_required(VERSION 3.6)
# ソースコード追加
set(SUB_SOURCE Sub.cpp)
# ソースコードをsubという名前に入れ、ライブラリとして作成
set(SUB_LIB sub)
add_library(${SUB_LIB} STATIC ${SUB_SOURCE})
プロジェクトルートのCMakeListsからsubディレクトリのソースを使いたいので、add_libraryでライブラリにしています。STATICを指定することで静的ライブラリにすることができます。
これで、親のターゲット(main)からライブラリとしてリンクすることで使用可能になります。
よく使用したもの
CMakeでよく使用したものを紹介します。
vcpkgについて
vcpkgとはC++のためのオープンソースのパッケージ管理ツールです。
指定のライブラリをインストール・ビルド・ライブラリ化を一気にやってくれます。
homebrewとかyumとかと似ていますね。あちらはシステムにライブラリなど置かれますが。
さらに、ツールチェインとして設定することで、find_packageで簡単にvcpkgでインストールしたライブラリを見つけることができます。
set(CMAKE_TOOLCHAIN_FILE ${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
ビルドについて
CMakeプロジェクトを作成する際、ビルドタイプを指定することができます。
デバッグ用
$ cmake .. -DCMAKE_BUILD_TYPE=Debug
リリース用
$ cmake .. -DCMAKE_BUILD_TYPE=Release
ヘッダーを探すとき
今のCMakeListsパスとは異なる場所のヘッダーを見つけるときは、
target_include_directoriesを使います。
target_include_directories(targetName ${headerName1} ...)
targetName・・・ヘッダーを探すときのターゲットを指定します
${headerName1}・・・ヘッダー名
target_include_directories(${APP_NAME} PRIVATE sub)
これをつける事によって、
#include "sub/Sub.hpp"
と、フルパスを指定していたのが、
#include "Sub.hpp"
とヘッダーの名前だけになり、シンプルにかけました!
ライブラリを探すとき
ライブラリを見つけるときは、
- find_package(おすすめ)
- target_link_directories
の2つの方法があります。
find_package
find_packageとは指定のライブラリを探し、インクルードパスや依存ライブラリ関係を自分で指定することなくリンクできる便利な関数です。
つまり、
- target_include_directories
- target_link_directories
- 依存ライブラリをtarget_link_librariesする
これらが必要なくなります。最高ですね。
find_packageは指定のライブラリを
- モジュールモード
- コンフィグモード
の2つのモードで設定ファイルを探していきます。
設定ファイルはパッケージ管理システムでインストールした時に一緒に入っていることが多いです。pvpkgとかyumとかです。
1. モジュールモード
モジュールモードではFind<PackageName>.cmakeというファイルを探します。
CMAKE_MODULE_PATHにパスを設定することで、CMakeが見つけることができます。
以下、使用例です。
find_package(packageName REQUIRED)
REQUIREDは見つからなかったら、エラーを出すオプションです。
2. コンフィグモード
コンフィグモードでは<PackageName>Config.cmakeというファイルを探します。
CMAKE_PREFIX_PATHにパスを設定することで、CMake側から見つけることができます。
以下、使用例です。
find_package(packageName CONFIG QUIET)
CONFIGをつけるかで、モードが変わります。
QUIETは見つけられなくてもエラーを出しません。見つからないとリンクとかできないので気をつけてください。
target_link_directories
target_link_directories(targetName ${libName1} ...)
targetName・・・ライブラリを探すときのターゲットを指定します
${libName1}・・・ライブラリ名。ターゲットも指定できます
target_link_directoriesを使用するだけだと、リンクや依存関係ができてなかったり、ヘッダーのパスが見つからないので設定する必要があります。
find_packageで見つけられるなら、target_link_directoriesや上記など設定が必要ありません。
モジュール、コンフィグファイルがない場合は、自分で書く必要があります。恐ろしい…
つまづいた点
同じ名前のライブラリが複数ある
find_packageを使って指定のライブラリを見つけるのですが、
yum(Linuxを使用)でインストールしたライブラリと、vcpkgでインストールしたライブラリの2つが混在してしまい、リンクがおかしくなりました(ツールチェインにvcpkgを設定せずにvcpkgのライブラリを使おうとしているからかも)。
yumでインストールしたライブラリはバージョンが低く、vcpkgでインストールしたライブラリを見つけたいと思っていました。
モジュールやコンフィグファイルを見ていたら探すときのヒントとして、 <PackageName>_ROOT_DIRを参照していそうな感じだったので、
set(<PackageName>_ROOT_DIR ${LIB_PATH})
このように、ライブラリがある場所をセットしたらvcpkgの方を見つけてくれました。
さいごに
CMakeは想像以上に使いこなすのが難しいですね。
使いこなせれば便利だということは分かりました。vimみたいですね。
vcpkgとfind_packageがあるからぎりぎり作成できた気がします。これなかったらって思うと…笑
ビルドできるまで苦労しましたが、達成感はすごかったです。詳しい方いれば教えて下さい笑
最後まで見てくださり、ありがとうございました!