Claude CodeでZephyr RTOSをwestビルドで開発する手順
Zephyr RTOSアプリのファイル構成とwestビルド、devicetreeオーバーレイの差し替えをClaude Codeでどう回すか、権限設定とhooksの具体例つきでまとめます。
Claude CodeでZephyr RTOSのアプリを開発するとき、鍵になるのはCMakeベースのビルドシステムと、devicetreeオーバーレイによるハードウェア差分の管理です。Arduino CLIとは設定を当てる単位が違うため、westコマンドと設定ファイルの役割をどう伝えるかで仕上がりが変わります。この記事ではアプリケーションの構成からビルド、devicetreeオーバーレイの差し替え、CLAUDE.mdとhooksでの安定化までを順に扱います。
Zephyrアプリのディレクトリ構成とClaude Codeの役割分担
Zephyrの公式ドキュメントは、アプリケーションの置き場所で3つの型を区別しています。
アプリケーションの置き場所3型
repository application
zephyrリポジトリの中に置く型です。
workspace application
westワークスペースの中で、zephyrリポジトリの外に置く型です。
freestanding application
ワークスペースの外に独立して置く型です。
ZEPHYR_BASE環境変数を適切に設定する必要があります。
最小構成のアプリケーションは次の5ファイルで成立します。
<app>
├── CMakeLists.txt
├── app.overlay
├── prj.conf
├── VERSION
└── src
└── main.cCMakeLists.txtはビルドシステムの入口、app.overlayはハードウェア設定を上書きするdevicetreeオーバーレイ、prj.confはソフトウェア機能を有効化するKconfigフラグメントです。Claude CodeはEditツールでこの3種類の設定ファイルとsrc以下のソースコードを編集し、ビルド・書き込みの実行だけをBashツールに任せる分担がそのまま当てはまります。
ゼロから作る場合、CMakeLists.txtは次の4行が最小の例です。
cmake_minimum_required(VERSION 3.28.0)
find_package(Zephyr)
project(my_zephyr_app)
target_sources(app PRIVATE src/main.c)find_package(Zephyr)がZephyrのビルドシステムを取り込み、CMakeターゲットappを作ります。project()の呼び出しはこの後に置く必要があり、順序を逆にするとZephyr側のproject(Zephyr-Kernel)と競合します。
ファイルを1から書かせるより、公式のexample-applicationリポジトリを土台にする方が要る設定の過不足を防げます。
cd <home>/zephyrproject
git clone https://github.com/zephyrproject-rtos/example-application my-app既存のワークスペース内にクローンすれば、カスタムボードポートやCI設定(twister)まで含んだ構成をそのまま流用できます。Claude Codeにはこのテンプレートの各ファイルをEditツールで書き換えさせ、CMakeLists.txtの構造は変えない指示にしておくと安全です。
westビルドをBashツールに任せる
Zephyrのビルドは2段階です。まずCMakeがbuild.ninjaなどのビルド定義を生成し、次にNinja(またはMake)がソースを実際にコンパイルします。標準のビルドツールであるwestはこの2段階をまとめて実行するラッパーで、内部でCMakeと生成したビルドツールを呼び出します。Windowsではジェネレータがninja固定で、makeは使えません。Linux/macOSはninjaとmakeのどちらも選べます。
west build -b reel_board samples/hello_world-bでボードを指定し、末尾にアプリケーションのディレクトリを渡します。設定ファイルを一時的に差し替えたいときはCONF_FILEを--区切りで渡します。
west build -b <board> -- -DCONF_FILE=prj.alternate.confBOARD・CONF_FILE・DTC_OVERLAY_FILEの3つの変数は、コマンドライン引数(-D)・環境変数・CMakeLists.txt内のset()の3通りで指定でき、この順に優先されます。Claude Codeに繰り返しビルドさせるときは、環境変数で固定するよりコマンドライン引数で明示させた方が、どの設定でビルドしたかが会話ログに残ります。ビルドログが長い場合は、Gradleビルドの出力を絞る手順の考え方も使えます。
書き込みまで自動化すると確認プロンプトが挟まって流れが止まるので、プロジェクトの.claude/settings.jsonに許可ルールを追記しておきます。
{
"permissions": {
"allow": [
"Bash(west build *)",
"Bash(west flash *)",
"Bash(tee build/build.log)"
]
}
}3行目は、後述のログ保存でwest build ... 2>&1 | tee build/build.logと書かせるための許可です。Claude Codeは|で区切った複合コマンドを1つずつ照合し、許可ルールに合わない部分があればそこで確認を求めます。Bash(west build *)だけではteeの側が通りません。
Bash(west build *)は-t cleanや-t pristineのようなターゲット指定も含めて許可されます。使えるターゲットはwest build -t helpで一覧できます。ワイルドカードはサブコマンドの後ろに置くのが安全です。Bash(west *)まで広げるとwest initやwest updateまでノー確認で通るようになるため、ワークスペースの構成自体を変えるコマンドをどこまで許可するかは別途判断します。
QEMUエミュレータで実機なしに動作確認する
書き込み自体はwest flashをBashツールから実行すれば自動化できます。ただしボードをUSBに接続する作業やリセットボタンの操作、シリアル出力の目視確認はClaude Codeの手が届かず、人の手を挟む工程として残ります。Zephyrはこの制約を、QEMUによるソフトウェアエミュレーションで部分的に埋められます。たとえばqemu_x86をボードに指定すると、x86のエミュレータ上で動かせます。
west build -b qemu_x86 samples/hello_world
timeout 15 west build -t run > qemu.log 2>&1 || truewest build -t run(ninja runでも同じ)を実行すると、Ctrl+A、Xでの手動停止が前提になるため、Claude Codeに読ませるときはtimeoutで打ち切ってログをファイルへ落とす形にします。timeoutはLinuxのcoreutilsのコマンドで、macOSではgtimeout(coreutils)になります。実機を用意する前の段階で、ロジックの検証まで進められます。
devicetreeオーバーレイとKconfigの差分をClaude Codeに読ませる
ビルドが通らない、あるいは動作が想定と違うときの多くは、app.overlay(ハードウェア)とprj.conf(ソフトウェア)のどちらに手を入れるべきかを取り違えています。ビルドシステムはデフォルトでapp.overlayとprj.confをそれぞれ自動的に探すので、追加のオーバーレイやKconfigフラグメントを使うときだけDTC_OVERLAY_FILE・EXTRA_DTC_OVERLAY_FILE・CONF_FILE・EXTRA_CONF_FILEで明示します。複数ファイルを渡すときはセミコロン区切りです。既存のprj.confに別のKconfigを混ぜたいときは、west build -- -DEXTRA_CONF_FILE=file.confの形で渡します。オーバーレイ側のノード名エラーの直し方はClaude Codeでdevicetree overlayを書きビルドエラーを潰す手順にあります。
BOARDには、この3通りより上に、CMakeキャッシュに残った前回の値があります。ビルド設定時と違うボードで走らせないための仕組みです。westは既定ではビルドディレクトリの作り直しが要るかを調べないため、同じディレクトリを別の--boardで使い回すとエラーになりえます。west build -p(--pristine=always)で作り直すか、--pristine=autoでwestに判定させます。west config build.pristine autoを設定すれば、常に判定させる運用にもできます。
1つのソースを複数のボード・製品バリアント向けにビルドする場合はFILE_SUFFIXが使えます。FILE_SUFFIX=mouseを指定すると、prj.confの代わりにprj_mouse.confが優先され、該当するボード用オーバーレイ(例: boards/native_sim_mouse.overlay)が無ければnative_sim.overlayにフォールバックします。Claude Codeに新しいバリアントを追加させるときは、この命名規則をCLAUDE.mdに書いておくと生成されるファイル名が揃います。
実験的な機能を有効化する場合はCONFIG_WARN_EXPERIMENTAL=yをprj.confに加えておくと、対象のKconfigオプションを有効にした時点でCMakeの設定段階に警告が出ます。Claude Codeにビルドログを読ませるとき、この警告文字列を拾わせておけば、実験的APIへの依存を後から見つけやすくなります。
CLAUDE.mdとhooksでビルドループを安定させる
ボード名やリビジョン、ワークスペースの構成はプロジェクトごとに固定なので、CLAUDE.mdに書いておくとClaude Codeが毎回コマンドを組み立て直さずに済みます。
## ビルド規約
- ボードは `nrf9160dk@0.14.0/nrf9160/ns` のようにリビジョン付きで指定する
- 設定ファイルやボードを変更したら `west build -t pristine` を実行してから再ビルドする
- devicetreeの差分は `app.overlay` に集約し、`DTC_OVERLAY_FILE` で分岐させない
- `west build` は必ず `west build ... 2>&1 | tee build/build.log` の形で実行し、出力を `build/build.log` に残す。`tee` の出力先はこのパスで固定する
- ビルド後は `<app>/build/zephyr/.config` と `build/build.log` の両方を確認する最後の1行は、ninjaの標準出力にエラーが出なくても.configの値が意図とずれていることがあるためです。.configにはビルドに使われた設定値、zephyr.elfには最終的な実行バイナリが入ります。
ビルドの成否をClaude Codeに伝える流れは、4段階です。
ビルド結果がClaudeに届くまで
- 1
ログを残す
west build ... 2>&1 | tee build/build.logで、出力をbuild/build.logに書き出します。 - 2
フックを起動する
PostToolUseのifにBash(west build *)を書くと、west buildのときだけスクリプトが動きます。ifはBashの引数まで見て判定します。 - 3
ログを走査する
check-zephyr-build.shがbuild/build.logからerror:かwarning:を含む行を探します。 - 4
stderrをClaudeに返す
見つかった行をstderrに出して
exit 2で終わります。exit 0のstderrはデバッグログに残るだけで、Claudeには届きません。
設定は次のとおりです。PostToolUseは成功した呼び出しの後に動き、失敗した呼び出しはPostToolUseFailureの担当なので、同じスクリプトを両方に登録します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "scripts/check-zephyr-build.sh",
"if": "Bash(west build *)"
}
]
}
],
"PostToolUseFailure": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "scripts/check-zephyr-build.sh",
"if": "Bash(west build *)"
}
]
}
]
}
}スクリプトは次のとおりです。
#!/usr/bin/env bash
LOG="build/build.log"
[ -f "$LOG" ] || exit 0
MATCHES="$(grep -E 'error:|warning:' "$LOG")"
if [ -n "$MATCHES" ]; then
echo "$MATCHES" >&2
exit 2
fi
exit 0ログに警告とエラーが1行ずつあるとき、スクリプトは次の2行をstderrへ出し、終了コード2で終わります。-- Found ...のようにerror:もwarning:も含まない行は拾われません。
warning: Experimental symbol FOO is enabled.
/app/src/main.c:12:5: error: unknown type name foo_t1段階目でteeを挟むのは、ビルドログをファイルにも残したいからです。ただしteeには落とし穴があります。bashの既定では、パイプ全体の終了コードは最後のコマンドのものです。
$ false 2>&1 | tee build/p.log; echo $?
0
$ set -o pipefail; false 2>&1 | tee build/p.log; echo $?
1west buildがコンパイルエラーで落ちても、| teeで受けた呼び出しの終了コードは0になりえます。set -o pipefailを効かせない限り、パイプ全体は成功として返ります。
警告(warning:)止まりでビルド自体は成功する出力は、終了コードでは区別できません。ログの文字列で探す構成が効くのはそこです。Kconfigの警告をそもそもエラーにしたいときは、KCONFIG_WARNING_AS_ERROR変数でビルドを止める方法もあります。
Arduino系記事との違いをどう使い分けるか
同じ組み込み開発でも、Claude CodeでArduinoを制御するやClaude CodeでESP32の開発とシリアルデバッグを進める手順で扱った構成とは、設定を当てる単位が異なります。
| 観点 | Arduino CLI(ESP32等) | Zephyr(west) |
|---|---|---|
| ハードウェア設定の変更方法 | Arduino CLI(ESP32等)FQBNと--board-optionsでボードとメニュー設定を指定する | Zephyr(west)app.overlay(devicetree)でボードの回路情報だけを上書きする |
| ソフトウェア機能の有効化 | Arduino CLI(ESP32等)スケッチのマクロ定義や#ifdef分岐 | Zephyr(west)prj.confのKconfigオプション(CONFIG_CPP=yなど)を追記する |
| 複数機種向けの構成切り替え | Arduino CLI(ESP32等)ボードごとにFQBNと--board-optionsを変えて指定する | Zephyr(west)FILE_SUFFIXでprj_mouse.confのような派生ファイルを自動選択する |
Arduino系はFQBNで機種を指定する形で、Zephyrはファイル単位で設定を分離できるため、複数機種・複数バリアントを1つのリポジトリで管理する構成に向いています。どちらを選ぶかは、対象チップがどちらの環境で扱えるかと、バリアント管理が要る規模かどうかで決まります。
よくあるつまずき
- Windowsで
makeが使えない: Windowsのジェネレータはninja固定です。Linux/macOSと同じ手順をwest config build.generator "Unix Makefiles"で共有しようとすると、Windows側だけ失敗します。 CONF_FILEで上書きしたはずの設定が反映されない:BOARD・CONF_FILE・DTC_OVERLAY_FILEは-D引数・環境変数・CMakeLists.txt内のset()の順に優先されます。シェルに古いCONF_FILE環境変数が残っていたり、CMakeLists.txtにset(CONF_FILE ...)が書かれていたりすると、-Dを付け忘れたコマンドではそちらの値が優先されて上書きが効きません。迷ったら常に-DCONF_FILE=...で明示指定します。- 設定を変えたのにビルドに反映されない: 切り分けには
cleanとpristineを使い分けます。
clean と pristine の違い
west build -t clean
.configを残したまま、生成されたファイルだけ消します。設定は前回のままです。
west build -t pristine
.configを含む生成物をすべて消し、ビルドディレクトリを作り直した状態に戻します。設定自体を疑うときはこちらです。
- カスタムボードが見つからずエラーになる: 独自のボード定義を追加した場合、
-DBOARD_ROOT=<path>かCMakeLists.txt内のBOARD_ROOT指定が抜けていないか確認します。CMakeLists.txt内で指定する場合は絶対パスが必要です。 - ボードのリビジョン違いを指定できない:
<board>@<revision>の形式で指定します。例えばnrf9160dk@0.14.0/nrf9160/nsのように、リビジョンの後ろにボード修飾子を続けます。
こうした「ビルドしてログを読ませ、直す」の反復はClaude Code hooksでテストを自動実行するで扱うPostToolUseとStopの使い分けとも共通する組み方です。
まとめ
フックを入れるなら、teeを挟んだビルドが成功扱いになる条件を先に決めておくと、警告止まりの失敗も会話に戻せます。ボードやFILE_SUFFIXを替えて試す場面では、pristineで作り直すかどうかをCLAUDE.mdに書いておくと切り分けが速くなります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。