Edited at
CMakeDay 20

CMake: カスタムターゲットによるグループ化

More than 3 years have passed since last update.


はじめに

みなさん、こんにちは。今回は CMake のカスタムターゲットという機能を用いてターゲットのグループ化を行う方法について書いていきます。


カスタムターゲットとは何か?

まず、カスタムターゲットとは何かについて述べます。CMake では、add_executable()add_library()などによって、実行ファイルやライブラリなどを生成するための各ビルドシステムのターゲットを作成します。カスタムターゲットとは、生成を目的としない任意のターゲットを作成するためのもので、以下のような構文になっています。


add_custom_target(Name [ALL] [command1 [args1...]]

[COMMAND command2 [args2...] ...]
[DEPENDS depend depend depend ... ]
[WORKING_DIRECTORY dir]
[COMMENT comment] [VERBATIM]
[SOURCES src1 [src2...]])

add_custom_target — CMake 3.0.2 Documentation


各引数の意味は以下となります。


Name

<Name>にはターゲット名を指定します。ここで指定した名前がビルドシステムのターゲットとして追加されます。


CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)

add_custom_target(my_target)


$ cmake .

$ make my_target
Scanning dependencies of target my_target
Built target my_target

ここで、ALLオプションを追加すると、デフォルトターゲット(Unix Makefiles では all)にも追加されます。


CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)

add_custom_target(my_target ALL)
add_executable(src src.cpp)


$ cmake .

$ make
Scanning dependencies of target my_target
[ 0%] Built target my_target
Scanning dependencies of target src
[100%] Building CXX object CMakeFiles/src.dir/src.cpp.o
Linking CXX executable src
[100%] Built target src


COMMAND

command<N>arg1 ...には、カスタムターゲットが実行されたときに呼び出されるコマンドとその引数を指定します。COMMAND ...セクションは何回でも指定できます。また、一番最初に限り、COMMAND識別子を省略することができます。


CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)

add_custom_target(my_target echo @@ command_1
COMMAND echo @@ command_2
COMMAND echo @@ command_3
)


$ cmake .

$ make my_target
Scanning dependencies of target my_target
@@ command_1
@@ command_2
@@ command_3
Built target my_target


DEPENDS

depend ...には、依存するターゲット名を指定します。また、DEPENDS ...セクションは何回でも指定できます。


CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)

add_custom_target(my_target1 echo @@ command_1)
add_custom_target(my_target2 echo @@ command_2)
add_custom_target(my_target3 echo @@ command_3)
add_custom_target(my_target4 echo @@ command_4
DEPENDS my_target1 my_target2
DEPENDS my_target3
)


$ cmake .

$ make my_target4
Scanning dependencies of target my_target3
@@ command_3
Built target my_target3
Scanning dependencies of target my_target1
@@ command_1
Built target my_target1
Scanning dependencies of target my_target2
@@ command_2
Built target my_target2
Scanning dependencies of target my_target4
@@ command_4
Built target my_target4


WORKING_DIRECTORY

dirには、カスタムターゲットの動作ディレクトリを指定します。


CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)

add_custom_target(my_target pwd WORKING_DIRECTORY ~)


$ cmake .

$ make my_target
Scanning dependencies of target my_target
/path/to/home
Built target my_target


COMMENT

コメントとは、ビルド前に表示される[100%] Building CXX object ...のようなメッセージのことで、commentに指定した内容が表示されます。


CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)

add_custom_target(my_target
echo @@ my_target
COMMENT [[!! my_target comment !!]]
)


$ cmake .

$ make my_target
Scanning dependencies of target my_target
[100%] !! my_target comment !!
@@ my_target
[100%] Built target my_target


VERBATIM

VERBATIMオプションを指定すると、各引数の値がビルドシステムでそのまま出力されるようにエスケープされます。例えば、下記の例ではコマンドの引数に${PATH}という文字列がありますが、これは、ビルドシステムが Unix Makefiles の場合、環境変数参照の形となっています。したがって、実行すると環境変数PATHの値が出力されてしまいます。VERBATIMオプションを指定すると、${PATH}と指定したとおりに出力されます。


CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)

add_custom_target(my_target1 echo @@ PATH: [[${PATH}]])
add_custom_target(my_target2 echo @@ PATH: [[${PATH}]] VERBATIM)


$ cmake .

