CMake: コマンド定義

## はじめに

みなさん、こんにちは。今回は、コマンド定義について書いていきます。

## `function()`コマンド

独自のコマンドを定義するには、`function()`コマンドを使います。この`function()`コマンドは、`endfunction()`コマンドとセットで使います。構文は次のようになっています。

> ```cmake
> function(<name> [arg1 [arg2 [arg3 ...]]])
>   COMMAND1(ARGS ...)
>   COMMAND2(ARGS ...)
>   ...
> endfunction(<name>)
> ```
> <cite>[function — CMake 3.0.2 Documentation](http://www.cmake.org/cmake/help/v3.0/command/function.html#function)</cite>

`function()`コマンドの`<name>`にはコマンド名、`arg1 ...`には必須の引数名を渡します。また、`endfunction()`コマンドの引数は、おそらくラベル以上の意味はないので記述する必要はありません。

## コマンド内の特殊な変数

定義されたコマンド内では、以下の特殊な変数が使用できます。

### `ARGC`

コマンド呼び出し時に渡された、引数の数です。

### `ARGV`

コマンド呼び出し時に渡された、すべての引数のリストです。

### `ARGV<n>`

コマンド呼び出し時に渡された、`<n>`番目の引数です。`<n>`は`0`から始まります。しかし、後述する理由により、この変数は使ってはなりません。おもわぬトラブルに巻き込まれます。

### `ARGN`

コマンド呼び出し時に渡された、必須ではない引数のリストです。

---

```cmake
function(func arg1 arg2)
  message("arg1: ${arg1}")
  message("arg2: ${arg2}")
  message("")
  message("ARGC: ${ARGC}")
  message("ARGV: ${ARGV}")
  message("ARGV0: ${ARGV0}")
  message("ARGV1: ${ARGV1}")
  message("ARGN: ${ARGN}")
endfunction()

func(a b) #[[
  arg1: a
  arg2: b
  
  ARGC: 2
  ARGV: a;b
  ARGV0: a
  ARGV1: b
  ARGN: 
]]

func(a b c d) #[[
  arg1: a
  arg2: b
  
  ARGC: 4
  ARGV: a;b;c;d
  ARGV0: a
  ARGV1: b
  ARGN: c;d
]]
```

## コマンドの中断

コマンド内の任意の場所で`return()`コマンドを呼び出すと、現在のコマンドの実行を中断し、呼び出し元に戻ることができます。

```cmake
function(func)
  message("start func command")
  return()
  message("end func command")
endfunction()

func() #[[
  start func command
]]
```

また補足として、この`return()`コマンドは、ディレクトリスコープ内でも使用することができ、この場合、親スコープの`add_subdirectory()`コマンドの呼び出し部分直後に処理が移ります。なお、親スコープが無い場合――つまりプロジェクトルートのディレクトリスコープで使用すると、CMakeスクリプトの実行を中断します。

## `macro()`コマンド

`macro()`コマンドは、`function()`コマンドと同様に独自のコマンドを定義することができます。`macro()`コマンドは`endmacro()`コマンドとセットで使用し、構文は次のようになっています。

> ```cmake
> macro(<name> [arg1 [arg2 [arg3 ...]]])
>   COMMAND1(ARGS ...)
>   COMMAND2(ARGS ...)
>   ...
> endmacro(<name>)
> ```
> <cite>[macro — CMake 3.0.2 Documentation](http://www.cmake.org/cmake/help/v3.0/command/macro.html#macro)</cite>


`macro()`コマンドの`<name>`にはコマンド名、`arg1 ...`には必須の引数名を渡します。`endmacro()`コマンドの引数は、おそらくラベル以上の意味はないので記述する必要はありません。

`macro()`コマンドで定義されたコマンド内では、`function()`コマンドの時と同様に、`ARGV`などの特殊な変数が使用でき、任意の場所で`return()`コマンドを呼び出すことで中断できます。

## `macro()`コマンドと`function()`コマンドの違い

ここまで見ると、`macro()`コマンドの動作は`function()`コマンドと全く同じに見えます。しかし、`macro()`コマンドで定義されたコマンドは、以下に示す点で`function()`コマンドによって定義されたコマンドとは、決定的に異なります。

### スコープを生成しない

`function()`コマンドで定義したコマンドは、呼び出されるとスコープを生成していました。しかし、`macro()`コマンドで定義したコマンドはスコープを生成しません。よって、コマンド内での変数操作は、呼び出し元に影響します。

```cmake
macro(func)
  message("value1: ${value1}")
  message("value2: ${value2}")
  message("")
  set(value1 2)
  set(value2 2)
  message("value1: ${value1}")
  message("value2: ${value2}")
endmacro()

set(value1 1)
message("value1: ${value1}") # value1: 1
message("value2: ${value2}") # value2: 

func() #[[
  value1: 1
  value2: 
  
  value1: 2
  value2: 2
]]

message("value1: ${value1}") # value1: 2
message("value2: ${value2}") # value2: 2
```

### `return()`コマンドを使用すると呼び出し元の処理も中断する

`macro()`コマンドによって定義したコマンドの内部で、`return()`コマンドを使用すると、処理を中断できますが、呼び出し元の処理も中断してしまいます。

```cmake
macro(func1)
  message("start func1 command")
  return()
  message("end func1 command")
endmacro()

function(func2)
  message("start func2 command")
  func1()
  message("end func2 command")
endfunction()

func2() #[[
  start func2 command
  start func1 command
]]
```

### コマンド引数は変数ではない

実は、`macro()`コマンドで定義されたコマンドの引数は変数ではありません。これらは、変数参照の形`${}`になっている部分を呼び出し時の値で置換しているだけです。よって、以下の例では想定とは異なる動作をします。

```cmake
macro(func1 value)
  set(value 2)
  message("func1 value: ${value}")
  foreach(v IN LISTS ARGV)
    message("func1 ARGV element: ${v}")
  endforeach()
endmacro()

function(func2)
  set(value 1)
  message("func2 ARGV: ${ARGV}")
  message("func2 value: ${value}")
  
  func1(x y z)
  
  message("func2 value: ${value}")
endfunction()

func2(a b c) #[[
  想定:
    func2 ARGV: a;b;c
    func2 value: 1
    func1 value: 2
    func1 ARGV element: 2
    func1 ARGV element: y
    func1 ARGV element: z
    func2 value: 1
  
  実際:
    func2 ARGV: a;b;c
    func2 value: 1
    func1 value: x
    func1 ARGV element: a
    func1 ARGV element: b
    func1 ARGV element: c
    func2 value: 2
]]
```

## `macro()`コマンドの謎

上記で`function()`コマンドとの差異を見てきましたが、`macro()`コマンドで定義されたコマンドは不可思議な動作をしています。これはいったいなんなのでしょうか？

実は`macro()`コマンドは、コマンド定義というより、C言語の関数マクロのようなものです。つまり、『コマンド引数は変数ではない』の`func1()`コマンドの呼び出し部分は、以下のような手順で、ただのコード片に置換されていたのです。

### ステップ1: コマンド引数の置換

与えられたコマンド引数を元に、変数参照している引数をそれぞれ置換します。

```cmake
set(value 2)
message("func1 value: x")
foreach(v IN LISTS ARGV)
  message("func1 ARGV element: ${v}")
endforeach()
```

### ステップ2: コマンド呼び出し部の置換

`func1()`コマンド呼び出し部分を、ステップ1で処理したコード片で置換します。

```cmake
function(func2)
  set(value 1)
  message("func2 ARGV: ${ARGV}")
  message("func2 value: ${value}")
  
  set(value 2)
  message("func1 value: x")
  foreach(v IN LISTS ARGV)
    message("func1 ARGV element: ${v}")
  endforeach()
  
  message("func2 value: ${value}")
endfunction()
```

## 補足: `ARGV<n>`は使ってはならない

コマンド内の特殊な変数において、`ARGV<n>`がありますが、この変数の使用はやめるべきです。というのも、ある条件下でおもわぬ挙動をするからです。以下の例を見てください。

まず、任意の引数を渡せるコマンド`f1()`を定義し、それを呼び出すコマンド`f2()`を定義します。ここで、`f1()`では`ARGV`などの特殊な変数を参照しています。では、`f2()`を呼び出してみましょう。

```cmake
function(f1)
  message("f1 ARGC: ${ARGC}")
  message("f1 ARGV: ${ARGV}")
  message("f1 ARGN: ${ARGN}")
  message("f1 ARGV0: ${ARGV0}")
  message("f1 ARGV1: ${ARGV1}")
endfunction()

function(f2)
  f1()
endfunction()

f2() #[[
  f1 ARGC: 0
  f1 ARGV: 
  f1 ARGN: 
  f1 ARGV0: 
  f1 ARGV1: 
]]
```

`f2()`内部で呼び出している`f1()`には引数を指定していないので、`ARGC`は 0 で`ARGV`・`ARGN`は空です。ですので当然`ARGV0`・`ARGV1`も空となります。しかし、`f2()`に引数を与えた時、おかしな挙動をします。

```
f2(a b) #[[
  f1 ARGC: 0
  f1 ARGV: 
  f1 ARGN: 
  f1 ARGV0: a
  f1 ARGV1: b
]]
```

なんと、`f1()`に引数を渡していないのに`ARGV0`・`ARGV1`がそれぞれ`f2()`コマンドに渡した引数の値になってしまっています。しかも、`ARGC`・`ARGV`・`ARGN`は引数を渡していない時の値のままなのに、です！

これは憶測ですが、おそらく`ARGV<n>`などの変数は、`<n>`番目の引数が存在しない場合には設定されません。つまり、普通の変数と同じく親スコープの値を引き継ぎます。

`ARGV<n>`のような変数は、ふつうオプション値(普段は引数を渡さないが、第1引数に値を渡すと挙動を変える、など)を参照するのに使用すると思いますが、このような仕様では使い物になりません。トラブルの元なので使うのをやめましょう。代替として以下のようにするとよいでしょう。

```cmake
function(f1)
  set(args ${ARGV} "" "")
  list(GET args 0 arg0)
  list(GET args 1 arg1)
  unset(args)
  
  message("f1 arg0: ${arg0}")
  message("f1 arg1: ${arg1}")
endfunction()

function(f2)
  f1()
endfunction()

f1() #[[
  f1 arg0: 
  f1 arg1: 
]]
f1(a) #[[
  f1 arg0: a
  f1 arg1: 
]]
f1(a b) #[[
  f1 arg0: a
  f1 arg1: b
]]
f2(a b) #[[
  f1 arg0: 
  f1 arg1: 
]]
```

しかし、`macro()`で定義したコマンド内の`ARGV<n>`は、このような挙動はしません。これは、`macro()`コマンドで定義したコマンドの引数が変数ではないからだと思われます。

```cmake
macro(m1)
  message("m1 ARGC: ${ARGC}")
  message("m1 ARGV: ${ARGV}")
  message("m1 ARGN: ${ARGN}")
  message("m1 ARGV0: ${ARGV0}")
  message("m1 ARGV1: ${ARGV1}")
endmacro()

macro(m2)
  m1()
endmacro()

m2() #[[
  f1 ARGC: 0
  f1 ARGV: 
  f1 ARGN: 
  f1 ARGV0: 
  f1 ARGV1: 
]]
m2(a b) #[[
  f1 ARGC: 0
  f1 ARGV: 
  f1 ARGN: 
  f1 ARGV0: 
  f1 ARGV1: 
]]
```

## おわりに

以上、コマンド定義でした。上で述べたように`macro()`コマンドは、その動作仕様の詳細を知らないと、呼び出し元スコープの汚染・不可思議な挙動などを引き起こすので、使用するのは控えたほうがよいです。たいていの場合は、`function()`コマンドで問題ないはずです。また、`ARGV<n>`変数はトラブルの元なので使用するのをやめましょう。

明日は、mrk_21 さんの『CMake: キャッシュ変数と環境変数』です。