前回の投稿で説明したように、すべての CMake ベースのプロジェクトには CMakeLists.txt という名前のスクリプトが含まれていなければなりません。 このスクリプトはターゲットを定義しますが、サード パーティ ライブラリの検索や C++ ヘッダー ファイルの生成など、他の多くのことを行うことも可能です。 CMake スクリプトには多くの柔軟性があります。

外部ライブラリを統合するたびに、また、別のプラットフォームのサポートを追加するときに、しばしばスクリプトを編集する必要があります。 ドキュメントがかなり散らばっているので、言語をよく理解しないまま長い間 CMake スクリプトを編集していましたが、やがて物事が理解できるようになりました。 この投稿の目標は、できるだけ早く同じポイントに到達することです。

この投稿では、CMake の組み込みコマンドのすべてをカバーしませんが、この言語の構文とプログラミング モデルへのかなり完全なガイドとなります。

Hello World

以下の内容で hello.txt というファイルを作成すると、cmake -P hello.txt を使ってコマンドラインから実行することができます。 (-P オプションは与えられたスクリプトを実行しますが、ビルドパイプラインは生成されません)。

$ cmake -P hello.txtHello world!

All Variables Are Strings

CMake では、すべての変数が文字列です。 文字列リテラル内の変数は、${}で囲むことで代用することができます。 これは変数参照と呼ばれる。 hello.txt を以下のように修正する:

message("Hello ${NAME}!") # Substitute a variable into the message

さて、cmake コマンドラインで -D オプションを使って NAME を定義すると、スクリプトはそれを使用する:

$ cmake -DNAME=Newman -P hello.txtHello Newman!

変数が未定義の場合、デフォルトは空の文字列になる:

$ cmake -P hello.txtHello !

スクリプト内で変数を定義するには、set コマンドを使ってください。 最初の引数は代入する変数の名前、2番目の引数はその値です。

set(THING "funk")message("We want the ${THING}!")

引数の周りの引用符は、引数にスペースや変数参照がない限り、省略可能です。 例えば、上の1行目に set("THING" funk) と書いても同じ意味になります。 ほとんどの CMake コマンド(後述の ifwhile を除く)において、このような引数を引用するかどうかは、単にスタイルの問題である。

You Can Simulate a Data Structure using Prefixes

CMake はクラスを持ちませんが、同じプレフィックスで始まる名前を持つ変数のグループを定義することにより、データ構造をシミュレートすることが可能です。 そして、ネストされた ${} 変数参照を使用して、そのグループの変数を検索することができます。 例えば、次のスクリプトは “John Smith lives at 123 Fake St.”:

set(JOHN_NAME "John Smith")set(JOHN_ADDRESS "123 Fake St")set(PERSON "JOHN")message("${${PERSON}_NAME} lives at ${${PERSON}_ADDRESS}.")

設定する変数名で変数参照を使うこともできます。 例えば、PERSON の値がまだ “JOHN” である場合、以下は変数 JOHN_NAME に “John Goodman” を設定します:

set(${PERSON}_NAME "John Goodman")

Every Statement is a Command

CMake において、すべてのステートメントは文字列引数のリストを取り、戻り値を持たないコマンドである。 引数は、(引用符で囲まれていない)スペースで区切られています。 すでに見てきたように、set コマンドはファイルスコープで変数を定義します。

別の例として、CMake には算術を実行する math コマンドがあります。 最初の引数は EXPR でなければならず、2 番目の引数は代入する変数の名前、3 番目の引数は評価する式で、すべて文字列です。 以下の 3 行目では、CMake は MY_SUM の文字列値を math に渡す前に、囲んだ引数に代入していることに注意してください。

math(EXPR MY_SUM "1 + 1") # Evaluate 1 + 1; store result in MY_SUMmessage("The sum is ${MY_SUM}.")math(EXPR DOUBLE_SUM "${MY_SUM} * 2") # Multiply by 2; store result in DOUBLE_SUMmessage("Double that is ${DOUBLE_SUM}.")

