最近 Iris という自作キーボードを組み立てて使い始めました。

自作キーボードの利点の一つとしてキーマップを自由に変更可能という点があります。キーマップの変更はファームウェアを書き換えることで行います。ファームウェアは現在恐らく自作キーボード界のデファクトスタンダードである qmk_firmware を使うことが多いと思われ、Iris も qmk_firmware に対応しているものの一つです。

qmk_firmware は有志の手によって GUI ツールが用意されておりますが私は現在 GitHub 上でコードベースで管理しております。理由としては変更の履歴を残しておきたいからです。他にもありますが要するに GitHub だと管理が楽!

Iris を組む前は元々 Planck という自作キーボードを使っておりそちらも GitHub 上でファームウェアを管理していました。また、CircleCI と連携させてタグをつけてプッシュした場合にビルドとリリースを自動で行うようにしていました。

これは自宅と職場で二つの Planck を使用しており例えば自宅でキーマップを変更した際に職場の PC へビルドしたファームウェアを送るのが面倒であったり、そもそもそれぞれ PCB のリビジョンが違っていて二回ビルド作業が必要であったり、いろいろ面倒なためです。
自動でそれぞれのリビジョン向けのファームウェアをビルドして GitHub のリリースへ上げることでかなり楽になりました。

そして今回は CircleCI ではなく、GitHub Actions を使用して自動ビルド、リリースを行います。これで GitHub 上で全部完結できます。

GitHub Actions とは

GitHub 上でコードのテストやビルド、リリースといった一連のワークフローを実行することのできる機能です。

ワークフローはランナーと呼ばれる GitHub でホストされるマシン上のコンテナで実行されます。ランナーの環境は Linux, macOS, Windows があるみたいです。今回は Linux を使います。
またランナー上で動かすコンテナは自前で用意することも可能です。

一連のワークフローは YAML で書くことができます。ワークフロー定義ファイルは対象のリポジトリの .github/workflows 以下に配置します。最小限のテンプレはリポジトリの Actions ページから始めることができるので、これを元にいじっていくのが良いと思います。

ワークフローの定義

今回かなり雰囲気で触っていて実はまだ細かいことは把握できていないので、作成したワークフローベースで理解した内容をざっくりまとめます。

ワークフロー名

設定ファイルの先頭に名前を定義します。

name: Build and Release firmware

実行のタイミング

トリガーするタイミングを設定できます。プッシュを行った際や cron 形式で定期実行を設定することもできます!

今回は以下の条件でトリガーされるように設定します。

  • マスターブランチへのプッシュ。
  • v から始まるタグのプッシュ (v0.0.1 など)。
on:
  push:
    branches:
      - master
    tags:
      - v*

上記だと例えば master ブランチにタグつけてプッシュすると二つリリースが作成されます。
のでタグのプッシュ時のみに変更予定。

ジョブの定義

ワークフローには少なくとも一つのジョブが定義されている必要があります。また、ジョブには個々のタスクを実行する一連のステップが含まれています。ステップではコマンドの実行やアクションを使用することができます。アクションとは例えば GitHub 上にリリースを追加する等、よく使いそうな機能を自動化したものです。
以下にアクションの一覧があります。

例えばリリースを作成するアクションは以下。

このように GitHub の API を頑張って叩くステップを書かなくても済むようになっています。またアクションは自作することも可能です。

さて、今回は build という名前でジョブを作成しました。また、ランナーは Ubuntu を使用します。

jobs:
  build:

    runs-on: ubuntu-latest

次にジョブを実行するコンテナを設定します。設定しない場合、ランナー上で直接ステップが実行されます。また、ステップ毎にコンテナを指定することもできます。
今回、QMK 公式が公開している qmk_firmware のビルド環境が含まれているコンテナ qmkfm/base_container を使用します。

qmkfm/base_container の詳しい使い方は説明されていないのですが、以下のスクリプトから確認することができます。

ワークフローでは以下のように定義します。ランナーへリポジトリをチェックアウトし、そのディレクトリを qmkfm/base_container コンテナと共有します。これでコンテナからビルドできるようになります。
マウント先の /qmk_firmware は qmkfm/base_container コンテナの作業ディレクトリです。

    container:
      image: docker://qmkfm/base_container:latest
      volumes: 
        - .:/qmk_firmware