$ make my_target1 my_target2
Scanning dependencies of target my_target1
@@ PATH: /usr/local/bin:/usr/bin
Built target my_target1
Scanning dependencies of target my_target2
@@ PATH: ${PATH}
Built target my_target2


SOURCES

このオプションは、add_custom_command(OUTPUT)コマンドとセットで使用するためのものです。add_custom_command(OUTPUT)は、独自の生成方法を定義するためのものですが、そのままでは呼び出されません。そこで、add_custom_target()コマンドにSOURCESオプションを付与することでこれを可能とします。add_custom_command(OUTPUT output1 ...)の出力ファイルoutput1 ...で指定したファイル名をadd_custom_target(Name SOURCES src1 ...)コマンドのsrc1 ...に指定するとNameターゲットが呼び出された時にadd_custom_command()が呼び出されます。


CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)

add_custom_target(my_target
SOURCES copied_src.cpp
)
add_custom_command(OUTPUT copied_src.cpp
COMMAND cp ${PROJECT_SOURCE_DIR}/src.cpp copied_src.cpp
)



src.cpp

#include <iostream>


int main() {
std::cout << "src" << std::endl;
return 0;
}

$ cmake .

$ make my_target
Scanning dependencies of target my_target
[100%] Generating copied_src.cpp
[100%] Built target my_target
$ cat copied_src.cpp
#include <iostream>

int main() {
std::cout << "src" << std::endl;
return 0;
}


カスタムターゲットによるグループ化

さて、カスタムターゲットの使い方が分かりましたが、このコマンドの使用頻度は高くないと思ったかもしれません。生成を目的としないタスクとしてのターゲットで思いつくのはテストの実行くらいでしょうか。しかし、テストの実行には CTest という強力なツールがありますし、そもそもenable_testing()コマンドによって、testターゲットが生成されます。かといって、add_custom_command()と連携するにも、独自の生成ルールなど普段使うものではありません。

しかし、カスタムターゲットには、任意のターゲットをグループとしてまとめる、という機能があり、これは普段よく使うものです。例えば、以下の構成を持つプロジェクトがあったとします。

project-root/

├── CMakeLists.txt
├── mylib
│   ├── hoge.hpp
│   └── foga.hpp
├── src
│   ├── CMakeLists.txt
│   ├── hoge.cpp
│   └── fuga.cpp
└── test
   ├── CMakeLists.txt
   ├── hoge_test.cpp
   └── foga_test.cpp


mylib/hoge.hpp

#ifndef MYLIB_HOGE_HPP

#define MYLIB_HOGE_HPP

namespace mylib {
inline int hoge() {
return 1;
}
}

#endif



mylib/fuga.hpp

#ifndef MYLIB_FUGA_HPP

#define MYLIB_FUGA_HPP

namespace mylib {
inline int fuga() {
return 2;
}
}

#endif



src/hoge.cpp

#include <iostream>

#include <mylib/hoge.hpp>

int main() {
std::cout << mylib::hoge() << std::endl;
return 0;
}


src/fuga.cpp

#include <iostream>

#include <mylib/fuga.hpp>

int main() {
std::cout << mylib::fuga() << std::endl;
return 0;
}


test/hoge_test.cpp

#define BOOST_TEST_MAIN

#include <boost/test/included/unit_test.hpp>
#include <mylib/hoge.hpp>

BOOST_AUTO_TEST_SUITE(mylib)
BOOST_AUTO_TEST_CASE(mylib) {
BOOST_CHECK_EQUAL(hoge(), 1);
}
BOOST_AUTO_TEST_SUITE_END()


test/fuga_test.cpp

#define BOOST_TEST_MAIN

#include <boost/test/included/unit_test.hpp>
#include <mylib/fuga.hpp>

BOOST_AUTO_TEST_SUITE(mylib)
BOOST_AUTO_TEST_CASE(mylib) {
BOOST_CHECK_EQUAL(fuga(), 2);
}
BOOST_AUTO_TEST_SUITE_END()


CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)

find_package(Boost REQUIRED)
add_compile_options(-std=gnu++1y)
include_directories(${Boost_INCLUDE_DIRS})

include_directories(${PROJECT_SOURCE_DIR})
enable_testing()

add_subdirectory(src)
add_subdirectory(test)



src/CMakeLists.txt

add_executable(hoge hoge.cpp)

add_executable(fuga fuga.cpp)


test/CMakeLists.txt

add_executable(hoge_test hoge_test.cpp)

add_test(NAME hoge COMMAND $<TARGET_FILE:hoge_test>)

