こんにちは。田原です。

CMakeは各種ファイル操作にも対応しています。ファイルのリード/ライト、ファイルのコピーや移動、フォルダの作成などは当然として、CMakeにとって便利な(サブフォルダ配下も含む)ファイルやフォルダのリスト作成、更にびっくり機能のファイルのダウンロード/アップロードなどなどです。さて、このファイル・リストの作成はビルド・システム構築に便利ですが、最近びっくり拡張されていました。今回はそのCONFIGURE_DEPENDSについて解説します。

1.ファイル・リストを返すGLOB/GLOB_RECURSE

GLOB/GLOB_RECURSEはファイルやフォルダのパスをリストとして返します。必要なファイルだけをコピーする時などに便利です。
また、add_executableやadd_libraryなどに渡すソース・ファイルのリストを生成するのにも使われることがあります。
後者は特にソースを追加した際に、再生成を忘れて「あちゃっ」となることも少くないのですが、CONFIGURE_DEPENDSで対処できそうです。

さて、まずは準備です。

CMakeのリストを使いますので、それを表示する簡易ツールを用意しました。

また、テスト用のフォルダを次のように設けています。

サンプル・ソースを置くフォルダ/(私の環境ではC:/cpp-school/sample/)
  print_list.cmake
  sample*.cmake
  foo/
    foo.txt
    foo.log
    bar/
      bar.txt
      bar.log
      baz/
        baz.txt
        baz.log

1-1.file(GLOB)

file(GLOB)コマンドのパラメータについて説明します。

パラメータ名 意味
LIST_DIRECTORIES フォルダ名(ディレクトリ名)をリストに含めるかどうかを指定します。
省略するとフォルダ名もリストに含まれます。
RELATIVE 出力されるパスを指定したフォルダからの相対パスとします。省略すると絶対パスになります。
CONFIGURE_DEPENDS ファイル・リストが変化するとMakefileを再生成します。
3.12から追加された新機能。

具体的にはサンプルを見るのが早いと思います。LIST_DIRECTORIESとRELATIVEを指定しています。
${CMAKE_SOURCE_DIR}については前回の解説を参照下さい。
なお、CONFIGURE_DEPENDSは複雑ですので後述します。

include(print_list.cmake)

file(GLOB LIST "foo/*")
print_list("GLOB only" "${LIST}")

file(GLOB LIST LIST_DIRECTORIES false "foo/*")
print_list("GLOB LIST_DIRECTORIES false" "${LIST}")

file(GLOB LIST RELATIVE "${CMAKE_SOURCE_DIR}" "foo/*")
print_list("GLOB RELATIVE ${CMAKE_SOURCE_DIR}" "${LIST}")
C:\cpp-school\sample>cmake -P sample0.cmake
-- ##### GLOB only #####
-- C:/cpp-school/sample/foo/bar
-- C:/cpp-school/sample/foo/foo.log
-- C:/cpp-school/sample/foo/foo.txt

-- ##### GLOB LIST_DIRECTORIES false #####
-- C:/cpp-school/sample/foo/foo.log
-- C:/cpp-school/sample/foo/foo.txt

-- ##### GLOB RELATIVE C:/cpp-school/sample #####
-- foo/bar
-- foo/foo.log
-- foo/foo.txt

1-2.file(GLOB_RECURSE)

GLOBとほぼ同じ機能ですが、フォルダの下を再帰的にリストアップします。
パラメータはGLOBと同じです。

GLOB同様、具体的にはサンプルを見るのが早いと思いますのでサクッとサンプルです。

include(print_list.cmake)

file(GLOB LIST "foo/*")
print_list("GLOB only" "${LIST}")

file(GLOB LIST LIST_DIRECTORIES false "foo/*")
print_list("GLOB LIST_DIRECTORIES false" "${LIST}")

file(GLOB LIST RELATIVE "${CMAKE_SOURCE_DIR}" "foo/*")
print_list("GLOB RELATIVE ${CMAKE_SOURCE_DIR}" "${LIST}")
C:\cpp-school\sample>cmake -P sample1.cmake
-- ##### GLOB_RECURSE only #####
-- C:/cpp-school/sample/foo/bar/bar.log
-- C:/cpp-school/sample/foo/bar/bar.txt
-- C:/cpp-school/sample/foo/bar/baz/baz.log
-- C:/cpp-school/sample/foo/bar/baz/baz.txt
-- C:/cpp-school/sample/foo/foo.log
-- C:/cpp-school/sample/foo/foo.txt

