CMakeユーザーはcmake -Eを活用せよ
CMakeでちょっと複雑なソフトを作っていると、ビルド時に単にソースをコンパイルするだけでなく、ちょっとしたコマンド実行やファイル操作をしたくなる場合がある。何らかのスクリプトで設定ファイルからソースコードを生成したり、あるファイルを必要な場所にコピーしたりなど。
そういう場合にはadd_custom_commandやadd_custom_targetを使えば任意のシェルコマンドを実行することができる。もちろんLinuxならcpやrm、Windowsであればcopyやdelが実行できるのだが、こうしたプラットフォーム依存な処理をするのはCMakeの意義に照らしてあまり良いことではない。
そこで出てくるのがcmake -Eだ。これを使うとif(WIN32)みたいな不格好な場合分けをせずに済む。
add_custom_commandおよびadd_custom_targetについて
add_custom_targetは指定のコマンドを実行するだけのCMakeターゲットを定義できる。add_executableやadd_libraryのようなビルド処理はせず、指定のコマンドの実行だけが行われる。ファイルの自動生成のような前処理のほか、デバッグ用に実行ファイルを実行するだけのターゲットなどを定義するのにも有用だ。
add_custom_commandの場合は2通りの使い方がある。1つはコマンドとそれによって自動生成されるファイルを定義するものだ。これで例えば自動生成されるソースファイルを定義して、それをadd_executableやtarget_sourcesで指定すれば、依存関係まで自動で認識してやってくれる。
もう1つはあるターゲットのビルドの前か後に任意のコマンドを実行するもので、これも前処理や後処理のために使いやすい。
基本的には独立したターゲットである必要性が薄い限り、add_custom_commandの方が使い勝手が良いと思う。ただし紐づけるターゲットの定義(add_executableなど)は同じディレクトリのCMakeListでなければならないことに注意。
参考:
- https://cmake.org/cmake/help/latest/command/add_custom_target.html
- https://cmake.org/cmake/help/latest/command/add_custom_command.html
ファイルパスの取り扱い
ファイルパスの仕組みというのはOSごとに異なるものである。というかほぼほぼWindowsが特殊というのが実情だが、世界がPOSIX一色であることを前提に開発する態度が自分は気に入らないのでOSごとに異なるものと考えて開発すべきであると主張する。
CMakeはOSごとの違いに対してそれを吸収するため、「cmake-style path」という概念を定義している。これはつまり、プラットフォームに依存しないCMake独自方式のパスの持ち方ということだ。CMake変数などでファイルパスを持つ場合は基本的にこの形で保持される。実体は区切り文字に/を使うほぼほぼUnixスタイルのパスだが、WindowsのC:のようなドライブ指定文字列も受け入れるのが特徴だ。
add_executableをはじめとするCMakeコマンドにおいてファイルパスを指定する場合はcmake-style pathで指定すれば良いのだが、add_custom_commandなどでコマンドを実行するときの引数としてファイルパスを指定したい場合はそうもいかない。cmake-style pathをOSネイティブなパス文字列に変換する必要がある。
具体的な方法としては、fileコマンドでネイティブパス文字列に変換することができる。ただしこの場合はconfiguration時になるので、build時に行いたい場合はgenerator expressionを利用する。$<SHELL_PATH:...>を使うことで変換をかけることができる。
参考:
- https://cmake.org/cmake/help/latest/command/file.html#to-native-path
- https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:SHELL_PATH
cmake -E
この記事の本題。
一般的なCLIツールは、あまりプラットフォームに依存せず同じコマンドで実行できることが多い。だからadd_custom_commandにそのまま乗せられる。せいぜい上で解説したようなパスの変換を入れるくらいだ。しかし逆に、ファイルのコピーだとか削除みたいな基本的なコマンドはプラットフォームに依存して異なる場合が多い。cpとかrmとかをWindowsのcmd.exeの上で使うことはできない。
そこで出てくるのがcmake -Eによるコマンド群だ。これはCMakeコマンドモードというもので、いくつかの基本的なコマンドをOSに依存しない形で実行することができる。
例としてファイルのコピーを挙げると、
cmake -E copy (from...) (to)
のような形で実行することができる。cpを使うべきかcopyを使うべきかといったOSによる場合分け処理は必要ない。さらにもう一つの利点として、cmake-style pathを用いることができるので、変換のためのfile(TO_NATIVE_PATH)も$<SHELL_PATH:...>も必要ない。
add_custom_commandなどを使うときは積極的にこれを活用していくとよい。
add_custom_command(TARGET app
COMMAND ${CMAKE_COMMAND} -E copy ${FROM} ${TO}
)
${CMAKE_COMMAND}というのはCMakeのバイナリパスが入ったCMake変数だ。cmakeからcmakeを実行するときにはこれを使った方が良いらしい。
できること
cmake -Eと打って実行すれば出てくるのでそれを見た方が早いのだが、一応ここにも転記しておく。CMakeバージョン4.1.2で確認。
Available commands:
capabilities - Report capabilities built into cmake in JSON format
cat [--] <files>... - concat the files and print them to the standard output
chdir dir cmd [args...] - run command in a given directory
compare_files [--ignore-eol] file1 file2
- check if file1 is same as file2
copy <file>... destination - copy files to destination (either file or directory)
copy_directory <dir>... destination - copy content of <dir>... directories to 'destination' directory
copy_directory_if_different <dir>... destination - copy changed content of <dir>... directories to 'destination' directory
copy_if_different <file>... destination - copy files if it has changed
echo [<string>...] - displays arguments as text
echo_append [<string>...] - displays arguments as text but no new line
env [--unset=NAME ...] [NAME=VALUE ...] [--] <command> [<arg>...]
- run command in a modified environment
environment - display the current environment
make_directory <dir>... - create parent and <dir> directories
md5sum <file>... - create MD5 checksum of files
sha1sum <file>... - create SHA1 checksum of files
sha224sum <file>... - create SHA224 checksum of files
sha256sum <file>... - create SHA256 checksum of files
sha384sum <file>... - create SHA384 checksum of files
sha512sum <file>... - create SHA512 checksum of files
remove [-f] <file>... - remove the file(s), use -f to force it (deprecated: use rm instead)
remove_directory <dir>... - remove directories and their contents (deprecated: use rm instead)
rename oldname newname - rename a file or directory (on one volume)
rm [-rRf] [--] <file/dir>... - remove files or directories, use -f to force it, r or R to remove directories and their contents recursively
sleep <number>... - sleep for given number of seconds
tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]
- create or extract a tar or zip archive
time command [args...] - run command and display elapsed time
touch <file>... - touch a <file>.
touch_nocreate <file>... - touch a <file> but do not create it.
create_symlink old new - create a symbolic link new -> old
create_hardlink old new - create a hard link new -> old
true - do nothing with an exit code of 0
false - do nothing with an exit code of 1
Available on Windows only:
delete_regv key - delete registry value
env_vs8_wince sdkname - displays a batch file which sets the environment for the provided Windows CE SDK installed in VS2005
env_vs9_wince sdkname - displays a batch file which sets the environment for the provided Windows CE SDK installed in VS2008
write_regv key value - write registry value
個人的にはcopy,rmあたりをよく使っている。copyは指定の階層のディレクトリまで自動で作ってくれる点が便利だ。ビルドを高速化したい場合はcopy_if_different系列を使うと良いと思われる。またデバッグ用途でecho,environmentあたりをたまに使っている。基本的なコマンドはおよそ揃っていると言っていいだろう。
気を付けるべき点
どうやらワイルドカード(*)を使うと不具合が起こる場合があるらしい。全てのファイルに何かしたい場合はfile(GLOB)またはfile(GLOB_RECURSE)などで収集してforeachで回した方が良いと思われる。