CMake: 条件分岐

## はじめに

みなさん、こんにちは。今回は条件分岐について書いていきます。

## 構文

条件分岐には`if()`コマンドを使います。この`if()`コマンドは、`elseif()`・`else()`・`endif()`コマンドとセットで使用します。構文は下記のとおりです。

> ```cmake
> if(expression)
>   # then section.
>   COMMAND1(ARGS ...)
>   COMMAND2(ARGS ...)
>   ...
> elseif(expression2)
>   # elseif section.
>   COMMAND1(ARGS ...)
>   COMMAND2(ARGS ...)
>   ...
> else(expression)
>   # else section.
>   COMMAND1(ARGS ...)
>   COMMAND2(ARGS ...)
>   ...
> endif(expression)
> ```
> <cite>[if — CMake 3.0.2 Documentation](http://www.cmake.org/cmake/help/v3.0/command/if.html#if)</cite>

`elseif()`コマンドと`else()`コマンドは省略可能です。また、`else()`コマンドと`endif()`コマンドの引数は、おそらくラベル以上の意味を持たないので記述する必要はありません。

## 式の種類

さて、`if()`コマンドおよび`elseif()`コマンドの引数には、そのブロックが実行される条件式を記述します。この条件式には以下に示す種類があります。

### ブール定数

CMakeの値はすべて文字列型ですが、この条件式の文脈では以下の値がBoolean値として認識されます。また、これらの値は大文字・小文字の区別がされません。

#### 真と解釈される値

* `0`でない数値
* `ON`
* `YES`
* `TRUE`
* `Y`

```cmake
if(1)
if(TRUE)
if(on)
if(Yes)
```

####  偽と解釈される値

* 空文字列
* `0`
* `OFF`
* `NO`
* `FALSE`
* `N`
* `IGNORE`
* `NOTFOUND`
* 末尾が`-NOTFOUND`である文字列

```cmake
if(0)
if(FALSE)
if(off)
if(No)
if(HogePackage-NotFound)
```

### ブール演算子

論理積を表す`AND`、論理和を表す`OR`、否定を表す`NOT`が使えます。また、これらはすべて大文字でなければなりません。

```cmake
if(true AND true)
if(true OR false)
if(NOT false)
```

### 括弧`()`内の式

複雑な条件式を構築するために使います。
括弧`()`でくくられた式は一番最初に評価されます。

```cmake
if(true AND (false OR true))
```

### テスト

変数が定義済みかどうかのチェックや数値の比較などを行います。

```cmake
if(DEFINED variable)
if(1 LESS 2)
```

## 式の優先順位

上で示した各種式の優先順位は以下のとおりとなっています。

1. 括弧`()`で囲まれた式
1. 単項テスト
    
    `EXISTS`・`DEFINED`・`TARGET`など
    
1. 二項テスト
    
    `EQUAL`・`LESS`・`GREATER`など
    
1. `NOT`ブール演算子
1. `AND`ブール演算子
1. `OR`ブール演算子

## テストの種類

テストには以下に示す種類があります。

### 対象が存在するか

対象が存在するかどうかを判定する単項テストです。

* `DEFINED <variable-name>`
* `COMMAND <command-name>`
* `POLICY <policy-id>`
* `TARGET <target-name>`
* `EXISTS <path>`

```cmake
if(DEFINED value)    # value という変数がセットされているかどうか
if(COMMAND message)  # message というコマンドが存在するかどうか
if(POLICY CMP0011)   # CMP0011 というポリシーが存在するかどうか
if(TARGET test)      # test というターゲットが定義されているかどうか
if(EXISTS src/a.cpp) # src/a.cpp というパスが存在しているかどうか
```

### 対象がある性質を満たしているか

対象がある性質を満たしているかどうかを判定する単項テストです。

* `IS_DIRECTORY <path>`
* `IS_SYMLINK <path>`
* `IS_ABSOLUTE <path>`

```cmake
if(IS_DIRECTORY src/)  # src/ がディレクトリかどうか
if(IS_SYMLINK /bin/sh) # /bin/sh がシンボリックリンクかどうか
if(IS_ABSOLUTE /)      # / が絶対パスかどうか
```

### 文字列がパターンに一致するか

`<string> MATCHES <regexp>`

文字列`<string>`が正規表現`<regexp>`にマッチするかどうかを判定する二項テストです。文字列`<string>`がない場合は常に`false`を返します。

```cmake
if("aaaab" MATCHES "^a+b$") # "aaaab" =~ /^a+b$/
```

### 数値を比較

数値の比較を行う二項テストです。

* `<lvalue> EQUAL <rvalue>`
* `<lvalue> LESS <rvalue>`
* `<lvalue> GREATER <rvalue>`

```cmake
if(1 EQUAL 1)   # 1 == 1
if(1 LESS 2)    # 1 < 2
if(2 GREATER 1) # 2 > 1
```

### 文字列を比較

文字列の比較を行う二項テストです。辞書順に基づきます。

* `<lvalue> STREQUAL <rvalue>`
* `<lvalue> STRLESS <rvalue>`
* `<lvalue> STRGREATER <rvalue>`

```cmake
if("a" STREQUAL "a")   # "a" == "a"
if("a" STRLESS "aa")   # "a" < "aa"
if("b" STRGREATER "a") # "b" > "a"
```

### バージョンを比較

バージョンの比較を行う二項テストです。バージョンとは`major[.minor[.patch[.tweak]]]`というフォーマットになっているものです。数値的な順序や辞書順には基づきません。例えば、`0.1`という値と`0.1.0.0`という値は等しいと評価されます。また、`1.19`は`1.2`より大きいと評価されます。

* `<lvalue> VERSION_EQUAL <rvalue>`
* `<lvalue> VERSION_LESS <rvalue>`
* `<lvalue> VERSION_GREATER <rvalue>`

```cmake
if(0.1 VERSION_EQUAL 0.1.0.0)  # 0.1 == 0.1.0.0
if(0.1 VERSION_LESS 0.1.1)     # 0.1 < 0.1.1
if(1.19 VERSION_GREATER 1.2)   # 1.19 > 1.2
```

### ファイルのタイムスタンプを比較

`<file1> IS_NEWER_THAN <file2>`

ファイル`<file1>`とファイル`<file2>`のタイムスタンプの比較を行う二項テストです。`<file1>`が`<file2>`より新しい場合に`true`を返します。また、どちらかのファイルが存在しない場合にも`true`を返します。

```cmake
if(build/my_exe IS_NEWER_THAN /usr/local/bin/my_exe)
```

## 変数名と解釈される値

条件式に記述する値は、歴史的経緯により、次の文脈で変数名であると解釈されることがあります。

* 単一の値
* ブール演算子の左辺値および右辺値
* `MATCHES`テストの左辺値
* `IS_NEWER_THAN`以外の比較テストの左辺値および右辺値

この文脈において、ブール定数に解釈されない値で、かつ名前がその値である変数が存在するとき、それは変数名であると解釈され、変数の値で評価されます。

```cmake
set(value 1) #[[
 true や false といった名前にすると変数名ではなく、
 ブール定数と解釈されてしまうので使用してはいけない。
]]

if(value)                 # if(1)
if(NOT value)             # if(NOT 1)
if(value OR false)        # if(1 OR false)
if(value MATCHES "[0-9]") # if(1 MATCHES "[0-9]")
if(value EQUAL 1)         # if(1 EQUAL 1)
if(2 GREATER value)       # if(2 GREATER 1)
```

## おわりに

以上、条件分岐でした。`if()`コマンドは簡単かと思いきや、歴史的経緯の仕様などが合わさり複雑になっています。そのため、使用には注意が必要です。

明日は、mrk_21 さんの『CMake: ループ』です。