はじめに
以前にもCMakeLists.txtの書き方をまとめたが内容が古くなったので改めて調べ直した。当時の理解が甘かったところやCMakeが3になってできることなども対応した。
Ubuntu 20.04では3.16に対応しているため、そのバージョンで使用できる機能を紹介する。
公式サイト
cmakeの公式サイトではバージョンごとに使用できる機能が検索できるようになっている。
使ってるバージョンのcmakeで特定の機能が使用可能かを確認したい場合はここで確認すると良い。
プロジェクト名の設定
project
でプロジェクト名を設定すると、プロジェクト名を${PROJECT_NAME}
で参照できるようになる。
cmake_minimum_required(VERSION 3.16) project(hello) add_executable(${PROJECT_NAME} main.cpp)
C++バージョンの設定
CMAKE_CXX_STANDARD
で使用するC++のバージョンを指定できる。cmake 3.16では、98
、11
、14
、17
、20
が指定できる。
Ubuntu 18.04で使用されているcmake 3.10では17
まで指定できる。
CMAKE_CXX_STANDARD_REQUIRED
をON
にすると対応しているC++バージョンが使用できない環境ではエラーにすることができる。
OFF
の場合は古いバージョンにフォールバックされる。
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)
パッケージの追加
外部のライブラリをリンクしたい場合はパッケージを追加する必要がある。
ここでいうパッケージはapt
などのパッケージシステムのことではなく古くはconfiugre
スクリプトなどで参照される開発用パッケージのこと。
これらはcmakeでは*.cmake
によって定義される。
昔からあるpkg-config
では*.pc
によって定義されている。
find_packageによるパッケージの追加
*.cmake
が提供されているライブラリはfind_package
でプロジェクトに追加することができる。
この例ではabseil-cppを追加している。
REQUIRED
を指定すると見つからない場合はエラーになる。
パッケージが見つかったかどうかは<プロジェクト名>_FOUND
という変数で確認することができるが、REQUIRED
をしている場合はパッケージが見つからなければcmakeが失敗するため、
このチェックを省くことができる。
find_package(absl REQUIRED) # 見つからないとエラー
逆に見つからなくても処理を続行したい場合はREQUIRDをつけずに実行し、必要に応じて下記のように処理を分岐する。
find_package(absl) if (absl_FOUND) message("abseil is found!") else() message("abseil is not found!") # 見つからなくても処理は継続する endif()
COMPONENTS設定
OpenCVやBoost、Qt5などの規模の大きいライブラリではfind_packageでCOMPONETNS
によってコンポーネントを指定すると、そのパッケージの中の必要な機能だけをプロジェクトに追加することができる。
特にQt5では必ず1つ以上のコンポーネントを指定する必要がある。
find_package(OpenCV COMPONENTS core) find_package(Boost 1.74 COMPONENTS date_time thread) find_package(Qt5 COMPONENTS Quick REQUIRED) target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBRARIES} # OpenCV::coreは指定できない ${Boost_LIBRARIES} # Boost::date_timeのように個別でもOK Qt5::Quick # Qt5_LIBRARIESは空となっているため実質的に指定できない )
インクルードとライブラリの設定
基本的にはインクルードに関しては自動的に設定されるため特にCMakeLists.txtで設定する必要はない。これについては自動生成されるflags.make
で確認することができる。
ライブラリに関してはtarget_link_libraries
で<プロジェクト名>_LIBRARIES
を追加すれば良いケースがほとんどだが、
Qt5などはQt5::Quick
などコンポーネントを個別に追加する必要があるパッケージも存在する。
Qt5に関してはQt5内でのコンポーネント間の依存関係を自動的に解決してくれるようになっている。
モジュールモードとコンフィグモード
find_packageのモジュールモード、コンフィグモードの違いに関しては実践C++応用講座CMake編 第10回 find_packageの仕組みと使い方が詳しい。
簡単に言うと、find_moduleでパッケージの検索に必要な*.cmake
を提供しているのがCMake側なのがモジュールモードで、ライブラリ側なのがコンフィグモードということらしい。
pkg-configによるパッケージの追加
pkg-configコマンドによるパッケージの追加を有効化するにはfind_package(PkgConfig)
をする必要がある。
パッケージを追加するにはpkg_check_modules
によってパッケージを指定する。こちらはfind_packageによるパッケージの追加と異なり、インクルードとライブラリの設定は手動で行う必要がある。
インクルードパスの設定は<pkg_check_modulesの第1引数>_INCLUDE_DIRS
で参照することができる。
ライブラリの設定も同様に<pkg_check_modulesの第1引数>_LIBRARIES
で参照できる。
find_package(PkgConfig REQUIRED) # pkg-configを有効化 pkg_check_modules(STLINK REQUIRED stlink) # stlink.pcが提供されている # "<pkg_check_modulesの第1引数>_INCLUDE_DIRS"でアクセスできる include_directories(${STLINK_INCLUDE_DIRS}) ... add_executable ... # "<pkg_check_modulesの第1引数>_LIBRARIES"でアクセスできる target_link_libraries(${PROJECT_NAME} ${STLINK_LIBRARIES} )
インクルードディレクトリの設定
include_directories
によって追加する。
# 2回目以降は追加となる include_directories(${STLINK_INCLUDE_DIRS}) # pkg-configの変数 include_directories(${PROJECT_SOURCE_DIR}/include) # 自プロジェクトのincludeを追加 # 1回で複数追加することもできる include_directories( ${STLINK_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR}/include )
ライブラリの追加
target_link_libraries
によって追加する。
# 2回目以降は追加となる target_link_libraries(${PROJECT_NAME} ${STILNK_LIBRARIES}) # pkg-configの変数 target_link_libraries(${PROJECT_NAME} Qt5::Quick) # COMPONENTSの指定 target_link_libraries(${PROJECT_NAME} -lrt) # オプション直の指定も可能 # 1回で複数追加することもできる target_link_libraries(${PROJECT_NAME} ${STILNK_LIBRARIES} Qt5::Quick -lrt)
ライブラリの追加(static)
こちらもtarget_link_libraries
によって追加する。
ビルド済みのバイナリをリンクする場合は*.a
までのフルパスを指定する。
target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/prebuilt_lib/libsdi.a ${PROJECT_SOURCE_DIR}/prebuilt_lib/libgdi.a )
実行ファイルの生成
CMakeで実行形式を生成したい場合は、add_executable(<ターゲット名> <ソースファイル名>)
で設定する。
ソースファイルは暗黙に${PROJECT_SOURCE_DIR}
のものを参照する。
add_executable(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/hello.cpp ) # ${PROJECT_SOURCE_DIR}を省略しても同じ add_executable(${PROJECT_NAME} main.cpp )
add_executableは1つのCMakeLists.txtで複数記述できるが、同じターゲット名は指定できない。
# これはできない add_executable(${PROJECT_NAME} main.cpp) add_executable(${PROJECT_NAME} print.cpp)
異なるプロジェクト名であれば指定できる。
# これはOK add_executable(${PROJECT_NAME} main.cpp) add_executable(subtool print.cpp)
アセンブラの有効化
add_executableにhello.S
などアセンブラのソースファイルを登録したい場合は、予め有効化しておく必要がある。
# アセンブラを有効化 enable_language(ASM) add_executable(${PROJ_NAME} ${PROJECT_SOURCE_DIR}/main.c ${PROJECT_SOURCE_DIR}/hello.S )
サブディレクトリの登録
ライブラリなどのサブモジュールを一緒にビルドする場合、1つのCMakeLists.txtに全て定義することもできるが、 従来のMakefileのようにディレクトリごとにCMakeLists.txtを置くこともできる。
その場合はadd_subdirectory(ディレクトリ名)
で登録する。
add_subdirectory(sayhello) add_subdirectory(saybye)
サブディレクトリでライブラリをビルドする場合はtarget_link_libraries
にサブディレクトリ名を指定する。
target_link_libraries(${PROJECT_NAME} sayhello)
これはサブディレクトリで生成するライブラリが動的でも静的でもおなじ。
ライブラリの生成
ライブラリをビルドする場合はadd_library
にSHARED
を設定する。
project(sayhello) add_library(${PROJECT_NAME} SHARED hello.cpp )
もしくはBUILD_SHARED_LIBS
をON
にすれば、add_libraryにSHAREDの指定を省略できる。
project(sayhello) set(BUILD_SHARED_LIBS ON) add_library(${PROJECT_NAME} hello.cpp )
この例ではlibsayhello.so
が生成される。
ライブラリの生成(スタティック)
ライブラリをビルドする場合はadd_library
にSTATIC
を設定する。
project(sayhello) add_library(${PROJECT_NAME} STATIC hello.cpp )
もしくはBUILD_SHARED_LIBS
をON
にしなければ、add_libraryにSTATICの指定を省略できる。
project(sayhello) # set(BUILD_SHARED_LIBS ON) 省略時のadd_libraryのデフォルトはSTATIC add_library(${PROJECT_NAME} hello.cpp )
この例ではlibsayhello.a
が生成される。
ビルドタイプの設定
デバッグ版やリリース版などで最適化オプションを切り替えるにはCMAKE_BUILD_TYPE
を設定する。
ビルドタイプには下記の4種類が設定できる。
Build Type | C++ optio variables | 概要 |
---|---|---|
Debug | CMAKE_CXX_FLAGS_DEBUG | デバッグ |
Release | CMAKE_CXX_FLAGS_RELEASE | リリース |
RelWithDebInfo | CMAKE_CXX_FLAGS_RELWITHDEBINFO | デバッグ情報つきリリース |
MinSizeRel | CMAKE_CXX_FLAGS_MINSIZEREL | コードサイズ最適化 |
CMAKE_CXX_FLAGS_xxxの他にも下記のようなものが定義されている。
- CMAKE_C_FLAGS_xxx
- CMAKE_EXE_LINKER_FLAGS_xxx
- CMAKE_MODULE_LINKER_FLAGS_xxx
- CMAKE_SHARED_LINKER_FLAGS_xxx
- CMAKE_STATIC_LINKER_FLAGS_xxx
ビルドタイプをDebug
に設定する場合は下記のようにする。
set(CMAKE_BUILD_TYPE Debug)
Debugモードの時のC++のビルドオプションを変更したい場合は下記のようにする。
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
CXXFLAGSなどの設定
CXXFLAGS
などの一般的なに変数に関してはCMAKE_CXX_FLAGS
のように予め定義してある。
下記のようにset
で設定できる。
set(CMAKE_CXX_FLAGS "-D__ARMV8")
既存の変数に追加したい場合はいくつか書き方があるが下記のようにすると良さそう。
# 自分を展開し再代入する set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__ARMV8") # 文字列として追加する(先頭の空白は必須) string(APPEND CMAKE_CXX_FLAGS " -D__ARMV8")
list(APPEND ...)
は:
区切りとなるためこのケースでは使用できない。
定義済みの変数は下記の通り。
- CMAKE_C_FLAGS(CFLAGS)
- CMAKE_CXX_FLAGS(CXXFLAGS)
- CMAKE_CUDA_FLAGS(CUDAFLAGS)
- CMAKE_Fortran_FLAGS(FFLAGS)
インストール
make install
など、インストールのルールを追加するにはinstall
で設定する。
いろいろ細かく設定できるが生成された実行ファイルをインストールするだけなら下記のような感じでよい。
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
インストール先はCMAKE_INSTALL_PREFIX
と結合される。デフォルトでは/usr/local
に設定されているため、この場合は/usr/local/bin
にインストールされることになる。
compile_commands.jsonを生成
compile_commands.json
はclangdにソースコードをコンパイルをする際のコンパイルオプションを知らせるためのもので、
これがないと、外部ライブラリを使用する場合などに補完が残念になる。
compile_commands.jsonについてはClangdでC++ソースコードを補完する際にコンパイルオプションを指定する方法が詳しい。
cmakeでcompile_commands.jsonを生成するにはCMAKE_EXPORT_COMPILE_COMMANDS
をONに設定する。
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
このようにCMakeLists.txtで設定してもよいが、毎回生成する必要は無いので下記のようにコマンドラインでしていするのでも良い。
$ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmakeを実行したディレクトリに生成されるので、必要に応じてファイルを移動するなりシンボリックリンクを貼るなりする。
まとめ
CMakeが3になってしばらく経ったが、2の時代からいろいろと進化していた。