みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

CMakeLists 覚書 (2020年版)

はじめに

以前にも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では、9811141720が指定できる。 Ubuntu 18.04で使用されているcmake 3.10では17まで指定できる。

CMAKE_CXX_STANDARD_REQUIREDONにすると対応している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_librarySHAREDを設定する。

project(sayhello)

add_library(${PROJECT_NAME} SHARED
    hello.cpp
)

もしくはBUILD_SHARED_LIBSONにすれば、add_libraryにSHAREDの指定を省略できる。

project(sayhello)

set(BUILD_SHARED_LIBS ON)

add_library(${PROJECT_NAME}
    hello.cpp
)

この例ではlibsayhello.soが生成される。

ライブラリの生成(スタティック)

ライブラリをビルドする場合はadd_librarySTATICを設定する。

project(sayhello)

add_library(${PROJECT_NAME} STATIC
    hello.cpp
)

もしくはBUILD_SHARED_LIBSONにしなければ、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の時代からいろいろと進化していた。