あなたが必要とするほとんどすべてのことに対して CMake コマンドがあります。 string コマンドでは、正規表現の置換を含む高度な文字列操作を行うことができます。 file コマンドはファイルの読み書きやファイルシステムのパスを操作できます。

Flow Control Commands

フロー制御文もコマンドのひとつです。 if/endif コマンドは、囲んだコマンドを条件付きで実行する。 空白は重要ではないが、可読性のために囲まれたコマンドをインデントするのが一般的である。

if(WIN32) message("You're running CMake on Windows.")endif()

CMake には while/endwhile コマンドもあり、期待通り、条件が真である限り、囲まれたコマンドを繰り返す。 以下は、100万までのすべてのフィボナッチ数を表示するループです:

set(A "1")set(B "1")while(A LESS "1000000") message("${A}") # Print A math(EXPR T "${A} + ${B}") # Add the numeric values of A and B; store result in T set(A "${B}") # Assign the value of B to A set(B "${T}") # Assign the value of T to Bendwhile()

CMake の ifwhile の条件は、他の言語と同じようには書かれていません。 例えば、数値比較を行うには、上記のように文字列の引数として LESS を指定する必要があります。 ドキュメントには、有効な条件の書き方が説明されています。

ifwhile は他の CMake コマンドと異なり、変数名を引用符なしで指定した場合、その変数の値を使用するコマンドになっています。 上記のコードでは、while("${A}" LESS "1000000")の代わりにwhile(A LESS "1000000")と記述することでこの動作を利用していますが、どちらの形式も同等です。

Lists are just Semicolon-Delimited Strings

CMake は引用符で囲まれていない引数に対して特別な置換ルールを持っています。 引数全体が引用符なしの変数参照で、その変数の値にセミコロンが含まれている場合、CMake はその値をセミコロンで分割し、複数の引数を囲むコマンドに渡します。 例えば、以下は math:

set(ARGS "EXPR;T;1 + 1")math(${ARGS}) # Equivalent to calling math(EXPR T "1 + 1")

に3つの引数を渡します。一方、引用符で囲まれた引数は、代入後でも複数の引数に分割されることはありません。 CMake は常に引用された文字列をセミコロンを残して単一の引数として渡します:

set(ARGS "EXPR;T;1 + 1")message("${ARGS}") # Prints: EXPR;T;1 + 1

もし二つ以上の引数が set コマンドに渡された場合、それらはセミコロンで結合され、指定された変数に代入されます。

set(MY_LIST These are separate arguments)message("${MY_LIST}") # Prints: These;are;separate;arguments

このようなリストは list コマンドで操作することができます:

set(MY_LIST These are separate arguments)list(REMOVE_ITEM MY_LIST "separate") # Removes "separate" from the listmessage("${MY_LIST}") # Prints: These;are;arguments

foreach/endforeach コマンドは複数の引数を受け付けます。

foreach(ARG These are separate arguments) message("${ARG}") # Prints each word on a separate lineendforeach()

foreachに引用符で囲まれていない変数への参照を渡すことで、リストを反復処理することができます。 他のコマンドと同様に、CMake は変数の値を分割し、コマンドに複数の引数を渡します。

foreach(ARG ${MY_LIST}) # Splits the list; passes items as arguments message("${ARG}") # Prints each item on a separate lineendforeach()

Functions Run In Their Own Scope; Macros Don’t

CMakeでは、function/endfunction コマンドのペアを使用して関数を定義することが可能です。 以下は、引数の数値を 2 倍にし、結果を表示するものです。

function(doubleIt VALUE) math(EXPR RESULT "${VALUE} * 2") message("${RESULT}")endfunction()doubleIt("4") # Prints: 8

関数は独自のスコープで実行されます。 関数で定義された変数が呼び出し元のスコープを汚染することはない。 値を返したい場合は、関数に変数名を渡し、特別な引数 PARENT_SCOPEset コマンドを呼び出す:

function(doubleIt VARNAME VALUE) math(EXPR RESULT "${VALUE} * 2") set(${VARNAME} "${RESULT}" PARENT_SCOPE) # Set the named variable in caller's scopeendfunction()doubleIt(RESULT "4") # Tell the function to set the variable named RESULTmessage("${RESULT}") # Prints: 8

