QualiArtsのGitHub Actionsへの取り組みやアプリビルドについて

# はじめに
株式会社QualiArtsでUnityエンジニアをしている山本優威です。
本記事は、[QualiArts Advent Calender 2023](https://qiita.com/advent-calendar/2023/qualiarts) の24日目の記事になります。
今回はQualiArtsでのGitHub Actionsへの取り組みや、実際に私がアプリビルドをGitHub Actionsで動作(2023/12/20時点)するようにしましたので、その紹介になります。

# QualiArtsでのGitHub Actionsへの取り組み
QualiArtsでは今までGitHub Actionsを使って開発に活用をしてきました。
[こちらの弊社の田村が執筆した記事](https://technote.qualiarts.jp/article/52/)に詳細が載っていますが、主に
- PullRequestの自動テスト
- 定期的なコードのクリーンアップ
- 開発バージョン間の自動マージ
- PullRequestのラベル付与

などで活用しています。

https://technote.qualiarts.jp/article/52/

また最近の取り組みとして、Jenkinsで動作させていた**アプリビルドやUnityのアセットバンドルビルド**などをGitHub Actionsに移行させる動きがあります。
今回はその一環でアプリビルドをJenkinsからGitHub Actionsに移行させた話にもなります。

# GitHub Actionsを使ったアプリビルドについて
## 動機
先程取り組みについて紹介したところでも記載がありますが、従来QualiArtsではJenkinsというCIツールを使ってアプリビルドをしてきました。
ですが、使っていくうちに様々な問題が出てきたため、GitHub Actionsに移行することにしました。
理由につきましては紹介した弊社の田村の記事から引用させていただきます。

>大きな問題の1つは、ジョブ（GitHub Actionsでいうワークフロー）の設定が大変という点です。 Jenkinsの設定はGUIで行われるため、気軽に設定可能という利点があるものの、コードで管理するのが難しく、履歴を追うのが難しかったり再利用性に乏しかったりします。 また、設定の変更をレビューするようなフローも作りづらいため、その設定について作った人しか知らないという属人化が発生しがちです。
>
>もう1つ大きな問題は、管理が大変という点です。 Jenkinsはセキュリティの観点から継続的にアップデートが必要ですが、多数のプラグインがインストールされたJenkins環境では、プラグインの依存バージョンの問題などからアップデートは簡単ではありません。
>
>このような問題を解消できるかもしれない策として、GitHub Actionsの利用を進めています。 特にワークフローの設定に関しては、YAML形式でGitHub上で管理されるのに加えて、再利用も可能なため、Jenkinsにおける問題を解消できそうだと手応えを感じています。

## GitHub Actions化
動作の流れとコードをお見せした方がイメージが付きやすいと思うので、図解した処理の流れと完成形のYAMLコードを載せておきます。
任意で修正する必要がある箇所や修正点はコメントアウトで記載してあるので、参考にしてみてください。

![アプリビルドのフロー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643763/912b5015-335b-e66c-5c82-5263de380af1.png)

```yml:app-build.yml
name: Build-iOS or Android

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment'
        required: true
        type: choice
        default: 'Develop'
        options:
          - Custom
          - Develop
          - Staging
          - Release
      appCenterName:
        description: 'App Centerのアプリ名'
        required: true
        type: string
        default: 'hoge.hoge.hoge'
      jsonProperties:
        description: 'json Properties'
        required: false
        type: string
        default: '{"property1":"hoge","property2":"1","property3":"false"}'

env:
  TARGET_PLATFORM: iOS or Android

jobs:
  build:
    name: App Build
    runs-on: [ ] # 任意のランナーを選択
    environment: ${{ inputs.environment }}

    steps:
      # Inject
      - name: Inject
        id: inject-data
        run: |
          # Json型のデータからEnvironment・Outputを設定できるようにする
          # 例
          jsonProperties='${{ inputs.jsonProperties }}'

          property1=$(echo ${jsonProperties} | jq -r .property1)
          property2=$(echo ${jsonProperties} | jq -r .property2)
          property3=$(echo ${jsonProperties} | jq -r .property3)
          
          echo "PROPERTY1=${property1}" >> $GITHUB_OUTPUT
          echo "PROPERTY2=${property2}" >> $GITHUB_ENV
          echo "PROPERTY3=${property3}" >> $GITHUB_ENV
          
      # Checkout
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          clean: true
          submodules: recursive
          persist-credentials: false
          ssh-key: ${{ secrets.SSH_KEY }} # 任意のSSHキーを設定

      # Cache Library
　　　 - name: Cache Library
        uses: actions/cache@v3
        with:
          path: Library
          key: Library-${{ hashFiles('Assets/**', 'Packages/**','ProjectSettings/**') }}
          restore-keys: Library-

      # Unity Build
      - name: UnityBuild
        run: |
          UNITY_PATH={ # UnityのPathを指定 }/Unity
          UNITY_PATH \
            -buildTarget ${{ env.TARGET_PLATFORM }} \
            -quit \
            -batchmode \
            -nographics \
            -silent-crashes \
            -logFile - \
            -executeMethod BuildScript.PerformBuild \ # 実行するクラス.関数を設定
            -projectPath ./

      # XCode Build
      # iOSビルドの場合はここでXCodeビルドをする
 
      # Check AppCenter
      - name: Check AppCenter
        id: appcenter
        run: appcenter -v
        continue-on-error: true

      # Install AppCenter CLI
      - name: Install appcenter-cli
        if: ${{ steps.appcenter.outcome == 'failure' }}
        run: npm install -g appcenter-cli
      
      # Upload to App Center
      - name: Upload to App Center
        env:
          APPCENTER_ACCESS_TOKEN: ${{ secrets.APPCENTER_ACCESS_TOKEN }}
          APPCENTER_UPLOAD_FILE: ${{ env.PROJECT_PATH }}/Builds/Android.apk # iOSの場合はまた別のファイルパス
          APPCENTER_APPLICATION: ${{ inputs.appCenterName }}
          APPCENTER_DISTRIBUTION_GROUP: ${{ var.distributionGroup }}
        run: |
        　　appcenter \
        　　  distribute \
　　　　　　　　release \
　　　　　　　　--disable-telemetry \
　　　　　　　　--debug \
　　　　　　　　--silent \
　　　　　　　　--file "${APPCENTER_UPLOAD_FILE}" \
　　　　　　　　--app "${APPCENTER_APPLICATION}" \
　　　　　　　　--group "${APPCENTER_DISTRIBUTION_GROUP}" \
　　　　　　　　-R "${{ format('{0}/release_note.txt', github.workspace) }}" # ReleaseNoteがあれば指定
```

お見せできない部分も多いため、簡易的なコードになってしまいましたが雰囲気はこんな感じの流れで記載し、必要な部分を追記しながら書いていくようにすれば動くようになると思います。
また実際にはiOSとAndroidで別のyamlにしていたり、証明書やプロジェクト単位で必要なInject、設定をビルド前にしていたりします。

Jenkinsからの移行ということもあり、Jenkinsもyamlで記述する関係上コードで変更した点は多くなく、GitHub特有のPathや書き方に変更しています。

## 工夫点
### Input制限の突破
GitHub Actionsではinputで宣言できる上限が決まっていて、その個数は10になります。
様々なパラメータを必要とするアプリビルドでは10個では足りない可能性があり、今回はinputの1つを使ってjson形式でstringでパラメータを一部受け取るようにしています。

```yml:app-build.yml

on:
  workflow_dispatch:
    inputs:
      jsonProperties:
        description: 'json Properties'
        required: false
        type: string
        default: '{"property1":"hoge","property2":"1","property3":"false"}'

jobs:
  build:
    name: App Build
    runs-on: [ ] # 任意のランナーを選択
    environment: ${{ inputs.environment }}

    steps:
      # Inject
      - name: Inject
        id: inject-data
        run: |
          # Json型のデータからEnvironment・Outputを設定できるようにする
          # 例
          jsonProperties='${{ inputs.jsonProperties }}'

          property1=$(echo ${jsonProperties} | jq -r .property1)
          property2=$(echo ${jsonProperties} | jq -r .property2)
          property3=$(echo ${jsonProperties} | jq -r .property3)
          
          echo "PROPERTY1=${property1}" >> $GITHUB_OUTPUT
          echo "PROPERTY2=${property2}" >> $GITHUB_ENV
          echo "PROPERTY3=${property3}" >> $GITHUB_ENV
```

展開方法は少し複雑ですが、inputsのjsonPropertiesからstringを受け取り、
```yml:app-build.yml
jsonProperties='${{ inputs.jsonProperties }}'
```
下記のコードのようにすればjsonのpropertyに対応するvalueが受け取れるようになります。
```yml:app-build.yml
property=$(echo ${jsonProperties} | jq -r .jsonで指定したproperty)
```
あとはそのjob内で受け取ったvalueを使用できるようにOutputやEnvにすれば良いです。
```yml:app-build.yml
echo "PROPERTY=${property1}" >> $GITHUB_OUTPUT
```

### json型のinputにしたことによる影響の回避
上記の工夫でjson型のinputを追加したことにより、GitHub Actionsからそのjsonの内容を変更しようとした場合に難易度の向上とニューマンエラーの可能性が出てきました。
そこでQualiArtsではSlackアプリを使用してGUIを用意することにより、json型入力の影響を回避することにしました。
また他のメリットとしては他の職種の方もアプリビルドをすることがあるため、GitHubを開かなくて良くなるようにもしています。

#### アプリのHomeとModalの見た目
アプリの導入については[こちらの記事](https://qiita.com/seratch/items/1a460c08c3e245b56441)を参考にさせていただきました。

https://qiita.com/seratch/items/1a460c08c3e245b56441

上記の方法を用いてアプリを作成し、アプリビルド用のHomeとModalはこんな感じにしました。

![SlackApp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643763/310fb3de-11b8-b530-e1b8-7938c42f9bbe.png)

各ビルド環境とプラットフォーム毎にボタンを用意し、そのボタンを押すと対応するModalが開かれる仕組みになっています。
例だとAndroidのModalが開かれており、パラメータを入力後「実行」ボタンを押すことによりワークフローのdispatchイベントが呼ばれます。

また誰でもアプリビルドをできてしまうとそれはそれで問題になってしまうので、Homeが開かれたタイミングやモーダルを開くタイミングでSlackのUserIdを取得し、実行できるユーザーかどうかをチェックするようにもしています。
実行できるユーザーのホワイトリストはSlackのUserGroupを利用することにより実現していますので、Slack完結型にもなっています。

# まとめ
今回はQualiArtsのGitHub Actionsへの取り組みとGitHub Actionsでのアプリビルドの方法について簡単に紹介させていただきました。
GitHub Actionsを活用したアプリビルド自体はそこまで実装難易度が高い訳でもないので是非参考にして試してみてください！