あけましておめでとうございます。本年もよろしくお願い申し上げます。
ということで、この年末年始で久しぶりに焼酎を飲んだ田原です。

さて、CMakeはビルド・システム(makeのmakefileやVisual Studioの.projファイル)を生成します。それらを使ってビルドする際にコンパイラやリンカが呼び出されますし、時にはファイルをコピーすることなどもあります。それらのコンパイラ呼び出しやファイル・コピー等のコマンドの間には処理の順序の制約があります。ライブラリのビルドはそれを使う exeファイルより先にビルドする必要があるなどです。また修正されていないソース・ファイルはコンパイルを省略できます。今回は、これらのビルド・コマンドの実行順序を決め、ビルド・コマンドをスキップするかどうか判断するための依存関係について解説します。

1.ビルド時に実行するコマンドを出力するCMakeコマンド

ビルド時に実行されるCMakeコマンドは以下のものを使うことが多いので、今回はこれらについて解説します。

  1. add_executable
  2. add_library
  3. add_custom_target
  4. add_custom_command

1., 2., 3.の主な振る舞いを前回 説明しましたので、今回はadd_custom_commandについて解説します。

その前に、当講座では上記の各CMakeのコマンドのことを「CMakeコマンド」と呼び、これらのコマンドの中で定義されているビルド(コンパイルやリンク)コマンドやCOMMANDオプションで指定されたコマンドを「ビルド・コマンド」と呼ぶことにします。

2.ビルド・コマンド実行の可否を決定する依存関係(by ファイル更新日時)

後述する条件により、各CMakeコマンドが処理されます。その「処理される」際に、入力ファイルの更新日時が出力ファイルのそれより新しい場合、そのCMakeコマンドで定義されているビルド・コマンドが実行されます。これは、入力ファイルが修正された後まだビルド・コマンドが実行されていないので、そのビルド・コマンドが実行されるということなのです。

次のCMakeコマンドがこの依存関係を処理できます。

  1. add_executable
  2. add_library
  3. add_custom_command(OUTPUT …)

