はじめに
みなさん、こんにちは。今回は、コマンド定義について書いていきます。
function()
コマンド
独自のコマンドを定義するには、function()
コマンドを使います。このfunction()
コマンドは、endfunction()
コマンドとセットで使います。構文は次のようになっています。
function(<name> [arg1 [arg2 [arg3 ...]]]) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... endfunction(<name>)
function()
コマンドの<name>
にはコマンド名、arg1 ...
には必須の引数名を渡します。また、endfunction()
コマンドの引数は、おそらくラベル以上の意味はないので記述する必要はありません。
コマンド内の特殊な変数
定義されたコマンド内では、以下の特殊な変数が使用できます。
ARGC
コマンド呼び出し時に渡された、引数の数です。
ARGV
コマンド呼び出し時に渡された、すべての引数のリストです。
ARGV<n>
コマンド呼び出し時に渡された、<n>
番目の引数です。<n>
は0
から始まります。しかし、後述する理由により、この変数は使ってはなりません。おもわぬトラブルに巻き込まれます。
ARGN
コマンド呼び出し時に渡された、必須ではない引数のリストです。
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()
コマンドを呼び出すと、現在のコマンドの実行を中断し、呼び出し元に戻ることができます。
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()
コマンドとセットで使用し、構文は次のようになっています。
macro(<name> [arg1 [arg2 [arg3 ...]]]) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... endmacro(<name>)
macro()
コマンドの<name>
にはコマンド名、arg1 ...
には必須の引数名を渡します。endmacro()
コマンドの引数は、おそらくラベル以上の意味はないので記述する必要はありません。
macro()
コマンドで定義されたコマンド内では、function()
コマンドの時と同様に、ARGV
などの特殊な変数が使用でき、任意の場所でreturn()
コマンドを呼び出すことで中断できます。
macro()
コマンドとfunction()
コマンドの違い
ここまで見ると、macro()
コマンドの動作はfunction()
コマンドと全く同じに見えます。しかし、macro()
コマンドで定義されたコマンドは、以下に示す点でfunction()
コマンドによって定義されたコマンドとは、決定的に異なります。
スコープを生成しない
function()
コマンドで定義したコマンドは、呼び出されるとスコープを生成していました。しかし、macro()
コマンドで定義したコマンドはスコープを生成しません。よって、コマンド内での変数操作は、呼び出し元に影響します。
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()
コマンドを使用すると、処理を中断できますが、呼び出し元の処理も中断してしまいます。
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()
コマンドで定義されたコマンドの引数は変数ではありません。これらは、変数参照の形${}
になっている部分を呼び出し時の値で置換しているだけです。よって、以下の例では想定とは異なる動作をします。
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: コマンド引数の置換
与えられたコマンド引数を元に、変数参照している引数をそれぞれ置換します。
set(value 2)
message("func1 value: x")
foreach(v IN LISTS ARGV)
message("func1 ARGV element: ${v}")
endforeach()
ステップ2: コマンド呼び出し部の置換
func1()
コマンド呼び出し部分を、ステップ1で処理したコード片で置換します。
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()
を呼び出してみましょう。
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引数に値を渡すと挙動を変える、など)を参照するのに使用すると思いますが、このような仕様では使い物になりません。トラブルの元なので使うのをやめましょう。代替として以下のようにするとよいでしょう。
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()
コマンドで定義したコマンドの引数が変数ではないからだと思われます。
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: キャッシュ変数と環境変数』です。