add_executable(fuga_test fuga_test.cpp)
add_test(NAME fuga COMMAND $<TARGET_FILE:fuga_test>)


このプロジェクトをビルドするときは、下記のようにすると思います。

$ cmake .

$ make
Scanning dependencies of target fuga
[ 25%] Building CXX object src/CMakeFiles/fuga.dir/fuga.cpp.o
Linking CXX executable fuga
[ 25%] Built target fuga
Scanning dependencies of target hoge
[ 50%] Building CXX object src/CMakeFiles/hoge.dir/hoge.cpp.o
Linking CXX executable hoge
[ 50%] Built target hoge
Scanning dependencies of target fuga_test
[ 75%] Building CXX object test/CMakeFiles/fuga_test.dir/fuga_test.cpp.o
Linking CXX executable fuga_test
[ 75%] Built target fuga_test
Scanning dependencies of target hoge_test
[100%] Building CXX object test/CMakeFiles/hoge_test.dir/hoge_test.cpp.o
Linking CXX executable hoge_test
[100%] Built target hoge_test

しかし、開発時はテストを実行するのがほとんどではないでしょうか?そして、src 以下を実行することはないので、ビルドする必要はありません。これは、src 以下をビルドするときも同様です。このときはテストコードをビルドする必要はありません。しかし、現在のままだと両方ともビルドしてしまいます。かといって、いちいち$ make hoge_test fuga_testとするのは面倒なことです。

そこで、カスタムターゲットの出番です。カスタムターゲットで、テスト用のグループと実行コマンド用のグループを表すターゲットを作成します。まず、src/CMakeLists.txttest/CMakeLists.txtを次のようにします。


src/CMakeLists.txt

add_custom_target(build_src) # 追加

add_executable(hoge hoge.cpp)
add_dependencies(build_src hoge) # 追加

add_executable(fuga fuga.cpp)
add_dependencies(build_src fuga) # 追加



test/CMakeLists.txt

add_custom_target(build_test) # 追加

add_executable(hoge_test hoge_test.cpp)
add_test(NAME hoge COMMAND $<TARGET_FILE:hoge_test>)
add_dependencies(build_test hoge_test) # 追加

add_executable(fuga_test fuga_test.cpp)
add_test(NAME fuga COMMAND $<TARGET_FILE:fuga_test>)
add_dependencies(build_test fuga_test) # 追加


ここで、add_dependencies(<target> <target-dependency> ...)コマンドとは、指定したターゲット<target>に、依存ターゲット<target-dependency> ...を追加するものです。上記では、テストコマンドと実行コマンドを表すターゲットをそれぞれbuild_testbuild_srcとし、依存するターゲットに各テストコマンド・実行コマンドを指定しています。これによって、テストコマンドだけビルドするターゲットと実行コマンドだけビルドするターゲットが生成されました。つまり、テストコマンドだけビルドしたい場合は、$ make build_testとし、実行コマンドだけビルドしたい場合は、$ make build_srcとすればよいのです。このようなことはよくあることなので、ターゲットのグループを行うコマンドとして、カスタムターゲットは重宝されるのではないでしょうか。

$ make build_test

Scanning dependencies of target fuga_test
[ 50%] Building CXX object test/CMakeFiles/fuga_test.dir/fuga_test.cpp.o
Linking CXX executable fuga_test
[ 50%] Built target fuga_test
Scanning dependencies of target hoge_test
[100%] Building CXX object test/CMakeFiles/hoge_test.dir/hoge_test.cpp.o
Linking CXX executable hoge_test
[100%] Built target hoge_test
Scanning dependencies of target build_test
[100%] Built target build_test

$ make build_src

Scanning dependencies of target fuga
[ 50%] Building CXX object src/CMakeFiles/fuga.dir/fuga.cpp.o
Linking CXX executable fuga
[ 50%] Built target fuga
Scanning dependencies of target hoge
[100%] Building CXX object src/CMakeFiles/hoge.dir/hoge.cpp.o
Linking CXX executable hoge
[100%] Built target hoge
Scanning dependencies of target build_src
[100%] Built target build_src


おわりに

以上、カスタムターゲットによるグループ化でした。カスタムターゲットを使用することで、状況に応じて必要なものだけビルドすることができます。これによってビルド時間が短縮され、効率よく開発できます。

明日は、mrk_21 さんの『CMake: プリコンパイル済みヘッダーの作成と利用』です。