前回説明したようにadd_executableとadd_libraryの主な機能は、ソースを指定して実行形式をビルドするものでした。ソースが入力ファイル、実行形式ファイルやライブラリ・ファイルが出力ファイルとなります。(なお、ソースとして指定していなくてもソース・ファイルが#includeしているファイルも入力ファイルとして依存関係が自動的に処理されます。)

add_custom_commandは、ファイル生成機能ビルドイベントの2種類があります。後者は入力/出力ファイルがあったとしてもそれをCMakeに教えるパラメータがありませんので、ここで述べる依存関係の処理は行いません。

2-1.add_executableとadd_library

2つ目以降のパラメータで入力ファイルを指定します。(複数指定可能です。)
最初のパラメータで出力ファイル名が決定されます。(出力ファイルは1つだけ指定できます。)実際の出力ファイル名は使用するシステムによって異なります。add_executableは実行形式ファイル、add_libraryは静的リンク・ライブラリをビルドするためのビルド・コマンドを生成します。
例えば、下記CMakeコマンドそれぞれのシステム毎の入力ファイル名と出力ファイル名は下記のようになります。(この辺りCMakeがよしなに処理してくれるのでありがたいですね。)

add_library(lib lib.cpp)
add_executable(main main.cpp lib)
システム add_library add_executable
入力 出力 入力 出力
linuxのgcc lib.cpp liblib.a main.cpp main.cppとliblib.a
WindowsのVisual Studio lib.cpp lib.lib main.cpp main.cppとlib.lib

2-2. add_custom_commandのファイル生成機能

add_custom_commandはadd_custom_targetと似て非なるものです。add_custom_commandはビルド中に実行したいビルド・コマンドを定義しますが、ビルド・ターゲットにはなりません。大きく2つの機能があります。

まず、ファイル生成用の機能です。これはOUTPUTオプションにて指定します。この機能の最も重要なオプションは次の通りです。

  • OUTPUTオプションにて、出力ファイルを指定します。複数のファイルを指定できます。
  • MAIN_DEPENDENCYオプションにて、入力ファイルを指定します。
  • DEPENDSオプションにて、MAIN_DEPENDENCYと同様に入力ファイルを指定します。

MAIN_DEPENDENCYとDEPENDSはほぼ同じ機能です。MAIN_DEPENDENCYはVisual Studioでカスタム・コマンドがエラーになった場所を示すのに使われるようです。

公式のリファレンスに書かれていますが、こちらのフォーマットでは次のような Makefile定義を生成します。(なお、Makeターゲットにはならないようです。)

OUTPUT: MAIN_DEPENDENCY DEPENDS
        COMMAND

しかし、実際にやってみるとOUTPUTの方がMAIN_DEPENDENCYとDEPENDSより新しい場合でも、COMMANDが実行されました。gccのmakeとMS-Buildの両方でです。何か見落としがあるかも知れません。
なお、後述のサンプルのconfigure_fileは実行しても出力ファイルに変化がなければ上書きしませんので、この場合にはadd_custom_commandに依存しているビルド・コマンドは実行されません。この辺、公式のマニュアル通りには振る舞わないこと、および、振る舞いが複雑なため混乱してしまいます。。

次に、add_custom_commandはOUTPUTではなくTARGETを指定する形式もあります。こちらはビルド・イベントを定義します。指定したターゲットのビルドに対して、PRE_BUILDはコンパイル前に実行、POST_BUILDはコンパイル後に実行、POST_LINKはリンク後に実行されます。

3.CMakeコマンド処理の可否を決定する依存関係

ビルドする際に、ビルド・ターゲットを指定することができます。
Visual Studioの場合は、ソリューション・エクスプローラーからプロジェクト名を右クリックして「ビルド」した際のプロジェクトがビルド・ターゲットです。CMakeから –buildオプションでビルドする場合は、–targetオプションでターゲットを指定します。Makeコマンドでビルドする場合は、makeの次にターゲットを指定します。

例えば以下のようなイメージです。

> cmake --build . --target main
$ make main

そして、ターゲット指定を省略することもできます。(実際にはこのモードを使うことが多いと思います。)
この場合は、デフォルト・ターゲット(複数あることもあります。)がターゲットとなります。

これらのターゲットを定義するCMakeコマンドは次の通りです。

  1. add_executable
  2. add_library
  3. add_custom_target(add_custom_commandではないので注意)

全て第一引数に記述した名前がターゲットとなり、そのターゲット名を指定することが可能です。
そして、前回解説したようなルールでデフォルト・ターゲットが決定されます。
add_executableとadd_libraryは EXCLUDE_FROM_ALLやEXCLUDE_FROM_DEFAULT_BUILD(Visual Studio)を指定しなければデフォルト・ターゲットです。
add_custom_targetはALLを指定すればデフォルト・ターゲットになります。

そして、ビルド時に指定したターゲットが依存する他のCMakeコマンドもビルド時に処理されます。
この依存の仕方に2種類あります。

3-1. ファイルを媒体とする暗黙的な依存関係

あるCMakeコマンドの入力ファイルを生成する他のCMakeコマンドが有った場合に、自動的に成立する依存関係です。
例えば、add_libraryでビルドしたライブラリをリンクする実行形式をadd_executableで定義した場合、この add_executable は add_library に依存しています。
この依存関係が成立する時、次の効果を生じます。

  1. 依存される方のCMakeコマンドは、それに依存したCMakeコマンドより先に処理されます
    これにより、依存される側(上記の場合add_library)が先に処理されるのでライブラリが先にビルドされ、依存している側(add_executable)はその後に処理されるのでライブラリを問題なくリンクできます。

  2. CMake生成時、他のCMakeコマンドが出力するファイルがなくても依存する側のCMakeコマンドはエラーにならない。
    通常、入力ファイルが存在しない場合、CMake生成時に当該CMakeコマンドは「Cannot find source file」エラーになります。しかし、その入力ファイルが他のCMakeコマンドの出力ファイルの場合は、ビルドされるまで存在しなくても可笑しくないのでエラーになりません。

  3. 依存したCMakeコマンドがビルド・ターゲットの場合、依存される方のCMakeコマンドも処理されます
    自動的に依存関係を解決して必要なCMakeコマンドが処理されます。ビルドする際にビルドが必要なコマンドだけが処理対象になるので、高速にビルドされデバッグが効率的になります。
    ただし、残念なことに 前回解説したようにVisual Studio からビルドする時はこの機能は働きません

さて、このファイルを媒体とする依存関係が成立するものは次のコマンドです。

  1. add_executable
  2. add_library
  3. add_custom_command
    add_custom_targetは出力ファイルを定義できないため、残念ながら処理できないようです。その代りadd_custom_command(OUTPUT)が処理するようです。

なお、上記3つのCMakeコマンドは対等ではありません。

  • add_executable の出力に依存することはできません。
  • add_custom_commandはadd_libraryの出力に依存することはできません。

つまり、依存関係は、以下の方向に働きます。

add_custom_command ← add_library ← add_executable

3-2. ターゲット間に定義する明示的な依存関係

あまり頻繁にはないと思いますが、3-1のような依存はないけど処理順序を規制したい場合もあります。(特殊ケースですが、例えば「ターゲットA」をビルドする際にその「入力ファイルを自動生成するようなツールをビルドするターゲットB」がある時、AはBに依存させたいです。)
そのようなケースでは、add_dependenciesでターゲット間の依存関係を明示的に指定することができます。

ターゲットAをターゲットBに依存させたい時は、次のように指定子ます。

add_dependencies(ターゲットA ターゲットB)

実はadd_dependenciesを使う必要のあるケースは結構レアですが、複雑なビルド・システムを組む場合には必要になることがあります。頭の片隅にでもおいておくと良さそうです。

4.add_custom_command (OUTPUT)のサンプル

少し使い方が難しいのでadd_custom_command (OUTPUT)のサンプルを作ってみました。
例えば、プロジェクトのバージョン番号を一箇所で定義し、C++プログラムとC#プログラム(*1)やインストーラ等、複数の異なる言語等から参照したいケースはわりとあります。そのような時、CMakeを使ってバージョン番号を定義し、それを使用したいプログラミング言語のソースへ反映することができます。

そのためのCMakeコマンドとしてconfigure_fileがあります。これは入力ファイル内の ${CMake変数} という文字列をCMake変数の内容へ置換して出力します。
このconfigure_fileを仕込んだCMakeスクリプトversion.cmakeを add_custom_command(OUTPUT) から呼び出してみました(*2)
version.cmakeは、バージョン番号等をCMake変数として定義しconfigure_fileを使ってversion.h.in内の${CMake変数}文字列をその変数の定義内容で置き換えた version.h を生成しています。

cmake_minimum_required(VERSION 3.5.1)
project(sample)

include("${CMAKE_CURRENT_SOURCE_DIR}/base.cmake")

add_custom_command(OUTPUT version.h DEPENDS version.cmake version.h.in
    COMMAND ${CMAKE_COMMAND} -P version.cmake
    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)

add_executable(main main.cpp version.h)
set(PRODUCT_NAME "add_custom_command sample")
set(VERSION_MAJOR 1)
set(VERSION_MINOR 2)
set(BUILD_NUMBER 345)
set(LICENSE_HOLDER "Theoride Technology")
configure_file(version.h.in version.h)
#define VERSION_MAJOR   ${VERSION_MAJOR}
#define VERSION_MINOR   ${VERSION_MINOR}
#define BUILD_NUMBER    ${BUILD_NUMBER}

#define VERSION_STRING  \
    "${PRODUCT_NAME} ${VERSION_MAJOR}.${VERSION_MINOR}.${BUILD_NUMBER}\n" \
    "Copyright(C) 2019 ${LICENSE_HOLDER} all rights reserved."
出力されたversion.h
#define VERSION_MAJOR   1
#define VERSION_MINOR   2
#define BUILD_NUMBER    345

#define VERSION_STRING  \
    "add_custom_command sample 1.2.345\n" \
    "Copyright(C) 2019 Theoride Technology all rights reserved."
#include <cstdio>
#include <iostream>
#include "version.h"
#include "lib.h"

int main()
{
    std::cout << VERSION_STRING << "\n";
}

add_custom_command (OUTPUT)のDEPENDSにversion.cmake、version.h.inを指定しているので、バージョン番号(version.cmake)や、そのC++への反映用の雛形ファイル(version.h.in)を変更してversion.hより新しい更新日時になると、add_custom_command (OUTPUT)のCOMMANDが実行されてversion.hが生成されます。

そして、version.hが更新されたのでこれを入力ファイルとする add_executable()のビルド・コマンドも実行され、無事ビルドされます。

(*1)C++プログラムとC#プログラム
最近(3.8以降)のCMakeは実はC#にも対応しています。

(*2)なぜ、スクリプトを使うのか?
configure_fileをCMakeLists.txtから直接呼び出すことも当然できます。なので、わざわざスクリプト・ファイルを作らないで済ませることも可能です。version.cmakeの内容をそのままCMakeLists.txtに書いてもほぼ同様なことができます。この場合は、ビルド・システムを生成する際に変換されます。
ここでは、add_custom_command (OUTPUT)のサンプルとしたかったので、CMakeスクリプトとしています。またこのサンプルのようにすればバージョン番号等を修正した時でも再生成不要です。通常のビルド手順で済みますので反映漏れが発生しにくいです。

5.まとめ

CMakeの依存関係は結構便利で、現在のデバッグのためには不要なターゲットのビルドを容易に省略できます。ですが、依存関係に着目した解説はあまりありません。更にVisual Studioの振る舞いが独特なのでかなり混乱しました。
Visual StudioはMakeとは異なり、ビルド・ターゲット=デフォルト・ターゲットです。そして、ビルド・ターゲットから外すとそれに依存するターゲットをビルドする時にもビルドされません。なので、デフォルトではビルドせず特定のターゲットをビルドする時にだけビルドしたいという指定ができないことに気がつくのに多くの時間を要しました。
そこで、今回CMakeの依存関係について できる範囲ですが調査・整理して纏めてみました。多少なりと混乱回避のお役に立てることを願いつつ、今回はこれにて終了致します。