CMakeを使ってビルドする(1)

2022-03-10C++,CMake

今まで、プログラムをビルドするのにmakeを使用していましたが、GCCとVC++でそれぞれ異なるMakefileを用意しなければならないのでなかなか面倒でした。CMakeを使うことで、別々のMakefileを用意する手間を省いて共通化できそうだったので、試しに使ってみました。そのメモです。

CMakeとは?

CMakeは、CMakeLists.txtという設定ファイルにビルド用の設定を記述しておくことで、複数の異なるビルドシステム用の設定ファイル(make用のMakefileやVisual Studio用の*.slnなど)の生成を自動で行ってくれるツールです。DebugビルドとReleaseビルドを切り替えたり、使用するコンパイラを切り替えることも可能です。

CMakeはout-of-sourceビルドというものを基本としており、ソースと異なるディレクトリ内でビルドすることが推奨されています。一般的には、プロジェクトのルートにビルド用のディレクトリを作成し、そのディレクトリに移動してからcmakeすることが多いようです。リビルドする際には、ビルド用のディレクトリ自体を削除します。

CMakeLists.txtを作成する

使用するCMakeのバージョン

> cmake --version
cmake version 3.19.3

やりたいこと

まずは、次ができることを目指しました。

  • WindowsでC++のプログラムをビルドする
  • make用のMakefileを生成する
  • DebugビルドとReleaseビルドで異なるコンパイルオプションを設定する
  • コンパイラはGCCとVC++(cl.exe)の2つに対応させる

CMakeもなかなか複雑なので、まずは簡単なディレクトリ構成から始めてみました。

想定しているディレクトリ構成

プロジェクトのディレクトリ直下にソースが置かれている単純なケースを考えます。

project1/
  |- test1.h
  |- test1.cpp
  |- CMakeLists.txt
  |- build/ (ビルド用のディレクトリ)

サブディレクトリが存在する場合は、こちらもご覧ください。

簡単なCMakeLists.txt

実際にCMakeの設定ファイルであるCMakeLists.txtを作成します。まずはプログラムをビルドするための簡単な設定を行います。

cmake_minimum_required(VERSION 3.1)
project(cmake_test_1 CXX)
aadd_executable(cmake_test test1.cpp)

1行目cmake_minimum_required()

1行目ではこのプロジェクトで必要とするCMakeのバージョンを指定しています。これより古いバージョンのCMakeを使用した場合、ビルドシステム用の設定ファイルを生成できません。

2行目project()

2行目ではプロジェクト名を指定しています。ここで指定したプロジェクト名は、Visual Studio(MSBuild)用のソリューションファイル名で使われるようです。
2つ目のCXXはこのプロジェクトで使用される言語がC++であることを示します。省略した場合、CMakeはCとC++両方に対応しようとします。

ちなみに、project()を省略すると、プロジェクトの開発者向けに下記の様な警告が出ます。

CMake Warning (dev) in CMakeLists.txt:
  No project() command is present.  The top-level CMakeLists.txt file must 
  contain a literal, direct call to the project() command.  Add a line of 
  code such as

    project(ProjectName)

  near the top of the file, but after cmake_minimum_required().

  CMake is pretending there is a "project(Project)" command on the first  
  line.
This warning is for project developers.  Use -Wno-dev to suppress it.

これによると、project()は省略せずにファイルの先頭付近、かつその位置はcmake_minimum_required()の後に記述しなければならないようです。
プロジェクト名は省略せずにつけておきましょう。

3行目add_executable()

3行目で実行ファイルの生成に関する設定を行っています。次のような書式で記述します。

add_executable(ターゲット名 [EXCLUDE_FROM_ALL] 依存ファイル ...)

ターゲット名が生成する実行ファイルの名前に相当します。Windowsであれば勝手に.exeを付加してくれます。

依存ファイルにはビルドに必要なソースファイルなどを指定します。複数指定可能。ソースファイルでincludeしているヘッダファイルは、自動的に依存関係に追加されるので省略できます。

EXCLUDE_FROM_ALLはデフォルトターゲット(all)にターゲット名を含めないときに指定します。

Makefileの生成とビルド

Makefileの生成

先のCMakeLists.txtを用いて、WindowsでMakefileを生成するには、buildディレクトリに移動したあとでcmakeを実行します。

project> cd build
project\build> cmake -G "MinGW Makefiles" ..

Windowsでは-G "MinGW Makefiles"をつけないと、初期設定であるVisual Studio(MSBuild)用の設定ファイルが生成されます。Linuxではmakeが初期設定なので、このようなオプションは不要です。

