CMake: Generator Expressions

## はじめに

みなさん、こんにちは。今回は Generator Expressions について書いていきます。

## Generator Expressions とはなにか？

Generator Expressions とは、ターゲットプロパティでのみ有効な式で、ビルドシステム生成時に評価されます。

## Generator Expressions の種類

この Generator Expressions には以下の3種類があります。

### Logical Expressions

Logical Expressions とは、if式と条件式を扱うものです。指定条件を満たしたときのみ値を出力したい場合に使用します。

#### if式

if式の構文は以下のようになっています。

```
$<<condition>:<value>>
```

条件式`<condition>`では、`1`を真、`0`を偽と評価します。`if()`コマンドのように`TRUE`や`FALSE`、`NOTFOUND`などはブール定数と評価されない点に注意してください。これらの値を含め、`1`と`0`以外の値を指定した場合は、エラーになってしまいます。また、条件式`<condition>`には空白文字を含めてはいけません。

```
$<0:1>  # OK
$<1:2>  # OK
$<2:3>  # エラー
$<a:4>  # エラー
$<0 :5> # エラー
```

`<value>`には、条件式が真になったときに出力する値を指定します。残念ながら、条件式が偽になった場合に出力する値を指定することはできません。

#### 条件式

条件式には、`if()`コマンドにおけるブール演算子やテストに相当するものがあり、以下の様な構文になっています。

```
$<<expr-name>:<arg1>[,...]>
```

`<expr-name>`には条件式名、`<arg1>[,...]`には条件式で使う値を指定します。また、if式のときと同様に、`<expr-name>`には空白文字を含めてはいけません。

条件式には以下の種類があります。

##### ブール演算

###### `$<BOOL:<value>>`

