こんにちは。田原です。
今回から良く使うCMakeのコマンドについて解説していきます。まずは最も重要なビルド・ターゲットの定義方法です。ほとんどの場合、実行ファイルとライブラリのビルド・ターゲットの定義を行います。それらの他に自由に生成コマンドを指定できるカスタム・ターゲットの定義についても解説します。各コマンドの詳しい解説は当該コマンド名でググれば結構でてきますので、それらであまり見かけないEXCLUDE_FROM_ALLやALL指定に焦点を当ててみます。
1.ビルド・ターゲットを指定するコマンド3つ
ターゲットを定義する主なコマンドとして次の3つがあります。(前2つに比べると最後のadd_custom_targetの使用頻度は低いですが1つのプロジェクトで1つ~2つ使うことはそこそこあると思います。)
- add_executable
ビルドする実行ファイル(いわゆる.exe)を定義します。
使用するソース・ファイルをこのコマンドで指定します。
リンクするライブラリはtarget_link_librariesを使って指定します。 -
add_library
ビルドするライブラリ(.lib、.dll、.a、.so、.dylibなどなど)を定義します。共有(linux系)/動的リンク(Windows)ライブラリ、静的リンク・ライブラリを指定できます。
使用するソース・ファイルやリンクするライブラリについてはadd_executableと同様です。(ライブラリに他のライブラリをリンクする件は意外に奥が深いのですが、後々解説していきます。ここでは.exeの場合と同様とお考え下さい。) -
add_custom_target
殆どの場合、上述のadd_executableとadd_libraryで足りるのですが、例えばDoxygenなどのコンパイラやリンカ以外のツールで何らかのファイルを生成する場合に用いることが多いです。
Doxygen
FOSS界隈でよく使われるドキュメント作成ツールです。C++のソース・コードの中にDoxygenの書式で書いたコメントなどから自動的にドキュメントを生成するツールです。Theolizerもこれを使っています。関連情報のリンクを自動的に貼ってくれますし、クラス毎のドキュメントなども自動生成してくれるので「リファレンス・マニュアル」を作る際の強い味方です。
2.EXCLUDE_FROM_ALL/ALL指定について
ビルドする際、主なビルド・ターゲットについては「デフォルト」でビルド指定し、いちいちターゲットを指定しないで良いようにすることが多いと思います。
例えば、次のようにしてビルドすると「デフォルト」のビルド・ターゲットが全てビルドされます。
// VC++ > cmake --build . // gcc $ make
では、「デフォルト」のビルド・ターゲットってどうやって指定するのでしょうか?
指定したくなることが少ないためか、調べても意外に分からないので苦労しました。
主にEXCLUDE_FROM_ALL、ALLを使います。Visual Studio専用のEXCLUDE_FROM_DEFAULT_BUILDもあります。
2019/01/06 以下の修正しました。
誤>$ make sample
正>$ make
2-1.add_executable と add_library と EXCLUDE_FROM_ALL
何も指定しなければ「デフォルト」のビルド・ターゲットとなります。
ターゲット名の直後で EXCLUDE_FROM_ALL を指定すると「デフォルト」のビルド・ターゲットから外れます。
add_library(lib EXCLUDE_FROM_ALL lib.cpp lib.h) add_executable(main EXCLUDE_FROM_ALL main.cpp lib.h) target_link_libraries(main lib) # mainがlibをリンクすることの指定
ただし、以下の時はちゃんとビルドされます。
- makeや–buildで明示的にターゲット指定した時
> cmake --build . --target main $ make main
- 他のビルドされるターゲットから依存されている時
例えば、あるライブラリlibをEXCLUDE_FROM_ALL しても、そのライブラリlibを使う実行ファイルmainがビルドされた場合、このライブラリlibもビルドされます。
add_library(lib EXCLUDE_FROM_ALL lib.cpp lib.h) # mainが依存しているのでmainがビルドされる時ビルドされる add_executable(main main.cpp lib.h) # EXCLUDE_FROM_ALLしていないのでデフォルトでビルドされる target_link_libraries(main lib) # mainがlibをリンクすることの指定
> cmake --build . $ make
2-2.add_custom_target と ALL
何も指定しなければ「デフォルト」のビルド・ターゲットではありません。
ターゲット名の直後で ALL を指定すると「デフォルト」のビルド・ターゲットになります。
サンプルは後述します。
2-3.Visual Studio と EXCLUDE_FROM_DEFAULT_BUILD
VC++の場合でも、cmake –build . でビルドする時は、EXCLUDE_FROM_ALL は適切に機能するのですが、Visual Studio上でビルドする時は残念なことに、EXCLUDE_FROM_ALL が機能しません。Visual StudioではEXCLUDE_FROM_ALL相当の機能を実現できないのだと思います。
代わりにEXCLUDE_FROM_DEFAULT_BUILD という指定があります。こちらはちょっと残念な機能です。
EXCLUDE_FROM_DEFAULT_BUILDを指定されたターゲットはVisual Studio上の「ソリューションのビルド」のビルド・ターゲットから外れます。そして、残念な点はEXCLUDE_FROM_DEFAULT_BUILDを指定したターゲットは他のターゲットから依存されていてもビルドされません。なので、デフォルト・ビルド時にはビルド不要だけど、他の特定のターゲットをビルドする時だけビルドするという設定ができません。
add_library(lib EXCLUDE_FROM_ALL lib.cpp lib.h) set_target_properties(lib PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE) add_executable(main EXCLUDE_FROM_ALL main.cpp lib.h) set_target_properties(main PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE) target_link_libraries(main lib) # mainがlibをリンクすることの指定 add_executable(always always.cpp)
EXCLUDE_FROM_DEFAULT_BUILD は set_target_propertiesコマンドで指定しますので、上記のように設定してmainとlibをVisual Studioのビルド・ターゲットから外すと次のように振る舞います。
- Visual Studio上の「ビルド(B)→構成マネージャ(O)…」でみると、mainとlibはビルド・ターゲットから外れています。
-
ソリューションをビルドした時、mainとlibはビルドされません。
-
ソリューション エクスプローラで mainを指定してビルドしても lib はビルドされません。なので、libがビルドされていない場合、mainはリンク・エラーになります。更に嫌なことですがlibのビルド後lib のソースを修正していてもlibはビルドされません。
-
ソリューション・エクスプローラで libを指定してビルドして初めて lib がビルドされます。
上記の 3. の振る舞いは結構ハマる(ビルドしたつもりがビルドされていない)ので、他から依存されているターゲットは普段はビルド不要でもEXCLUDE_FROM_DEFAULT_BUILDを指定するのは避けた方が無難です。
3.以上のサンプルCMakeLists.txt
3-1.その前にちょっとだけ準備
- includeコマンド
定型的に記述するコードをサンプルからなるべく排除したいので、includeを使います。
これはC/C++の#includeと同じような機能です。(モジュールを取り込む機能ということなのでもう少し高度なようですが、ここでは定形処理を記述したファイルを単純に取り込む目的で使います。) -
cmake -Eコマンド
今回のサンプルでは、CMake内蔵のcopyコマンドを使います。大抵のOSがcopyコマンドを持っていますが、微妙にコマンド名や構文が異なるのでマルチプラットフォームで記述するのが面倒な部分です。
その辺を CMake 自身が提供しているので、これを使うと楽できます。
cmake -Eコマンドです。CMakeの中で使う時は、`${CMAKE_COMMAND}でcmakeを呼び出せます。パスを切っていない時にも対応しているのだろうと思います。
3-2.サンプル・コード
cmake_minimum_required(VERSION 3.5.1) project(sample) include("${CMAKE_CURRENT_SOURCE_DIR}/base.cmake") add_library(lib lib.cpp lib.h) #add_library(lib EXCLUDE_FROM_ALL lib.cpp lib.h) #set_target_properties(lib PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE) add_executable(main main.cpp lib.h) #add_executable(main EXCLUDE_FROM_ALL main.cpp lib.h) #set_target_properties(main PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE) target_link_libraries(main lib) # mainにlibをリンクする指定 add_custom_target(copy ALL COMMAND echo ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/license.txt" "$<TARGET_FILE_DIR:main>" COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/license.txt" "$<TARGET_FILE_DIR:main>" ) add_executable(always always.cpp)
add_executableとadd_libraryにEXCLUDE_FROM_ALLを指定してみたり、add_custom_targetからALLを外してみたり、EXCLUDE_FROM_DEFAULT_BUILD を有効にして Visual Studioで *.sln を読み込んで見たりしてみてください。
#include <string> class Lib { std::string mExeDir; public: Lib(std::string const& iArgv0); void print_license(); };
#include <iostream> #include <fstream> #include <string> #include "lib.h" Lib::Lib(std::string const& iArgv0) { mExeDir = "."; auto pos = iArgv0.find_last_of("/\\"); if (pos != std::string::npos) { mExeDir = iArgv0.substr(0, pos); } } void Lib::print_license() { std::ifstream ifs(mExeDir+"/license.txt"); std::string buff; while(std::getline(ifs, buff)) { std::cout << buff << std::endl; } }
#include "lib.h" int main(int argc, char const* argv[]) { (void)argc; // 警告抑止 Lib aLib(argv[0]); aLib.print_license(); }
4.まとめ
今回はちょっと短めです。法人化したのでそのあれやこれやの処理に苦労してて時間が余り取れていません。申し訳ないです。
実は、一昨日からペットのミリが寝込んでしまい病院で点滴を打ってもらってます。今日は少し回復しつつあるのでちょっとほっとしているところです。(いつもより誤字脱字が多いかも。)
次回は、もともと今回に含めたかった依存関係やadd_custom_targetと似ているadd_custom_command等について解説したいと思います。お楽しみに!
先日Qtの方でコメントさせて頂いた際、
温かい応援のお言葉を頂き、ありがとうございました。
CMakeも勉強したいと思っていたので、
本講座も有難く拝見させて頂いております。
最初の方でDoxygenについて触れられていますが、
Doxygenについても本講座あるいはその他の講座等で取りあげて頂くご予定はございますでしょうか。
こんにちは。
いつもありがとうございます!
Doxygenは便利なのですが、プログラム開発ツールではないので現在のところ取り上げる予定はないです。
すでに解説されている人も多いようですし、公式のドキュメントも比較的充実しています。http://www.doxygen.jp/
がんばってください。