上記で実行環境の設定ができたので、ステップの定義に移ります。

以下のステップでランナーにリポジトリをチェックアウトしています。uses でリポジトリをチェックアウトするアクション checkout を選択しています。

    steps:
    - uses: actions/checkout@v2

以下のように Docker Hub アクションを使用してステップで使用するコンテナを指定できます。が、今回は container: で設定したコンテナをずっと使うので以下の設定はいらなかったかも。

    - uses: docker://qmkfm/base_container:latest

ステップにも名前が付けられます。一つの step: 内に複数の名前を設定できます。名前を設定してから次のアクションまたは名前が設定されるまでの間が一つのまとまりとして扱われます。
ステップ名やアクション毎にまとめられている様子は以下です。

----------2020-02-12-0.40.17

ファームウェアのビルドを行うステップを以下のように定義します。

    - name: make
      run: |
        wget -P /tmp https://github.com/abcminiuser/lufa/archive/master.zip
        unzip -d /tmp /tmp/master.zip > /dev/null
        mv /tmp/lufa-master/LUFA lib/lufa/
        make keebio/iris/rev2:lorentzca
        mv -v .build/keebio_iris_rev2_lorentzca.hex .build/keebio_iris_rev2_lorentzca_${GITHUB_RUN_ID}.hex

ビルドには lufa も必要になるのですがこれもチェックアウトアクションで引っ張ってきた方がきれいかも。
また、${GITHUB_RUN_ID} はリポジトリ内の各実行の一意の番号です。これはワークフローを再実行しても変わりません。各実行に一意の番号とのことなのでファイル名に使ってみていますが深い意味はありません。
このようなシステム側の環境変数は以下のページで見ることができます。

以下のアクションでは成果物 (ビルドしたファームウェア) をワークフローにアップロードしています。アップロードした成果物は GitHub のワークフロー確認画面からダウンロードできます。またワークフロー内からも download-artifact アクションでダウンロードすることができます。今回成果物をワークフローにアップロードしている理由は特にないです。使い所としては、ジョブを跨いで成果物を共有したい場合に使うんじゃないかなと思われます。

    - uses: actions/upload-artifact@v1
      with:
        name: firmware
        path: .build/keebio_iris_rev2_lorentzca_${{ github.run_id }}.hex

以下のアクションで GitHub のリリースページにリリースを作成します。id: を設定していますがこれは後続の処理で必須です。

    - name: create release
      id: create_release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ github.ref }}
        release_name: Release ${{ github.ref }}
        body: |
          Changes in this Release
          - test body.
        draft: false
        prerelease: false

${{ secrets.GITHUB_TOKEN }} で自動でトークンを取得してくれます!便利。${{ github.ref }} ではワークフローの実行をトリガーしたブランチ名やタグ名を参照できます。
このような変数はコンテキストと呼ばれます。コンテキストではワークフローの実行やランナーの環境、ジョブやステップに関する情報を参照することができます。また、${{}} の部分はエクスプレッションと呼びます。
コンテキストの一覧は以下に記載されています。

最後に、成果物を作成したリリースページにアップロードします。upload-release-asset アクションを使用します。アップロード先の URL は ${{ steps.<step id>.outputs.<output name> }} で取得しています。
リリースの作成を行うステップで id を設定したのはこのためです。リリース作成ステップで URL が出力されるので、これを steps コンテキストで id を指定して参照している形です。
asset_path はアップロード対象のファイルを、asset_name はアップロード後のファイルの名前を指定しています。asset_content_type は今回はバイナリファイルをアップロードするので application/octet-stream を指定しています。

    - name: Upload Release Asset
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.create_release.outputs.upload_url }}
        asset_path: .build/keebio_iris_rev2_lorentzca_${{ github.run_id }}.hex
        asset_name: keebio_iris_rev2_lorentzca_${{ github.run_id }}.hex
        asset_content_type: application/octet-stream

以上です!今回作成したワークフローの全体像は以下にあります。

感想

GitHub Actions めっちゃ便利…。GitHub で完結できるのが嬉しい!

自作キーボード、ハードとソフトの両面から楽しめるので良い。♨