これは、[if()コマンドのブール定数](http://qiita.com/mrk_21/items/49d8802dc63a2791bcc3#ブール定数)を、if式の真偽値に変換するためのものです。

```
$<BOOL:1>             # 1
$<BOOL:2>             # 1
$<BOOL:TRUE>          # 1
$<BOOL:Yes>           # 1
$<BOOL:on>            # 1

$<BOOL:>              # 0
$<BOOL:0>             # 0
$<BOOL:FALSE>         # 0
$<BOOL:No>            # 0
$<BOOL:off>           # 0
$<BOOL:Hoge-NotFound> # 0
```

###### `$<AND:<arg1>[,...]>`

`if()`コマンドのANDブール演算子に相当するものです。値`<arg1>[,...]`がすべて`1`のときに`1`を、ひとつでも`0`があると`0`を出力します。

```
$<AND:1>       # 1
$<AND:1,1>     # 1
$<AND:1,1,1>   # 1
$<AND:1,1,1,1> # 1

$<AND:0>       # 0
$<AND:1,0>     # 0
$<AND:1,0,1>   # 0
$<AND:1,1,0,1> # 0
```

###### `$<OR:<arg1>[,...]>`

`if()`コマンドのORブール演算子に相当するものです。値`<arg1>[,...]`がすべて`0`のときに`0`を、ひとつでも`1`があると`1`を出力します。

```
$<OR:1>       # 1
$<OR:1,1>     # 1
$<OR:0,1>     # 1
$<OR:0,1,0>   # 1
$<OR:0,0,1,0> # 1

$<OR:0>       # 0
$<OR:0,0>     # 0
$<OR:0,0,0>   # 0
$<OR:0,0,0,0> # 0
```

###### `$<NOT:<arg>>`

`if()`コマンドのNOTブール演算子に相当するものです。

```
$<NOT:1> # 0
$<NOT:0> # 1
```

##### 比較

以下は、`if()`コマンドでの[各種比較テスト](http://qiita.com/mrk_21/items/49d8802dc63a2791bcc3#数値を比較)に相当します。`<left>`に左辺値、`<right>`に右辺値を指定します。

* `$<EQUAL:<left>,<right>>`
* `$<STREQUAL:<left>,<right>>`
* `$<VERSION_GREATER:<left>,<right>>`
* `$<VERSION_LESS:<left>,<right>>`
* `$<VERSION_EQUAL:<left>,<right>>`

##### 現在のコンパイラが指定したものかどうか

以下は、現在のコンパイラが指定したものかどうかを判定します。`<compiler-id>`にはコンパイラID(Clang、GNUなど)、`<compiler-version>`には、コンパイラのバージョンを指定します。

* `$<C_COMPILER_ID:<compiler-id>>`
* `$<CXX_COMPILER_ID:<compiler-id>>`
* `$<C_COMPILER_VERSION:<compiler-version>>`
* `$<CXX_COMPILER_VERSION:<compiler-version>>`

##### 現在のプラットフォームが指定したものかどうか

```
$<PLATFORM_ID:<platform-id>>
```

現在のプラットフォーム(Darwin、Linuxなど)が、指定のプラットフォーム`<platform-id>`かどうかを判定します。

##### 指定したターゲットに関するポリシーが最新の動作かどうか

```
$<TARGET_POLICY:<policy-id>>
```

指定したターゲットに関するポリシー`<policy-id>`が`NEW`に設定されているかどうかを判定します。ここで、ターゲットに関するポリシーは、以下となり、これ以外を指定するとエラーになります。

* [CMP0003](http://www.cmake.org/cmake/help/v3.0/policy/CMP0003.html "CMP0003 — CMake 3.0.2 Documentation")
* [CMP0004](http://www.cmake.org/cmake/help/v3.0/policy/CMP0004.html "CMP0004 — CMake 3.0.2 Documentation")
* [CMP0008](http://www.cmake.org/cmake/help/v3.0/policy/CMP0008.html "CMP0008 — CMake 3.0.2 Documentation")
* [CMP0020](http://www.cmake.org/cmake/help/v3.0/policy/CMP0020.html "CMP0020 — CMake 3.0.2 Documentation")
* [CMP0021](http://www.cmake.org/cmake/help/v3.0/policy/CMP0021.html "CMP0021 — CMake 3.0.2 Documentation")
* [CMP0022](http://www.cmake.org/cmake/help/v3.0/policy/CMP0022.html "CMP0022 — CMake 3.0.2 Documentation")
* [CMP0027](http://www.cmake.org/cmake/help/v3.0/policy/CMP0027.html "CMP0027 — CMake 3.0.2 Documentation")
* [CMP0038](http://www.cmake.org/cmake/help/v3.0/policy/CMP0038.html "CMP0038 — CMake 3.0.2 Documentation")
* [CMP0041](http://www.cmake.org/cmake/help/v3.0/policy/CMP0041.html "CMP0041 — CMake 3.0.2 Documentation")
* [CMP0042](http://www.cmake.org/cmake/help/v3.0/policy/CMP0042.html "CMP0042 — CMake 3.0.2 Documentation")
* [CMP0046](http://www.cmake.org/cmake/help/v3.0/policy/CMP0046.html "CMP0046 — CMake 3.0.2 Documentation")

### Informational Expressions

Informational Expressions とは、ターゲットプロパティなど、なんらかの情報を取得するものです。以下のような構文になっています。

```
$<<expr-name>[:<arg1>[,...]>
```

`<expr-name>`に取得したい式名、`<arg1>[,...]`に取得したい値を指定します。`<expr-name>`には空白文字を含めてはいけません。

Informational Expressions には以下の種類があります。

#### コンパイラの情報

コンパイラの情報を取得します。

* `$<C_COMPILER_ID>`
* `$<CXX_COMPILER_ID>`
* `$<C_COMPILER_VERSION>`
* `$<CXX_COMPILER_VERSION>`

#### ターゲットの情報

##### パス

指定したターゲット`<target-name>`の絶対パス、ファイル名、ディレクトリの絶対パスを取得します。`TARGET_LINKER_FILExxx`となっているものはライブラリのみ、`TARGET_SONAME_FILExxx`となっているものは共有ライブラリのみで、それ以外を指定するとエラーになります。

* `$<TARGET_FILE:<target-name>>`
* `$<TARGET_FILE_NAME:<target-name>>`
* `$<TARGET_FILE_DIR:<target-name>>`
* `$<TARGET_LINKER_FILE:<target-name>>`
* `$<TARGET_LINKER_FILE_NAME:<target-name>>`
* `$<TARGET_LINKER_FILE_DIR:<target-name>>`
* `$<TARGET_SONAME_FILE:<target-name>>`
* `$<TARGET_SONAME_FILE_NAME:<target-name>>`
* `$<TARGET_SONAME_FILE_DIR:<target-name>>`

##### プロパティ

```
$<TARGET_PROPERTY:[<target-name>,]<property-name>>
```

指定したターゲット`<target-name>`のプロパティ`<property-name>`の値を取得します。`<target-name>`を省略すると、Generator Expressions を設定したプロパティのターゲットになります。

#### その他の情報

それぞれ、現在のプラットフォーム(Darwin、Linux など)、`CMAKE_BUILD_TYPE`プロパティの値、インストール時のプレフィックスを取得します。ここで`$<INSTALL_PREFIX>`は`install(EXPORT)`コマンドでしか使用できないことに注意してください(それ以外で使用するとエラーになります)。

* `$<PLATFORM_ID>`
* `$<CONFIG>`
* `$<INSTALL_PREFIX>`


### Output Expressions

Output Expressions とは、指定された値を変換したり、特殊な文字を出力するものです。以下のような構文になっています。

```
$<<expr-name>[:<arg1>[,...]>
```

`<expr-name>`に式名、`<arg1>[,...]`に変換したい値を指定します。`<expr-name>`には空白文字を含めてはいけません。

Output Expressions には以下の種類があります。

#### 値の変換

##### `$<JOIN:<list>,<separator>`

リスト`<list>`を結合文字列`<separator>`で連結します。

```
$<JOIN:a;b;c,-> # a-b-c
```

##### `$<LOWER_CASE:<string>>`

文字列`<string>`をすべて小文字にします。

```
$<LOWER_CASE:Hoge> # hoge
```

##### `$<UPPER_CASE:<string>>`

文字列`<string>`をすべて大文字にします。

```
$<UPPER_CASE:Hoge> # HOGE
```

##### `$<MAKE_C_IDENTIFIER:<string>>`

文字列`<string>`をC言語の識別子名に変換します。空白文字・記号はすべてアンダースコア`_`に置換され、先頭の文字が数字の場合はアンダースコア`_`が先頭に付与されます。また、日本語などのワイド文字列は各バイトごとに認識されます。

```
$<MAKE_C_IDENTIFIER:0a-b c+d/eあd> # _0a_b_c_d_e___d
```

#### 特殊な文字の出力

以下はそれぞれ、`>`・`,`・`;`に置換されます。

* `$<ANGLE-R>`
* `$<COMMA>`
* `$<SEMICOLON>`

#### ある状況のときのみ出力する

##### `$<BUILD_INTERFACE:<content>>`

ビルド時にのみ`<content>`が出力されます。

##### `$<INSTALL_INTERFACE:<content>>`

インストール時のみ`<content>`が出力されます。

#### その他

##### `$<TARGET_NAME:<target-name>>`

`<target-name>`をターゲット名とみなします。

## Generator Expressions の用途

Generator Expressions の用途は様々ですが、例えば、ターゲットごとにコンパイルオプションを変えるときに役立ちます。以下は、特定のプロパティを持つときにその値を持つマクロを追加する例です。

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

add_compile_options(
  $<$<BOOL:$<TARGET_PROPERTY:VALUE>>:-DVALUE=$<TARGET_PROPERTY:VALUE>>
)
add_executable(bin1 src1.cpp)
add_executable(bin2 src2.cpp)
set_target_properties(bin2 PROPERTIES VALUE 200)
```

```cpp:config.hpp
#ifndef CONFIG_HPP
#define CONFIG_HPP

#ifndef VALUE
#define VALUE 100
#endif

#endif
```

```cpp:src1.cpp
#include <iostream>
#include "config.hpp"

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

```cpp:src2.cpp
#include <iostream>
#include "config.hpp"

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

```bash
$ cmake .
$ make
$ ./bin1 # src1: 100
$ ./bin2 # src2: 200
```

ここで、`compile_options()`コマンドは、ディレクトリプロパティの`COMPILE_OPTIONS`を設定するコマンドなのに、なぜターゲットプロパティでしか利用できない Generator Expressions が使えるのか、という疑問があります。これは、ディレクトリプロパティの`COMPILE_OPTIONS`を設定すると、それ以後に定義されたターゲットの`COMPILE_OPTIONS`プロパティの初期値となるからです。そして、ディレクトリプロパティの`COMPILE_OPTIONS`自体は利用されません。これは、`INCLUDE_DIRECTORIES`プロパティについても同様です。この2つのプロパティは Generator Expressions と特に相性がよいでしょう。

## おわりに

以上、Generator Expressions でした。Generator Expressions はクセのある機能ですが、うまく使うと便利でしょう。

明日は、mrk_21 さんの『CMake: CTest』です。