いまGNU make(Makefile)で作ってる某組み込み機器向けのプロジェクトを、CMakeに書き直したいと思った。だってMakefile書くのも読むのも苦手なんだもん。あと、いろいろな環境を横断して開発してるという事情も相まって、CMakeで書いたスクリプト(CMakeLists.txt)を極力使いまわしたいってのもあった。
しかし、ただCMakeに書き直すだけだとメリット薄い気がしたので、CMakeで吐き出すビルドスクリプトをMakefileじゃなくて、ドチャクソ速いと巷で噂のNinjaにも対応してみる事に。ビルドが速くなればそのぶん仕事サボれる時間が増えるからハッピーやね!
んで、いざ対応しようと思ったが右も左も分からんので、「CMake 組み込み」でググったら上の方に出てくるQiitaの記事(正直めっちゃ助かる)を参考にしつつお仕事の合間に組み込み環境に対応していったのだが、案の定ドハマリして1ヶ月ぐらい掛かってしまった。そもそもネット上にあんまり日本語の情報転がってないし、俺みたいなにわかビルドエンジニアには難易度高かったのよねぇ。
まあ、結果としてMakefileでやってたときに比べて、クリーンビルドの所要時間がだいたい半分になった。あと差分ビルドも一瞬で終わるので不安にはなるけど、機器に転送してみたらちゃんと動いてるみたいだから問題なかろう。たぶん今が一番覚えてる状態なので、忘れないうちに書き遺しておこう。
ジェネレータとビルドツールにNinjaを指定
まず、Ninjaは単体で実行ファイルが存在するので、ギッハブからあらかじめダウンロードしておく。Windowsの場合はninja.exe。
cmake .. -G"Ninja" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_MAKE_PROGRAM=ninja.exe
で、CMakeを実行するときにジェネレータに「Ninja」を指定した上で「CMAKE_MAKE_PROGRAM」にninja.exeのパスを設定する。
コンパイラやリンカの設定もCMakeの引数に直接渡してやればできなくもないが、ファイルにまとめて記載しといたほうが管理しやすいので、組み込みコンパイラ向けの設定をいろいろ書き連ねたtoolchain.cmakeを用意して、そいつも「CMAKE_TOOLCHAIN_FILE」で指定して読み込ませる。まあ、toolchainファイルの中でコンパイラのテストをスキップするとか色々お作法はあるのだが、詳細は前述のQiita記事見てもらったほうがわかりやすいと思うのでここでは省くぞよ。
コンパイラとかリンカの設定
# ツールのプレフィックス set(ARMCC_PREFIX arm-compiler-) # Cコンパイラ set(CMAKE_C_COMPILER ${ARMCC_PREFIX}gcc) # C++コンパイラ set(CMAKE_CXX_COMPILER ${ARMCC_PREFIX}g++) # リンカ set(CMAKE_LINKER ${ARMCC_PREFIX}ld)
コンパイラを通してリンカを呼び出す場合もあるので、ここらへんの設定は臨機応変な感じに。
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") # もりもり警告出す set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast") # がっつり最適化 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSOME_FLAG") # なんかフラグ設定
コンパイラにわたすオプションいろいろを、こんな感じでCMAKE_C_FLAGSにぶちこんでいく。C++コンパイラにわたすやつはCMAKE_CXX_FLAGSで。
set(CMAKE_EXE_LINKER_FLAGS "-L${LIBDIR} -lalpha -lbeta") set(CMAKE_C_LINK_EXECUTABLE "${CMAKE_LINKER} -o <TARGET>.out <OBJECTS> <LINK_FLAGS> <LINK_LIBRARIES>")
そいでリンカのフラグはCMAKE_EXE_LINKER_FLAGS、リンクで実行する某はCMAKE_C_LINK_EXECUTABLEに入れる。なんか<>で囲まれたマクロっぽいのがよく分からんかったのだが、吐き出されたビルドログを眺めてる感じだと下記のモノが入ってた。
- <TARGET> … CMakeLists.txt の add_executable() で設定したターゲット名が入る。
- <OBJECTS> … CMakeLists.txt の add_executable() で指定したソース部分に対応するオブジェクト一覧が入る。
- <LINK_FLAGS> …「CMAKE_EXE_LINKER_FLAGS」に書いた内容が入る。
- <LINK_LIBRARIES> … CMakeLists.txt の target_link_libraries() とかでリンク指定したライブラリが入る。
ビルド対象にアセンブラ混ぜたい
ビルドエラー出たので何かと思ったら、アセンブラで記述されたソースコードがビルド対象に入ってなかった。
project(proj C CXX ASM) file(GLOB_RECURSE SOURCES "*.c" "*.cpp" "*.s") add_executable(test ${SOURCES})
projectでASMを入れてやったり、ソースコード洗い出すときにアセンブラファイルの拡張子(*.s)を入れてやったりしたら通った。
プラットフォームごとに処理を分けたい
組み込み機器にのっける処理を部分的にWindows側でユニットテスト通して確認したりするのだが、Windowsのときだけユニットテスト用のプロジェクトをビルド対象に加えたい。
cmake .. -G"Ninja" -DCMAKE_SYSTEM_NAME=Windows (以下略
そんなときはテキトーなフラグをif使って判定させると良さげ。ここではCMAKE_SYSTEM_NAMEを使ってみる。
# Windows向けのビルド処理 if(CMAKE_SYSTEM_NAME MATCHES "Windows") # ユニットテスト用のプロジェクトをビルドに追加 add_subdirectory(unittest) # ifおわり endif()
こうしとけばif()やelseif()を使ってプラットフォームごとに処理を分岐できた。