-G "MinGW Makefiles"をつけた場合、コンパイラはGCCが初期設定として選択されます。コンパイラをVC++(cl.exe)に切り替えるときには-Dオプションを使って、変数CMAKE_CXX_COMPILERに値を設定します。

cmake -DCMAKE_CXX_COMPILER=cl -G "MinGW Makefiles" ..

ビルド

生成されたMakefileを使ってビルドするには、直接makeを使う方法のほか、cmake--buildオプションを付けて呼び出す方法もあります。

cmake --build Makefileのディレクトリ [--target ターゲット名]

--targetadd_executable()で指定したターゲット名を指定します。省略した場合はallがターゲット名として指定されます。EXCLUDE_FROM_ALLを指定していなければ、allターゲットに含まれているはずなので、省略して良いです。

ビルドタイプの切り替えとコンパイルオプションの設定

ビルドタイプの切り替え

DebugビルドやReleaseビルドなどのビルドタイプを切り替えるには、次のように-Dオプションを使って変数CMAKE_BUILD_TYPEにビルドタイプを指定します。

使用可能な値として、Debug, Release, RelWithDebInfo, MinSizeRelの4種類があらかじめ用意されていますが、独自に定義も可能なようです。

cmake -DCMAKE_BUILD_TYPE=ビルドタイプ -G "MinGW Makefiles" ..

CMakeではコンパイルオプションを保持する変数として、共通で使われるCMAKE_CXX_FLAGSの他に、各ビルドタイプに対応する変数CMAKE_CXX_FLAGS_DEBUG, CMAKE_CXX_FLAGS_RELEASE, CMAKE_CXX_FLAGS_RELWITHDEBINFO, CMAKE_CXX_FLAGS_MINSIZERELが用意されていて、それぞれOSやコンパイラに応じた初期値が与えられています。

各ビルドタイプのコンパイルオプション初期値を調査したものをここに示しました。

コンパイルオプションの設定

コンパイルオプションの設定は、上記の変数を直接変更するのではなく、target_compile_options(), target_compile_features(), target_compile_definitions()等を使って設定することが推奨されています。

コンパイルオプションの追加
target_compile_options(ターゲット名 [BEFORE] PRIVATE|PUBLIC|INTERFACE オプション ...)

コンパイルオプションを追加します。次のようにコンパイラに渡すオプション文字列をそのまま設定します。

target_compile_options(cmake_test PRIVATE -Wall -Wextra)

BEFOREを指定した場合、コンパイルオプションを最後に付加するのではなく、最初に挿入します。

言語機能の指定
target_compile_features(ターゲット名 PRIVATE|PUBLIC|INTERFACE オプション)

コンパイルする際に必要な言語機能を指定します。C++の言語仕様と値、コンパイルオプションの対応は以下の表を参照してください。

仕様 GCCオプション VC++オプション
C++98 cxx_std_98 -std=gnu++98 (なし)
C++11 cxx_std_11 -std=gnu++11 (なし)
C++14 cxx_std_14 -std=gnu++14 /std:c++14
C++17 cxx_std_17 -std=gnu++17 /std:c++17
C++20 cxx_std_20 -std=gnu++2a /std:c++latest

GCCの場合、-std=c++xxではなくGNU拡張の有効な-std=gnu++xxが指定されます。GNU拡張を無効にする場合には、CXX_EXTENSIONSというプロパティをあらかじめOFFにする必要があります。

set_property(TARGET cmake_test PROPERTY CXX_EXTENSIONS OFF)
target_compile_features(cmake_test PRIVATE cxx_std_17)

cxx_std_xxの代わりに、必要とする言語の機能を指定することも可能です。たとえば、cxx_constexprと指定することで、その機能をサポートしている仕様をオプションとして設定してくれます。

どのような機能が指定可能かは、公式ドキュメントを参照してください。

プリプロセッサ定義の追加・削除
target_compile_definitions(ターゲット名 PRIVATE|PUBLIC|INTERFACE オプション ...)

コンパイル時オプションとして、プリプロセッサの定義を追加または削除します。たとえばFOOを定義する場合は、

target_compile_definitions(cmake_test PRIVATE FOO)

逆にFOOを削除する場合は、

target_compile_definitions(cmake_test PRIVATE -DFOO)

とします。-Dと削除するプリプロセッサの間にスペースを入れてはいけません。

PRIVATE, PUBLIC, INTERFACE

これら3種類のキーワードはオプションの有効な範囲を示すために使われます。ターゲット自身に有効な設定の場合にはPRIVATEPUBLICを、ターゲットに依存する他のターゲットに有効なものはINTERFACEPUBLICを指定します。

キーワード ターゲット自身のビルドに必要 ターゲットに依存するターゲットのビルドに必要
PRIVATE ×
PUBLIC
INTERFACE ×