同様に、macro/endmacro コマンドのペアは、マクロを定義する。 関数とは異なり、マクロは呼び出し元と同じスコープで実行される。 したがって、マクロ内部で定義されたすべての変数は呼び出し元のスコープに設定される。 前の関数を次のように置き換えることができる:

macro(doubleIt VARNAME VALUE) math(EXPR ${VARNAME} "${VALUE} * 2") # Set the named variable in caller's scopeendmacro()doubleIt(RESULT "4") # Tell the macro to set the variable named RESULTmessage("${RESULT}") # Prints: 8

関数とマクロはどちらも任意の数の引数を受け付ける。 名前のない引数はリストとして、ARGNという特別な変数を通して関数に公開される。 以下は、受け取ったすべての引数を 2 倍にし、それぞれを別の行に表示する関数です:

function(doubleEach) foreach(ARG ${ARGN}) # Iterate over each argument math(EXPR N "${ARG} * 2") # Double ARG's numeric value; store result in N message("${N}") # Print N endforeach()endfunction()doubleEach(5 6 7 8) # Prints 10, 12, 14, 16 on separate lines

Including Other Scripts

CMake 変数は、ファイルスコープで定義されます。 include コマンドは、呼び出したスクリプトと同じスコープで他の CMake スクリプトを実行します。 これは、C/C++ の #include 命令とよく似ています。 通常、呼び出し側のスクリプトで共通の関数やマクロのセットを定義するために使用されます。

find_packageコマンドは、Find*.cmakeという形式のスクリプトを探し、同じスコープで実行することもできる。 このようなスクリプトは、外部ライブラリの検索によく使われる。 例えば、検索パスに FindSDL2.cmake という名前のファイルがある場合、find_package(SDL2)include(FindSDL2.cmake) と等価である。 (find_package コマンドを使用する方法はいくつかあり、これはそのうちの 1 つであることに注意してください。)

CMake の add_subdirectory コマンドは、一方で新しいスコープを作成し、指定したディレクトリから CMakeLists.txt というスクリプトをその新しいスコープに実行します。 通常、ライブラリや実行ファイルなど、CMake ベースの別のサブプロジェクトを呼び出し中のプロジェクトに追加するために使用します。 サブプロジェクトによって定義されたターゲットは、特に指定がない限り、ビルドパイプラインに追加されます。 サブプロジェクトのスクリプトで定義された変数は、set コマンドの PARENT_SCOPE オプションが使用されない限り、親のスコープを汚染しません。

例として、Turf プロジェクトで CMake を実行した場合のスクリプトの一部を以下に示します:

Getting and Setting Properties

CMake スクリプトでは add_executableadd_library または add_custom_target コマンドを使用してターゲットを定義します。 ターゲットが作成されると、get_property および set_property コマンドを使用して操作できるプロパティを持つようになります。 変数と異なり、ターゲットはサブディレクトリで定義されていても、すべてのスコープで表示されます。 ターゲットのプロパティはすべて文字列である。

add_executable(MyApp "main.cpp") # Create a target named MyApp# Get the target's SOURCES property and assign it to MYAPP_SOURCESget_property(MYAPP_SOURCES TARGET MyApp PROPERTY SOURCES)message("${MYAPP_SOURCES}") # Prints: main.cpp

その他、LINK_LIBRARIESINCLUDE_DIRECTORIESCOMPILE_DEFINITIONS などのターゲットプロパティがある。 これらのプロパティは、target_link_librariestarget_include_directoriestarget_compile_definitionsコマンドによって間接的に変更される。 スクリプトの最後に、CMake はこれらのターゲット プロパティを使用してビルド パイプラインを生成します。

他の CMake エンティティ用のプロパティもあります。 すべてのファイル スコープにディレクトリ プロパティのセットがあります。 すべてのスクリプトからアクセス可能なグローバル プロパティのセットがあります。 そして、すべての C/C++ ソース ファイルのソース ファイルのプロパティのセットがあります。

Articles

コメントを残す

メールアドレスが公開されることはありません。