-- ##### GLOB_RECURSE LIST_DIRECTORIES false #####
-- C:/cpp-school/sample/foo/bar/bar.log
-- C:/cpp-school/sample/foo/bar/bar.txt
-- C:/cpp-school/sample/foo/bar/baz/baz.log
-- C:/cpp-school/sample/foo/bar/baz/baz.txt
-- C:/cpp-school/sample/foo/foo.log
-- C:/cpp-school/sample/foo/foo.txt

-- ##### GLOB_RECURSE RELATIVE C:/cpp-school/sample #####
-- foo/bar/bar.log
-- foo/bar/bar.txt
-- foo/bar/baz/baz.log
-- foo/bar/baz/baz.txt
-- foo/foo.log
-- foo/foo.txt

1-3.CONFIGURE_DEPENDSについて

私も今回初めて使ったのですが、このオプションは「かゆいところ」に手を届かせるかなり特殊なオプションです。

file(GLOB/GLOB_RECURSE …)コマンドは通常は、cmakeでビルド・システム生成コマンドを実行(cmake ..など)した時だけ働いてファイル・リストを生成し、makefileへ出力します。

CONFIGURE_DEPENDSオプションを追加すると、ビルド(cmake –build .など)した時もfile(GLOB/GLOB_RECURSE …)コマンドが働き、ファイル・リストが変化していたら自動的にビルド・システム生成コマンドを実行してから、そのまま自動的にビルドします。いやびっくりです。

なお、公式のドキュメントに注意事項として次のように記載されています。(Google翻訳して整形)しかし、Windowsで後述の使い方をした時は期待通りに動作しました。

注:
GLOBを使用してソースツリーからソースファイルのリストを収集することはお勧めしません。 ソースの追加または削除時にCMakeLists.txtファイルが変更されない場合、生成されたビルドシステムは、CMakeに再生成をいつ依頼するかを知ることができません。 CONFIGURE_DEPENDSフラグは、すべてのジェネレーターで確実に機能しない場合があります。または、将来サポートされない新しいジェネレーターが追加された場合、それを使用するプロジェクトはスタックします。 CONFIGURE_DEPENDSが確実に機能する場合でも、再構築のたびにチェックを実行するコストがかかります。

余談ですが、Google翻訳がすごくなっている!
最近のGoogle翻訳の精度がすごいですね。慣用的な表現まで翻訳してくれるので驚いています。
時として単語の持つ別の意味で翻訳されることもあるので要注意ですが、翻訳が本当に楽になりました。

それではお試し用のサンプルです。
ソース・ファイルを3つとCMakeLists.txtを次のように追加しています。

サンプル・ソースを置くフォルダ/(私の環境ではC:/cpp-school/sample/)
  print_list.cmake
  CMakeLists.txt
  foo/
    foo.h
    foo.bpp
    bar/
      bar.c
cmake_minimum_required(VERSION 3.12.0)
project(sample)

