あけましておめでとうございます! 今年もよろしくお願い致します。
さて、CMakeについてもそろそろネタがなくなってきました。私自身が良く使っている機能について一通り解説が終わったという状況です。そこで、今回でCMake講座を終了とし、今までの「まとめ」的なサンプルと、もう少し残っている解説した方が良い項目について解説します。
1.お題
CMakeの機能を使って、次のようなプロジェクトを作ります。
- boostをダウンロードし、解凍する。
- そのboostをビルドする。(今回は静的リンク用)
- 上記処理をCMakeLists.tstから呼び出して、Makefile生成時に自動実行する。
- そのCMakeLists.txtは、上記boostのFormatを用いるプロジェクトとする。
- ビルド完了時にターゲットを自動実行する。
そこそこ複雑なプロジェクトなので、今まで解説してきたことをそれなりに使います。講座最後のまとめとして良いかなと思います。
2.boostを準備するCMakeスクリプト
上記のboostのダウンロード~ビルドまでの処理(boostを準備する処理)はCMakeスクリプトで処理します。「流れ作業」なので分離すると分かりやすく使いやすいと思います。
2-1. CMakeスクリプトへのパラメータの渡し方
CMakeLists.txtから呼び出すのでboostをダウンロードする先等をCMakeLists.txtから指定したいです。
CMakeスクリプトは、当講座の最初に解説したようにCMake -Pコマンドで呼び出します。その時は説明していませんが、パラメータも渡せます。
ビルド・ツールらしく、-D
オプションでCMake変数を定義します。まるでC++コンパイラで -D
オプションを使ってマクロを定義するのと良く似ています。
01 | message(STATUS "ARG0=${ARG0}") |
01 02 | > cmake "-DARG0=Hello, world!" -P sample.cmake -- ARG0=Hello, world! |
注意事項
注意事項があります。-Dオプションは-Pオプションより前で指定して下さい。後ろで指定すると無視されるようです。コマンド・ラインで使う時はつい後ろに書きたくなるので、始めて使った時かなり悩みました。
2-2. フォルダ生成
boostの準備はビルド・フォルダにておこなうので、まず最初にダウンロード先のフォルダを生成します。
これは前回解説したfileコマンドのMAKE_DIRECTORYを使います。
01 | file(MAKE_DIRECTORY "フォルダのフルパス") |
2-3. ファイルのダウンロード
ダウンロード元のURLとダウンロード先ファイルのパスを指定してダウンロードします。
01 | file(DOWNLOAD "URL" "ダウンロード先ファイル・パス" SHOW_PROGRESS) |
SHOW_PROGRESSを指定するとダウンロードの進行状況が表示されます。ダウンロードに数分かかることがあるのでハングアップと勘違いしないよう指定することをお勧めします。
2-4. アーカイブ・ファイルの解凍
CMakeには tarコマンド(圧縮/解凍ツール)が内蔵されています。CMake -E コマンドで呼び出すことができます。
CMakeスクリプトから呼び出すのでexecute_process()コマンドで呼び出します。
01 02 03 04 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf "圧縮ファイルのパス" WORKING_DIRECTORY "解凍先のフォルダ" ) |
${CMAKE_COMMAND}は CMakeのフル・パスへ展開されます。(例えばWindowsの場合cmake.exeのフル・パス)
WORKING_DIRECTORYはコマンドを起動する時の作業フォルダを指定します。tarコマンドでは作業フォルダへ解凍します。
2-5. boostをビルドする
boostをビルドする時は、bootstrapスクリプトで b2 コマンドを生成し、b2コマンドで様々なパラメータを指定して必要なboostをビルドします。詳しくはタイトルのリンク先をご覧下さい。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | if(WIN32) execute_process(COMMAND bootstrap.bat WORKING_DIRECTORY "boostのソース・フォルダ") else() execute_process(COMMAND ./bootstrap.sh WORKING_DIRECTORY "boostのソース・フォルダ") endif() execute_process( COMMAND ./b2 "--prefix=ビルドしたboostのインストール先フォルダのフル・パス" install variant=release link=static runtime-link=shared threading=multi -a -j 8 WORKING_DIRECTORY "boostのソース・フォル" ) |
ここでは静的リンク用(fPIC無し)で、リリース・ビルドのみ指定しています。
variantでdebugを指定するとデバッグ・ビルド用にビルドします。msvcでは「variant=release,debug」という指定が可能ですが、gccではどちらか一方のみ指定できます。(ビルド・システムの考え方の相違が原因と思います。)
2-6. ここまでのCMakeスクリプト・ファイル
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | message(STATUS "BOOST_VERSION=${BOOST_VERSION}") message(STATUS "BOOST_PATH=${BOOST_PATH}") # ダウンロードと解凍 string(REPLACE "." "_" VERSION ${BOOST_VERSION}) set(FILE_NAME "boost_${VERSION}") if(WIN32) set(FILE_NAME "${FILE_NAME}.7z") else() set(FILE_NAME "${FILE_NAME}.tar.bz2") endif() message(STATUS "FILE_NAME=${FILE_NAME}") set(URL "https://sourceforge.net/projects/boost/files/boost/${BOOST_VERSION}/${FILE_NAME}") message(STATUS "URL=${URL}") if(NOT IS_ABSOLUTE "${BOOST_PATH}") set(BOOST_PATH "${CMAKE_SOURCE_DIR}/${BOOST_PATH}") endif() set(FILE_PATH "${BOOST_PATH}/${FILE_NAME}") message(STATUS "FILE_PATH=${FILE_PATH}") if(NOT EXISTS ${FILE_PATH}) # ダウンロード file(MAKE_DIRECTORY "${BOOST_PATH}") file(DOWNLOAD "${URL}" "${FILE_PATH}" SHOW_PROGRESS) # 解凍 message(STATUS "extracting...") execute_process( COMMAND ${CMAKE_COMMAND} -E tar xvf "${FILE_PATH}" WORKING_DIRECTORY "${BOOST_PATH}" ) message(STATUS "extracted.") else() message(STATUS "${FILE_NAME} already downloaded.") endif() # ビルド set(BOOST_SOURCE "${BOOST_PATH}/boost_${VERSION}") message(STATUS "BOOST_SOURCE=${BOOST_SOURCE}") if(WIN32) set(VARIANT "release,debug") else() set(VARIANT "release") endif() if(WIN32) if(NOT EXISTS "${BOOST_SOURCE}/b2.exe") message(STATUS "boost:bootstrap ...") execute_process( COMMAND bootstrap.bat WORKING_DIRECTORY "${BOOST_SOURCE}" ) endif() else() if(NOT EXISTS "${BOOST_SOURCE}/b2") message(STATUS "boost:bootstrap ...") execute_process( COMMAND ./bootstrap.sh WORKING_DIRECTORY "${BOOST_SOURCE}" ) endif() endif() message(STATUS "boost:Build ...") if(NOT EXISTS "${BOOST_PATH}/include") execute_process( COMMAND ./b2 "--prefix=${BOOST_PATH}" install variant=${VARIANT} link=static runtime-link=shared threading=multi -a -j 8 WORKING_DIRECTORY "${BOOST_SOURCE}" ) endif() |
3.CMakeLists.txtのサンプル
上記の boost_build.cmake をMakefile生成時に呼び出してboostの準備を整え、そのboostを用いるサンプルをビルドして実行するCMakeLists.txtです。
今回は以前ご紹介した boost::format を用いるサンプル・ソースを微修正したものを用います。
3-1. boostの準備
boostは、ビルド・フォルダに専用フォルダ(boost)を用意して、そこへダウンロード→解凍→ビルド→インストールして使います。
01 02 03 04 05 06 07 | set(BOOST_PATH "${CMAKE_CURRENT_BINARY_DIR}/boost") execute_process( COMMAND ${CMAKE_COMMAND} -DBOOST_VERSION=1.67.0 -DBOOST_PATH=${BOOST_PATH} -P ${CMAKE_CURRENT_SOURCE_DIR}/boost_build.cmake ) |
execute_process()はビルド時ではなくMakefile生成時に実行されることをお忘れなきよう。
3-2. boostのインクルード・パス等をCMake変数へ設定する
そのためのCMakeのコマンドfind_packageを使います。
01 02 03 04 | set(BOOST_INCLUDEDIR "${BOOST_PATH}/include") find_package(Boost) message(STATUS "Boost_INCLUDE_DIRS =${Boost_INCLUDE_DIRS}") message(STATUS "Boost_LIBRARIES =${Boost_LIBRARIES}") |
実は、boost::formatはヘッダ・オンリーなのでライブラリを指定していないため、Boost_LIBRARIESは空です。
3-3. ターゲットのビルド指定
boost::formatのサンプル・ソースをビルドするための指定です。
01 02 03 04 05 06 07 08 | if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /EHsc") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11") endif() include_directories("${Boost_INCLUDE_DIRS}") add_executable(sample sample.cpp xprintf.cpp) target_link_libraries(sample "${Boost_LIBRARIES}") |
3-4. 最後にターゲットの実行コマンド(とgenerator-expressions)
これは今回始めて解説します。ターゲットをビルドした後、実行したいこともあります。Visual Studio で Go ボタンを押すようなイメージですね。
CMakeに含まれるテスト・ツールCTestを呼び出すなどいくつかの方法が考えられますが、ここではビルド後イベント(POST_BUILD)を用います。
CTestの問題点
CTestをexecute_processで呼び出す場合、日本語が文字化けするという問題がありました。既に治っている可能性もありますが、そもそも「CTestをexecute_processから呼び出す」という使い方を想定していない雰囲気でしたので修正されていない可能性もあります。
さて、CMake本家のマニュアルを見ていると、時々 「cmake-generator-expressionsを使用できます」との記述があることに気がついた方もいらっしゃると思います。
ざっくりいうと、Makefile生成モードで定義された値(主に各種パス)へ展開できる式です。つまり、スクリプト・モードでは使えません。
今回のまとめサンプルでは boost_build.cmakeというCMakeスクリプトと CMakeLists.txt を用いますが、前者では使えず、後者では使える式です。
これについてQiitaで解説されている方がいらっしゃいますので参考になると思います。
ここでは、$<TARGET_FILE:ターゲット名>
を使います。これは指定したターゲット(add_executableやadd_library等で指定したもの)で最終生成ファイル(exeやdll等)へのフル・パスへ展開されます。
つまり、add_custom_command()のPOST_BUILDのCOMMANDへ指定することで、ターゲットのビルド完了後に自動的に呼び出されます。
01 | add_custom_command(TARGET sample POST_BUILD COMMAND $<TARGET_FILE:sample>) |
3-5. CMakeLists.txtサンプル全体
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | cmake_minimum_required(VERSION 3.10.2) project(sample) message(STATUS "----- CMakeLists.txt") message(STATUS "CMAKE_CXX_COMPILER =${CMAKE_CXX_COMPILER}") message(STATUS "CMAKE_CXX_COMPILER_ID =${CMAKE_CXX_COMPILER_ID}") message(STATUS "CMAKE_GENERATOR_TOOLSET =${CMAKE_GENERATOR_TOOLSET}") # boostの準備 set(BOOST_PATH "${CMAKE_CURRENT_BINARY_DIR}/boost") set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_MULTITHREADED ON) execute_process( COMMAND ${CMAKE_COMMAND} -DBOOST_VERSION=1.67.0 -DBOOST_PATH=${BOOST_PATH} -P ${CMAKE_CURRENT_SOURCE_DIR}/boost_build.cmake ) # boostのインクルード・パスとライブラリ設定 set(BOOST_INCLUDEDIR "${BOOST_PATH}/include") find_package(Boost) message(STATUS "Boost_INCLUDE_DIRS =${Boost_INCLUDE_DIRS}") message(STATUS "Boost_LIBRARIES =${Boost_LIBRARIES}") # ターゲットのビルド if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /EHsc") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11") endif() include_directories("${Boost_INCLUDE_DIRS}") add_executable(sample sample.cpp xprintf.cpp) target_link_libraries(sample "${Boost_LIBRARIES}") # 実行 add_custom_command(TARGET sample POST_BUILD COMMAND $<TARGET_FILE:sample>) |
4.サンプル・ソースと実行結果
サンプル・ソースは冒頭にも書いたようにboostのFormatを用いたちょっとした便利ツールです。
01 02 03 04 | double = 2345.68 aEnum = 1 aRatio = 16/9 aString = test |
「Windows 10 + Visual Studio 2017」と「ubuntu 18 + gcc 7.4.0」にて動作確認しています。
5.まとめのまとめ
概ね1ヶ月に1回更新し、約1年間CMakeの使い方について解説してきましたが、とうとうネタも付きてしまいましたので、CMake編はこれで終了とさせて頂きます。長い間お付き合いありがとうございました!!
さて、今後のC++講座についてはまだ決めていませんので、次回開催がいつになるか未定ですが、解説できるネタが十分にたまったらQtについて解説したいとつらつら考えているところです。C++は非常に強力なマルチプラットフォームな言語ですが、GUIについてはからきしです。そのC++の超巨大な弱点に光を当てる最も完成度の高いプロジェクトがQtと思います。
Qtを使えるC++プログラマーへの需要が徐々に高まりつつあるように思いますし、少し後になると思いますが、再開したいと考えています。
それまで皆さんもごきげんよう!!