Scala用GitHub Actions紹介 ～シミュレータ・VRを繋ぐ「ArkTwin」のOSS化にあたって～

トヨタ自動車でリサーチャーをしている鳥越です。
Scalaで研究開発しているフレームワーク「[ArkTwin](https://github.com/arktwin/arktwin)」をOSS化するにあたり、GitHub Actionsを色々と設定してみました。しっかりCI組むのは久々だったのですが、昔よりも多くのことが簡単に自動化できて感動したので、その設定を10個ほど紹介します！

ちなみにArkTwinは、多種多様なシミュレータ・VRを繋いで街規模のシミュレーションを実現するためのメッセージングフレームワークです。

![ArkTwinデモ画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55208/63db17c4-4826-75aa-6f74-61e1b7a2de0c.png)

この写真は、交通流シミュレータ・仮想インフラカメラ・人流シミュレータ・VR歩行者・ドライブシミュレータをArkTwinによって繋いだデモの様子です。みんな同じ一つの仮想世界にいるため、お互いを視認したり避けたりすることができます。このデモでは各PCをLAN接続していますが、もちろんインターネット越しで接続することも可能です。
つまり雑に言うと、みんなが思い思いのシミュレータ・VRを繋いでオンラインゲームみたいなことができちゃいます（ということを目指しています）

ArkTwinについて詳しく知りたい方は、ぜひ次のリンクもご覧ください :star: 

[マルチメディア、分散、協調とモバイル(DICOMO2024)シンポジウムにて「スマートシティ模擬のための異種シミュレーション連携プラットフォーム: ArkTwinの開発」が野口賞を受賞 - トヨタ大手町](https://www.toyota-tokyo.tech/news/240731.html)

https://github.com/arktwin/arktwin

# 1. 単体テスト (ScalaTest)

Scalaのテストフレームワーク[ScalaTest](https://www.scalatest.org/)による単体テストです。CIの基本ですね。

## 設定

```yaml
- name: sbt test
  working-directory: arktwin
  run: sbt test
```
[.github/workflows/scala-ci.yaml](https://github.com/arktwin/arktwin/blob/v0.5.6/.github/workflows/scala-ci.yaml#L41)

## 実行例

![単体テストの実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55208/4223ec7d-419b-9787-ccbf-14fac13c07ba.png)

# 2. 結合テスト (Gatling)

GitHub Actionsのランナーでは、nohupコマンドによりバックグラウンドプロセスとしてサーバーも動かせるため、ローカル通信による結合テストが可能です。
ArkTwinにはCenterとEdgeという二つのモジュールがあり相互通信します。その結合テストを負荷試験ツール[Gatling](https://docs.gatling.io/)により、次の手順で行なっています。

1. ArkTwin Centerをバックグラウンド実行
1. ArkTwin Centerのヘルスチェックが成功するまで待つ
1. ArkTwin Edgeをバックグラウンド実行してCenterへ接続
1. ArkTwin Edgeのヘルスチェックが成功するまで待つ
1. GatlingでArkTwin EdgeのREST APIをテスト

## 設定

```yaml
- name: sbt center/run in the background
  working-directory: arktwin
  run: |
    nohup sbt center/run &
    curl --retry 180 --retry-delay 1 --retry-all-errors http://localhost:2236/health

- name: sbt edge/run in the background
  working-directory: arktwin
  run: |
    nohup sbt edge/run &
    curl --retry 180 --retry-delay 1 --retry-all-errors http://localhost:2237/health

- name: sbt e2e/Gatling/test
  working-directory: arktwin
  run: sbt e2e/Gatling/test
```
[.github/workflows/scala-ci.yaml](https://github.com/arktwin/arktwin/blob/v0.5.6/.github/workflows/scala-ci.yaml#L63)

## 実行例

![結合テストの実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55208/7c135bc9-25de-0da2-8d30-c66a649ebd9a.png)

# 3. REST API仕様変更検出 (oasdiff-action)

ArkTwin Edgeには、シミュレータ・VR接続用のREST APIがあります。
改修時に意図しないREST API変更を引き起こしていないか確認するため、ArkTwin EdgeにOpenAPI仕様書を出力させたのち、OpenAPI変更検出ツール[oasdiff-action](https://github.com/oasdiff/oasdiff-action)によってmainブランチおよび最新リリースとの比較を行っています。
この情報はプルリクエストのマージ前に確認するほか、リリースノートに記載するAPI変更履歴としても利用しています。

## 設定（抜粋）

```yaml
- name: Generate OpenAPI YAML files
  working-directory: arktwin
  run: |
    sbt "edge/run generate-openapi-edge ../temp/arktwin-edge-head.yaml"
    git checkout $(git tag -l "v*" --sort=-v:refname | head -n 1)
    sbt clean "edge/run generate-openapi-edge ../temp/arktwin-edge-release.yaml"

- name: Generate /api/edge changelog from latest relase
  uses: oasdiff/oasdiff-action/changelog@v0.0.19
  with:
    base: arktwin/temp/arktwin-edge-release.yaml
    revision: arktwin/temp/arktwin-edge-head.yaml
    output-to-file: arktwin/temp/arktwin-edge-changelog-from-release.txt

- name: Display /api/edge changelog from latest release
  run: cat arktwin/temp/arktwin-edge-changelog-from-release.txt
```
[.github/workflows/scala-ci.yaml](https://github.com/arktwin/arktwin/blob/745851d00cf4ac9f3e690f01c23cc2a4e2de0842/.github/workflows/scala-ci.yaml#L117)

## 実行例

![REST API仕様変更検出の実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55208/70a03dbe-1df8-4b09-f2c9-5da3972c457a.png)

# 4. REST API仕様公開 (deploy-pages)

リリースタグを打った際、OpenAPI仕様書を参照するSwagger UIの静的ページを生成し、[deploy-pages](https://github.com/actions/deploy-pages)によってGitHub Pagesにて公開しています。
このSwagger UIページはArkTwin Edge自身も配信しているのですが、起動することなく最新の仕様を確認できるのは便利なため、私自身が開発や説明によく利用しています。

## 設定

```yaml
- name: Generate OpenAPI YAML files
  working-directory: arktwin/arktwin
  run: sbt "edge/run openapi-center ../../pages/dist/swagger-ui/center/arktwin-center.yaml" "edge/run openapi-edge ../../pages/dist/swagger-ui/edge/arktwin-edge.yaml"

- uses: actions/checkout@v4
  with:
    repository: swagger-api/swagger-ui
    ref: v5.17.14
    path: swagger-ui

- name: Copy Swagger UI static files
  run: |
    mkdir arktwin/pages/dist/swagger-ui/bundle
    cp swagger-ui/dist/* arktwin/pages/dist/swagger-ui/bundle
    rm arktwin/pages/dist/swagger-ui/bundle/index.html

- uses: actions/configure-pages@v5

- uses: actions/upload-pages-artifact@v3
  with:
    path: arktwin/pages/dist

- uses: actions/deploy-pages@v4
  id: deployment
```
[.github/workflows/github-pages.yaml](https://github.com/arktwin/arktwin/blob/v0.5.6/.github/workflows/github-pages.yaml#L40)

## 実行例

![REST API仕様公開の実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55208/d7f305be-f13b-776a-8da1-e0a84c662841.png)

# 5. コードフォーマットのチェック (Scalafix & Scalafmt)

sbtのビルド設定にて、コードフォーマッター[Scalafix](https://scalacenter.github.io/scalafix/) & [Scalafmt](https://scalameta.org/scalafmt/)によりコンパイル時に自動でコードフォーマットするようにしています。
そのため基本的にはフォーマット済みのコードがコミットされるはずなのですが念のため、未フォーマットのファイルが残っているとエラーになるコマンドを利用してCIでもチェックしています。

## 設定

```yaml
- name: sbt scalafixAll --check
  working-directory: arktwin
  run: sbt "scalafixAll --check"

- name: sbt scalafmtCheckAll
  working-directory: arktwin
  run: sbt scalafmtCheckAll
```
[.github/workflows/scala-ci.yaml](https://github.com/arktwin/arktwin/blob/v0.5.6/.github/workflows/scala-ci.yaml#L27)

# 6. ファイルヘッダのチェック (sbt-header)

現状、Scalaファイルヘッダにて下記のライセンス表示をしています。

```
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 TOYOTA MOTOR CORPORATION
```

sbtのビルド設定にて、[sbt-header](https://github.com/sbt/sbt-header)によりヘッダをコンパイル時に自動挿入するようにしています。
そのため基本的にはヘッダ挿入済みのコードがコミットされるはずなのですが念のため、未挿入のファイルが残っているとエラーになるコマンドを利用してCIでもチェックしています。

## 設定

```yaml
- name: sbt headerCheck
  working-directory: arktwin
  run: sbt center/headerCheck center/Test/headerCheck common/headerCheck common/Test/headerCheck e2e/Test/headerCheck edge/headerCheck edge/Test/headerCheck
```
[.github/workflows/scala-ci.yaml](https://github.com/arktwin/arktwin/blob/v0.5.6/.github/workflows/scala-ci.yaml#L37)

# 7. 依存ライブラリのライセンスチェック (sbt-license-report)

ArkTwinのライセンスはApache License 2.0なのですが、依存ライブラリについても使いやすいライセンスのものに留める方針です。
そのため[sbt-license-report](https://github.com/sbt/sbt-license-report)によって、ホワイトリストに載せていないライセンスのライブラリへの依存が発生した場合はエラーになるようにしています。

## 設定

```yaml
- name: sbt center/licenseCheck
  working-directory: arktwin
  run: sbt center/licenseCheck
```
[.github/workflows/scala-ci.yaml](https://github.com/arktwin/arktwin/blob/v0.5.6/.github/workflows/scala-ci.yaml#L93)

# 8. Dependency Graph更新 (sbt-dependency-submission)

GitHubにはレポジトリ全体の依存を検出する機能Dependency Graphがあるものの、sbtは残念ながらサポートされていません。そこで[sbt-dependency-submission](https://github.com/scalacenter/sbt-dependency-submission)によって、Scalaの依存ライブラリを追加するようにしています。
なお、Dependency GraphはテストライブラリやGitHub Actionsなどの依存も対象とするため、ソフトウェアそのものの依存よりも対象が広いことには留意してください。

## 設定

```yaml
- uses: scalacenter/sbt-dependency-submission@v3
  with:
    working-directory: arktwin
```
[.github/workflows/dependency-submission.yaml](https://github.com/arktwin/arktwin/blob/v0.5.6/.github/workflows/dependency-submission.yaml#L19)

## 実行例

![Dependency Graph更新の実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55208/4f56d810-291d-edfa-6462-e4b951d342e6.png)

# 9. Dockerイメージビルド (build-push-action)

[build-push-action](https://github.com/docker/build-push-action)によってDockerイメージをビルドしています。
名前のとおりプッシュもできるのですが、現状はDockerfileの公開のみでイメージの配布は行なっていないため、ビルド成功するかどうかのみテストしています。

## 設定

```yaml
- uses: docker/build-push-action@v6
  with:
    file: docker/center.dockerfile
    tags: arktwin-center
    push: false
```
[.github/workflows/scala-ci.yaml](https://github.com/arktwin/arktwin/blob/v0.5.6/.github/workflows/scala-ci.yaml#L169)

# 10. CLA同意確認 (contributor-assistant)

プルリクエストを送ってくださった方にCLA (Contributor License Agreement)への同意と確認を自動で行うbotを[contributor-assistant](https://github.com/contributor-assistant/github-action)によって動かしています。
ちなみに導入時のバグがあるものの回避策を見つけたので報告済みです。 [#155](https://github.com/contributor-assistant/github-action/issues/155#issuecomment-2190310649)

## 設定

```yaml
- uses: contributor-assistant/github-action@v2.6.1
  if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  with:
    path-to-signatures: signatures/version1/cla.json
    path-to-document: https://github.com/arktwin/arktwin/blob/main/CLA.md
    branch: main
    allowlist: dependabot[bot], scala-steward
```
[.github/workflows/cla-assistant.yaml](https://github.com/arktwin/arktwin/blob/v0.5.6/.github/workflows/cla-assistant.yaml#L23)

## 実行例

![CLA同意確認の実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55208/9ab428c9-431b-5c27-3703-5d5145f8467c.png)

# おわりに

本記事が何かしら参考になったよという方は、ぜひGitHubの方でもスターいただけると嬉しいです :star: 

https://github.com/arktwin/arktwin