この記事はAkerun Advent Calendar 13日目の記事です。
今回はファームウェアエンジニア いとう が担当です。
前回からの流れで
前回 の記事でZephyr RTOSのビルドに使える開発環境を作成しました。これをもう少し活用できないでしょうか?ってことで この環境をそのまま CI/CDの実行環境として使ってみます。
TL;DR
できたこと
- ✅ GitLabと連動した自動ビルド&自動テスト
- ✅ クラウド上で高速かつ単体でも結合でもできるテスト環境
GitLab CIで自動ビルド
まずは、GitLab CIで自動ビルドをやってみます。GitLab CIには任意のDockerイメージをCIの実行環境として指定できます。ここに前回作成したイメージを指定すると、GitLab runner 側(CI実行サーバー)に各種ツールをインストールしておかなくてもDockerだけでビルドができてしまいます。
Zephyrならではの悩みどころとしては west を使って複数のGitリポジトリでプロジェクトを構成している場合、せっかくGitLab runnerがクローンしてくれた環境を無視して改めてwest init + west updateが必要となってしまいます。これについては今の所解決策は見当たらず、愚直に新しいディレクトリを作成してその中でwest init + west updateをやってます。そのためにssh系のクレデンシャルも必要となりました。ちょうどGitLabにはCIで実行する環境変数を権限のないメンバーから見えなくする機能があるのでこれを使っています。
.gitlab-ci.yml
抜粋
image: name: odorenotokorono.gitlabnourl.com:5555/hogehoge/dev-env-docker:0.1.1 entrypoint: [""] stages: - lint - build - test - deploy before_script: - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null - mkdir -p ~/.ssh - chmod 700 ~/.ssh - ssh-keyscan odorenotokorono.gitlabnourl.com >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts - mkdir -p .ccache - export CCACHE_BASEDIR=${PWD} - export CCACHE_DIR=${PWD}/.ccache - mkdir -p .pip - export PIPCACHE_DIR=${PWD}/.pip cache: paths: - .pip - .ccache variables: WEST_DIR: __west__ BUILD_TYPE: Debug .extract_west: &extract_west |- west init -m ${CI_REPOSITORY_URL} --mr ${CI_COMMIT_SHA} ${WEST_DIR} cd ${WEST_DIR} west update pip3 --cache-dir=${PIPCACHE_DIR} install -r ./zephyr/scripts/requirements.txt source ./zephyr/zephyr-env.sh .branch_param: &branch_param BUILD_ARTIFACT_NAME: "${CI_PROJECT_NAME}-${BUILD_BOARD}-${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}" .tag_param: &tag_param BUILD_ARTIFACT_NAME: "${CI_PROJECT_NAME}-${BUILD_BOARD}-${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}" .generic_artifact: &generic_artifact name: "${BUILD_ARTIFACT_NAME}" paths: - ./*.hex - ./*.exe - ./debug_files .generic_build: &generic_build stage: build script: - *extract_west - source ./app/VERSION - >- if [ -z "${EXTRAVERSION}" ]; then export VERSION_STRING=${VERSION_MAJOR}.${VERSION_MINOR}.${PATCHLEVEL}; else export VERSION_STRING=${VERSION_MAJOR}.${VERSION_MINOR}.${PATCHLEVEL}-${EXTRAVERSION}; fi - >- if [ -z "$CI_COMMIT_TAG" ]; then export BUILD_APP_NAME=${CI_PROJECT_NAME}-${BUILD_BOARD}-${CI_COMMIT_REF_SLUG}-${BUILD_TYPE}-${CI_COMMIT_SHORT_SHA}; else export BUILD_APP_NAME=${CI_PROJECT_NAME}-${BUILD_BOARD}-v${VERSION_STRING}-${BUILD_TYPE}; fi - echo ${VERSION_STRING} - west build -b ${BUILD_BOARD} -d build_${BUILD_BOARD} ./app -- -D CMAKE_BUILD_TYPE=${BUILD_TYPE} - cd ${CI_PROJECT_DIR} - mv ${WEST_DIR}/build_${BUILD_BOARD}/zephyr/${BUILD_KERNEL_NAME}.hex ./${BUILD_APP_NAME}.hex || true - mv ${WEST_DIR}/build_${BUILD_BOARD}/zephyr/${BUILD_KERNEL_NAME}.exe ./${BUILD_APP_NAME}.exe || true - mkdir debug_files - mv ${WEST_DIR}/build_${BUILD_BOARD}/zephyr/${BUILD_KERNEL_NAME}* ./debug_files/ - mv ${WEST_DIR}/build_${BUILD_BOARD}/zephyr/.config ./debug_files/ artifacts: <<: *generic_artifact tags: - docker .foo_model: &foo_model BUILD_BOARD: foo_model BUILD_KERNEL_NAME: foo_model build-firmware: <<: *generic_build artifacts: <<: *generic_artifact expire_in: 1 week variables: <<: *foo_model <<: *branch_param only: - branches build-firmware-release: <<: *generic_build artifacts: <<: *generic_artifact expire_in: 1 week variables: <<: *foo_model <<: *branch_param BUILD_TYPE: Release only: - branches
少し長めになってますがポイントは3つ
- キャッシュ
Zephyrのビルドは標準でcchaceによる、コンパイラキャッシュが有効となっています。これにより同じコンパイラオプションであれば再ビルドしても爆速でコンパイルが完了します(クリーンビルドでも)。ですがGitLab-CIのDocker環境は毎回フレッシュな環境になってしまうのでこのcchaceが効きません。このようなキャッシュデータを引き継ぐ機能をGitlab-CIは提供しています。下記抜粋中のcache:\n paths:
のところで指定しているディレクトリは別のビルドジョブの時も引き継がれます。設定さえしておけばこのキャッシュはCI実行マシンをまたいで共有できます。
before_script: - export CCACHE_BASEDIR=${PWD} - export CCACHE_DIR=${PWD}/.ccache - export PIPCACHE_DIR=${PWD}/.pip cache: paths: - .pip - .ccache
同様にpipのダウンロードデータもキャッシュ化してビルドジョブごとに使い回しています。
- westでマルチリポジトリの展開&ビルド
west使っている部分を抜き出すと次の通り、通常のターミナルで行う処理と変わりません。Release/Debugビルド両方でビルドできるように変数で切り替えを行なっています。ここでビルドに必要なpipパッケージのインストールも行なっています。
##中略 .extract_west: &extract_west |- west init -m ${CI_REPOSITORY_URL} --mr ${CI_COMMIT_SHA} ${WEST_DIR} cd ${WEST_DIR} west update pip3 --cache-dir=${PIPCACHE_DIR} install -r ./zephyr/scripts/requirements.txt source ./zephyr/zephyr-env.sh ##中略 .generic_build: &generic_build stage: build script: ##中略 west build -b ${BUILD_BOARD} -d build_${BUILD_BOARD} ./app -- -D CMAKE_BUILD_TYPE=${BUILD_TYPE} ##中略
- ビルド結果の保存
さて、ビルドもできたのでビルドでできたファイルをGitLabサーバーに保存しておきましょう。*.elf
はもちろん、デバッグ時に必要となりそうなファイルをごそっと取っておくとあとできっと役に立ちます。GitLab-CIではこのようなファイルはアーティファクトと呼ばれています。
##中略 .branch_param: &branch_param BUILD_ARTIFACT_NAME: "${CI_PROJECT_NAME}-${BUILD_BOARD}-${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}" ##中略 - mv ${WEST_DIR}/build_${BUILD_BOARD}/zephyr/${BUILD_KERNEL_NAME}.hex ./${BUILD_APP_NAME}.hex || true - mv ${WEST_DIR}/build_${BUILD_BOARD}/zephyr/${BUILD_KERNEL_NAME}.exe ./${BUILD_APP_NAME}.exe || true - mkdir debug_files - mv ${WEST_DIR}/build_${BUILD_BOARD}/zephyr/${BUILD_KERNEL_NAME}* ./debug_files/ - mv ${WEST_DIR}/build_${BUILD_BOARD}/zephyr/.config ./debug_files/ ##中略 .generic_artifact: &generic_artifact name: "${BUILD_ARTIFACT_NAME}" paths: - ./*.hex - ./*.exe - ./debug_files
小さなtipsですが生成されるファイルが毎回同じだと作業中に取りちがえる可能性があるため、ブランチの名前とコミットのハッシュの一部をファイル名にしてアーティファクト指定しています。アーティファクトは生存期間も指定できるの少し長めに設定しておけば、いつの間にかバグが入り込んだ時の「どこからバグが入り込んだかチェック」が少しは捗ります。
GitLab CIで自動テスト
自動でビルドができれば次は自動テストをやってみましょう。変更をプッシュするたびにテストが行われてどこも壊れていないことが保証されれば怖いもの無しです。Zephyr にはテストフレームワークが同梱されています。これを使うしかないでしょう。内容の紹介は省きますが普通に使えるやつです。
unit-test: stage: test script: - *extract_west - ./zephyr/scripts/sanitycheck -v -i --ninja -T ./app/test tags: - docker
さて、組み込みで自動テストはどのようにすればいいのでしょうか?私の知っている範囲で組み込みの自動テストは次の3種類にざっくり分類されると思います。
それぞれメリット・デメリットがあると思います。しっかりしたプロジェクトでは複数のテスト種類を実施しているのではないでしょうか?(しっかりしたプロジェクトを寡聞にして知りませんが・・・)Zephyrのテストフレームワークはいずれにも対応しているようです。今回はクラウド上で動作しているGitLab-CIで実施する自動テストなので実機でのテストはなしにするとして、実行スピード重視でホスト環境での単体テストにすべきか?組み合わせテストができるけど遅いエミュレーターを使うのが正解でしょうか・・・?
救世主 native_posix
!
先ほどの悩みは native_posix
で一気に解決できました。
これはZephyrをホスト側OS(今回はLinux)のアプリケーションとしてビルドしてしまい実行できるようにするBOARD
です。ビルドする際にボードの指定に-b native_posix
としてやれば zephyr.elfの代わりに zephyr.exe が生成されます。
- 実行はネイティブ!
./zephyr.exe
で実行できるし速い
- いろんなカーネルコールも呼べる!
- マルチスレッドもOK
- 各種ペリフェラルでテスト可能!
- 割り込み
- ハードウェアタイマー
- Bluetooth!
- FlashROM
- その他諸々
これは素晴らしいですね。FlashROMの動作を確かめたところ同一階層にあるファイルに読み書きでき、実機で使うのと同じAPI(NVS)で読み書きができました。経験上ファームウェアのバージョンアップ時に不揮発データのデータ追加などを行うとマイグレーション時に不具合でがちなのでこの辺を重点的にテストできれば安心感が増します。
これをZephyrのテスト環境として使うのは ***/hogehoge/testcase.yaml
の対応基板のホワイトリストにnative_posix
を指定するだけでOKです。unit_test
と違いprintk()
もk_sleep(ms)
呼び放題でテストを組めますし実行は速いです。
tests: hogehoge.genral: platform_whitelist: native_posix tags: test_framework
あとはガシガシテストを書いていくだけですね💪!捗る〜
できたこと&やりたいこと
できたこと
- ✅ GitLabと連動した自動ビルド&自動テスト
- ✅ クラウド上で高速かつ単体でも結合でもできるテスト環境
今後やりたいこと
- [ ] Raspberry piでGitLab runnerを動かして J-Link経由で実機自動テスト
- これは時間がなくてやっていないだけでやればできそう
- プッシュするたびAkerunがガシャガシャなったら楽しそう
- [ ] Raspberry pi (ARM64) で
native_posix
を動かしてなおかつBluetoothも動かせればAkerunのテストしたい範囲がほぼほぼカバーできそう- GitHubのissueに上がっていたので現状は無理らしい
- Raspberry piだったら安いしオフィスに転がっているので 10台ぐらい並べて並列自動テストしてみたい
- Raspberry piだったらI2CやSPIも出ているので、HAL書いてやれば最高の環境じゃないですか?SoC選定前にソフト作れちゃうしデバイス評価もできちゃう
以上です!最後まで読んでいただきありがとうございました〜 👋
・・・
株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co
Akerun Proの購入はこちらから akerun.com