最近またArduinoを再開したので、開発環境アップデートの第1弾として、CI環境の見直しを行いました。
昔々、まだGitHub Actionsとかいう超便利な代物が無かった時代、自作のArduinoライブラリはTravisCIに突っ込んで自動テストを回していました。
今はGitHub Actionsもあることですし、手始めに移行してみることにしました。
公式ワークフロー
GitHub Actionsは、再利用可能なワークフローをマーケットプレイス経由で簡単に組み込めるのが最高に便利ですよね。
Arduinoも 公式 がワークフローを公開してくれています。
公式ワークフローは2種類あります。
- 実行環境インストール、ボードやライブラリの追加、コンパイルまで全自動 (上記)
- 実行環境インストールのみ (arduino/setup-arduino-cli)
複雑な処理を行う場合は単体版の方が便利かと思いますが、今回はテストを回したいだけなので、全自動版を使用します。
リポジトリへ組み込む
早速、自分のリポジトリへワークフローを追加していきます。
まずディレクトリ構成ですが、他の一般的なワークフローと同じく、以下のような構成をとります。
-
.github/
-
workflows/
test.yaml
release.yaml
- ...
-
そして僕はYAMLが 何で存在するのか理解できないレベルで生理的に無理 苦手なので、基本的にワークフローはJSONで書いてから yq というフォーマット変換ツールへ通してYAMLを 仕方無く 生み出しています。
-
.github/
-
workflows_json/
to-yaml.sh
test.json
release.json
- ...
-
set -uC
cd ${0%/*}
yq -P -I 4 ./${1}.json | head -c -1 | tee ../workflows/${1}.yaml &> /dev/null
# test.json -> test.yaml
./to-yaml.sh test
ワークフローの記述
まずは見ていただければ、やってることは何となく分かるかと思います。
test.json
{
"name": "Test",
"on": {
"push": {
"branches": [
"dev"
],
"paths-ignore": [
".git*",
"**.md",
"*.properties"
]
},
"pull_request": {
"branches": [
"master",
"dev"
],
"paths-ignore": [
".git*",
"**.md",
"*.properties"
]
}
},
"jobs": {
"test": {
"name": "Test: ${{matrix.board.name}}",
"runs-on": "ubuntu-latest",
"strategy": {
"fail-fast": true,
"matrix": {
"board": [{
"vendor": "arduino",
"arch": "samd",
"name": "arduino_zero_native"
}, {
"vendor": "adafruit",
"arch": "samd",
"name": "adafruit_trinket_m0"
}, {
"vendor": "teensy",
"arch": "avr",
"name": "teensy41"
}],
"include": [{
"index": "https://downloads.arduino.cc/packages/package_index.json",
"board": {
"vendor": "arduino"
}
}, {
"index": "https://adafruit.github.io/arduino-board-index/package_adafruit_index.json",
"board": {
"vendor": "adafruit"
}
}, {
"index": "https://www.pjrc.com/teensy/package_teensy_index.json",
"board": {
"vendor": "teensy"
}
}]
}
},
"steps": [{
"name": "clone repository",
"uses": "actions/checkout@v3"
}, {
"name": "install arduino and run test",
"uses": "arduino/compile-sketches@v1",
"with": {
"fqbn": "${{matrix.board.vendor}}:${{matrix.board.arch}}:${{matrix.board.name}}",
"platforms": "- name: ${{matrix.board.vendor}}:${{matrix.board.arch}}\n source-url: ${{matrix.index}}"
}
}]
}
}
}
要点だけ掻い摘んで解説します。
matrix.board
GitHub Actionsの並列実行機能で、大量のボードを同時並行でテストできます。
テストしたいボードの FQBN を分解して記述します。
FQBNとは 製造者:アーキテクチャ:ボード名
で構成された、Arduinoにおけるボード識別子のことです。
それら3要素を、それぞれ matrix.board
配列のオブジェクトプロパティとして以下のように記述します。
-
vendor
... 製造者 -
arch
... アーキテクチャ -
name
... ボード名
FQBNは製造者によって予め定義されているため、事前に確認しておく必要があります。
matrix.include
上記の並列実行において、特定のコンテキストにのみプロパティを追加したい場合、ここでパターンマッチと追加するプロパティを記述できます。
Arduinoに慣れ親しんだ方ならご存知かと思いますが、サードパーティ製のボードを使用する場合は、製造者が提供しているボード定義を追加する必要があります。
全コンテキストにおいて全ボード定義を追加するのも不可能ではないですが、不必要なボード定義までダウンロードすることになり、ネットワーク負荷増大に繋がるため、コンテキストに必要なボード定義のみを追加するために、この機能を使います。
例えば、FQBNが adafruit:samd:adafruit_trinket_m0
の場合、必要なボード定義はAdafruit社の package_adafruit_index.json
です。
そこで、以下のように matrix.include
を指定することで、特定の vendor
の時のみその製造者が提供しているボード定義の index
プロパティを追加できます。
{
"index": "https://adafruit.github.io/arduino-board-index/package_adafruit_index.json",
// ↓ で "matrix.board.vendor" が "adafruit" の時のみ "matrix.index" に ↑ を追加する
"board": {
"vendor": "arduino"
}
}
arduino/compile-sketches@v1
今回の目玉です。
全自動で全てよろしくやってくれる本体です。
デフォルトのテスト対象は examples/
の中身全部となります。
他のテスト対象を指定したい場合や、依存関係ライブラリが存在する場合は、別途 with
プロパティ内でオプション設定できます。
ここでは2個のオプションを指定します。
with.fqbn
ボードのFQBN、つまり matrix.board
の3要素を全てコロンで繋げた値となります。
with.platforms
少し厄介です。
この中身はGitHub Actionsワークフローそのものではなく、実行中のワークフロー内で別に解析されるため、YAML文字列で書く必要があります。つらい。
ここで指定するのは、インストールしたいボードのプラットフォームと、そのボードが定義されているインデックスのURLです。
プラットフォームとは、具体的に言うとコンパイラのことで、これは製造者がアーキテクチャ毎に用意しているため、識別子は 製造者:アーキテクチャ
となります。
例えば、FQBNが adafruit:samd:adafruit_trinket_m0
の場合、プラットフォームは adafruit:samd
となります。
そしてボード定義は board.include
で拡張された matrix.index
に格納されています。
実行してみる
今回のワークフローでは dev
ブランチにpushしたとき、テストが走るようになっています。
master
ブランチへは直接プッシュできないよう保護しているので、基本的に細かい修正は dev
で、全く新しい機能を追加するときは dev
から新たに feat-xxx
ブランチを切る感じで運用してます。
お恥ずかしながら...ここで実際のワークフローの挙動を確認できます。
まとめ
自動テストが出来るようになることで、開発効率が圧倒的に良くなります。
皆さんも是非GitHub Actionsを活用してArduino開発を加速させてみてください。