自分自身のビルドに使うのであれば、PRIVATEを使用します。

Debugビルド用とReleaseビルド用のコンパイルオプションの設定

Debugビルド用とReleaseビルド用のコンパイルオプションを設定してみます。今回は、次のような内容とします。

  1. Debugビルド
    1. プリプロセッサの定義にUSE_HOGEを指定する
  2. Releaseビルド
    1. 追加なし
  3. 共通
    1. GCCでは警告オプション-Wall-Wextraを付加する
    2. VC++では警告オプション/W4を付加する
    3. C++17に対応する

作成したCMakeLists.txtは次の通りです。

cmake_minimum_required(VERSION 3.1)
project(cmake_test_1 CXX)
add_executable(cmake_test test1.cpp)

target_compile_options(
  cmake_test PRIVATE
  $<$<CXX_COMPILER_ID:MSVC>: /W4>
  $<$<CXX_COMPILER_ID:GNU>: -Wall -Wextra>
)
target_compile_features(cmake_test PRIVATE cxx_std_17)
target_compile_definitions(
  cmake_test PRIVATE
  $<$<CONFIG:Debug>: USE_HOGE>
)

唐突に見慣れない$<...>が出てきましたが、これはGenerator-Expressionsというもので、ビルドタイプやコンパイラによって設定する値を変更するための仕掛けです。

Generator-Expressionsについては、エントリを改めて説明したいと思います。

まとめ

プロジェクトのディレクトリ直下にソースが置かれている単純なケースで、GCCとVC++の2つのコンパイラに対応し、DebugビルドとReleaseビルドで異なるコンパイルオプションを設定できるようなCMakeLists.txtを作成してみました。

WindowsでMinGW用のMakefileを生成するには、コマンドラインで次を入力します。GCCの場合には-DCMAKE_CXX_COMPILERは不要です。

project\build> cmake -DCMAKE_CXX_COMPILER=コンパイラ -DCMAKE_BUILD_TYPE=ビルドタイプ -G "MinGW Makefiles" ..

(参考)各コンパイラにおけるビルドタイプごとのオプション初期値

GCC(MinGW含む)

GCCの場合-Wall等の警告関係のオプションが設定されていないので注意。

変数 オプションの初期設定
CMAKE_CXX_FLAGS (なし)
CMAKE_CXX_FLAGS_DEBUG -g
CMAKE_CXX_FLAGS_RELEASE -O3 -DNDEBUG
CMAKE_CXX_FLAGS_RELWITHDEBINFO -O2 -g -DNDEBUG
CMAKE_CXX_FLAGS_MINSIZEREL -Os -DNDEBUG
VC++
変数 オプションの初期設定
CMAKE_CXX_FLAGS /DWIN32 /D_WINDOWS /W3 /GR /EHsc
CMAKE_CXX_FLAGS_DEBUG /MDd /Zi /Ob0 /Od /RTC1
CMAKE_CXX_FLAGS_RELEASE /MD /O2 /Ob2 /DNDEBUG
CMAKE_CXX_FLAGS_RELWITHDEBINFO /MD /Zi /O2 /Ob1 /DNDEBUG
CMAKE_CXX_FLAGS_MINSIZEREL /MD /O1 /Ob1 /DNDEBUG
Clang(Windows)
変数 オプションの初期設定
CMAKE_CXX_FLAGS (なし)
CMAKE_CXX_FLAGS_DEBUG -g -Xclang -gcodeview -O0 -D_DEBUG -D_DLL -D_MT -Xclang –dependent-lib=msvcrtd
CMAKE_CXX_FLAGS_RELEASE -O3 -DNDEBUG -D_DLL -D_MT -Xclang –dependent-lib=msvcrt
CMAKE_CXX_FLAGS_RELWITHDEBINFO -O2 -g -DNDEBUG -Xclang -gcodeview -D_DLL -D_MT -Xclang –dependent-lib=msvcrt
CMAKE_CXX_FLAGS_MINSIZEREL -Os -DNDEBUG -D_DLL -D_MT -Xclang –dependent-lib=msvcrt
Clang(Linux)

Linux版のClangはWindows版と比べるとシンプルで、GCCと同じ。

変数 オプションの初期設定
CMAKE_CXX_FLAGS (なし)
CMAKE_CXX_FLAGS_DEBUG -g
CMAKE_CXX_FLAGS_RELEASE -O3 -DNDEBUG
CMAKE_CXX_FLAGS_RELWITHDEBINFO -O2 -g -DNDEBUG
CMAKE_CXX_FLAGS_MINSIZEREL -Os -DNDEBUG

C++,CMake

Posted by izadori