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

## はじめに

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

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

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

> ```cmake
> add_custom_target(Name [ALL] [command1 [args1...]]
>                   [COMMAND command2 [args2...] ...]
>                   [DEPENDS depend depend depend ... ]
>                   [WORKING_DIRECTORY dir]
>                   [COMMENT comment] [VERBATIM]
>                   [SOURCES src1 [src2...]])
> ```
> <cite>[add\_custom\_target — CMake 3.0.2 Documentation](http://www.cmake.org/cmake/help/v3.0/command/add_custom_target.html)</cite>

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

### Name

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

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)

add_custom_target(my_target)
```

```bash
$ cmake .
$ make my_target
Scanning dependencies of target my_target
Built target my_target
```

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

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)

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

```bash
$ 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`識別子を省略することができます。

```cmake: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
)
```

```bash
$ cmake .
$ make my_target
Scanning dependencies of target my_target
@@ command_1
@@ command_2
@@ command_3
Built target my_target
```

### DEPENDS

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

```cmake: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
)
```

```bash
$ 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`には、カスタムターゲットの動作ディレクトリを指定します。

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)

add_custom_target(my_target pwd WORKING_DIRECTORY ~)
```

```bash
$ cmake .
$ make my_target
Scanning dependencies of target my_target
/path/to/home
Built target my_target
```

### COMMENT

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

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)

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

```bash
$ 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}`と指定したとおりに出力されます。

```cmake: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)
```

```bash
$ 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()`が呼び出されます。

```cmake: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
)
```

```cpp:src.cpp
#include <iostream>

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

```bash
$ 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
```

```cpp:mylib/hoge.hpp
#ifndef MYLIB_HOGE_HPP
#define MYLIB_HOGE_HPP

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

#endif
```

```cpp:mylib/fuga.hpp
#ifndef MYLIB_FUGA_HPP
#define MYLIB_FUGA_HPP

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

#endif
```

```cpp:src/hoge.cpp
#include <iostream>
#include <mylib/hoge.hpp>

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

```cpp:src/fuga.cpp
#include <iostream>
#include <mylib/fuga.hpp>

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

```cpp: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()
```

```cpp: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()
```

```cmake: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)
```

```cmake:src/CMakeLists.txt
add_executable(hoge hoge.cpp)
add_executable(fuga fuga.cpp)
```

```cmake: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>)
```

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

```bash
$ 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.txt`と`test/CMakeLists.txt`を次のようにします。

```cmake: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) # 追加
```

```cmake: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_test`・`build_src`とし、依存するターゲットに各テストコマンド・実行コマンドを指定しています。これによって、テストコマンドだけビルドするターゲットと実行コマンドだけビルドするターゲットが生成されました。つまり、テストコマンドだけビルドしたい場合は、`$ make build_test`とし、実行コマンドだけビルドしたい場合は、`$ make build_src`とすればよいのです。このようなことはよくあることなので、ターゲットのグループを行うコマンドとして、カスタムターゲットは重宝されるのではないでしょうか。

```bash
$ 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
```

```bash
$ 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: プリコンパイル済みヘッダーの作成と利用』です。