if(MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /EHsc")
else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11")
endif()

include(print_list.cmake)

file(GLOB_RECURSE SOURCE_LIST CONFIGURE_DEPENDS "foo/*.h" "foo/*.cpp")
print_list("GLOB_RECURSE only" "${SOURCE_LIST}")

add_executable(sample ${SOURCE_LIST})
ソース群(超手抜きですごめんなさい)
[cpp title=”foo/foo.h”] void foo();
[/cpp] [cpp title=”foo/foo.cpp”] #include <iostream>

void foo()
{
std::cout << "foo()\n";
}
[/cpp] [cpp title=”foo/bar/bar.c”] #include "../foo.h"

int main()
{
foo();
}
[/cpp]

ここまでのサンプル

ご覧のように bar.c としているためCMakeLists.txtの12行目のfile(GLOB …)で抽出されずadd_executable()へ与えられません。
bar.cでmain()関数を定義しているので、これをビルドするとmain()関数無しエラーになります。
ちょっとやってみましょう。

C:\cpp-school\sample>mkdir build

C:\cpp-school\sample>cd build

C:\cpp-school\sample\build>cmake ..
-- Building for: Visual Studio 15 2017
-- Selecting Windows SDK version 10.0.17134.0 to target Windows 10.0.18362.
-- The C compiler identification is MSVC 19.16.27027.1
-- The CXX compiler identification is MSVC 19.16.27027.1
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- ##### GLOB_RECURSE only #####
-- C:/cpp-school/sample/foo/foo.cpp
-- C:/cpp-school/sample/foo/foo.h

-- Configuring done
-- Generating done
-- Build files have been written to: C:/cpp-school/sample/build

C:\cpp-school\sample\build>cmake --build .
.NET Framework 向け Microsoft (R) Build Engine バージョン 15.9.21+g9802d43bc3
Copyright (C) Microsoft Corporation.All rights reserved.

  Checking File Globs
  Checking Build System
  CMake does not need to re-run because C:/cpp-school/sample/build/CMakeFiles/generate.stamp is up-to-date.
  Building Custom Rule C:/cpp-school/sample/CMakeLists.txt
  CMake does not need to re-run because C:/cpp-school/sample/build/CMakeFiles/generate.stamp is up-to-date.
  foo.cpp
MSVCRTD.lib(exe_main.obj) : error LNK2019: 未解決の外部シンボル _main が関数 "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) で
参照されました。 [C:\cpp-school\sample\build\sample.vcxproj]
C:\cpp-school\sample\build\Debug\sample.exe : fatal error LNK1120: 1 件の未解決の外部参照 [C:\cpp-school\sample\build\sample.vcxp
roj]

C:\cpp-school\sample\build>

さて、ここから CONFIGURE_DEPENDS が威力を発揮します!!
foo/bar/bar.cをfoo/bar/bar.cppへリネームして実行してみましょう。

C:\cpp-school\sample\build>cmake -E rename ../foo/bar/bar.c ../foo/bar/bar.cpp

CMakeにはbar.cppが突然追加されたように見えます。追加したことを makefile へ教える必要がありますので、CONFIGURE_DEPENDS 無しの場合は再度Makefile生成する必要があります。生成しないでビルドすると当然エラーになります。

元々file(GLOB_RECURSE)からCONFIGURE_DEPENDS を外してcmake ..していた場合
[text] C:\cpp-school\sample\sample\build>cmake –build .
.NET Framework 向け Microsoft (R) Build Engine バージョン 15.9.21+g9802d43bc3
Copyright (C) Microsoft Corporation.All rights reserved.

MSVCRTD.lib(exe_main.obj) : error LNK2019: 未解決の外部シンボル _main が関数 "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) で
参照されました。 [C:\cpp-school\sample\sample\build\sample.vcxproj] C:\cpp-school\sample\sample\build\Debug\sample.exe : fatal error LNK1120: 1 件の未解決の外部参照 [C:\cpp-school\sample\sample\bui
ld\sample.vcxproj] [/text]

しかし、CONFIGURE_DEPENDS を指定していた場合は、スルッとビルドまで通ります!!

C:\cpp-school\sample\build>cmake --build .
.NET Framework 向け Microsoft (R) Build Engine バージョン 15.9.21+g9802d43bc3
Copyright (C) Microsoft Corporation.All rights reserved.

  Checking File Globs
  -- GLOB mismatch!
  Checking Build System
  CMake is re-running because C:/cpp-school/sample/build/CMakeFiles/generate.stamp is out-of-date.
    the file 'C:/cpp-school/sample/build/CMakeFiles/cmake.verify_globs'
    is newer than 'C:/cpp-school/sample/build/CMakeFiles/generate.stamp.depend'
    result='-1'
  -- Selecting Windows SDK version 10.0.17134.0 to target Windows 10.0.18362.
  -- ##### GLOB_RECURSE only #####
  -- C:/cpp-school/sample/foo/bar/bar.cpp
  -- C:/cpp-school/sample/foo/foo.cpp
  -- C:/cpp-school/sample/foo/foo.h

  -- Configuring done
  -- Generating done
  -- Build files have been written to: C:/cpp-school/sample/build
  bar.cpp
  sample.vcxproj -> C:\cpp-school\sample\build\Debug\sample.exe
  Building Custom Rule C:/cpp-school/sample/CMakeLists.txt
  CMake does not need to re-run because C:/cpp-school/sample/build/CMakeFiles/generate.stamp is up-to-date.

6行目で「GLOB mismatch!」(GLOBが変わったぞ!)と言って、自動的に再生成しています。14行目を見ると bar.cpp がソース・リストに追加されていますね。20行目でmakefileに書き込まれ、その後、普通にビルドされています。

2.まとめ

あああ、本当は「よく使う機能」として各種ファイル操作(コピーや移動などなど)とファイルのリード/ライトなども解説する予定だったのですが、CONFIGURE_DEPENDS の調査・解説に時間がかかって時間切れとなってしまいました。続きは次回へ繰り越します。

しかし、CONFIGURE_DEPENDS は便利そうです。ビルドに数分かかるようなプロジェクトで、再生成を忘れてビルドの最後にエラーになるとがっかりしますよね。そのようなミスを撲滅できそうなので期待です。
注意事項があるので注意しつつ使っていこうと思います。

それでは今回はここまでです。お疲れさまでした!