見出し画像

CMakeとNinjaを組み込み向けに使う

※2020年頃に別ブログで書いてた記事の移植版です。

ビルド環境を乗り換えたい

いまGNU make(Makefile)で作ってる某組み込み機器向けのプロジェクトを、CMakeに書き直したいと思った。Makefile書くのも読むのも超苦手なので、読んでるだけで吐きそうになる。

あと、Windows上でライブラリをテストしてから実機ビルドで組み込んだりとかやってて、いろいろな環境を横断して開発してるという事情もある。そこで、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()を使ってプラットフォームごとに処理を分岐できた。

使い始めてから1年以上経った感想

この記事を最初に書いてから実際にお仕事で運用していく中で不運(ハードラック)と踊(ダンス)っちまう事案は色々あったけれども、最近は結構安定してきた。フォルダ構成をバリバリ変えてもCMakeLists.txtの記述を変えてやれば柔軟に対応できるので控えめに言って最高。ビルド時間も早いし。

ただ、何も考えずに複数のプログラムやライブラリをNinjaで爆速並列ビルドしたら違うプログラムのオブジェクトファイルが混ざる挙動があったりもしたので、依存関係ちゃんと設定しとかないとガバりやすい点は注意かねえ。

この記事が気に入ったらサポートをしてみませんか?