Mirrativ Tech Blog

株式会社ミラティブの開発者(バックエンド,iOS,Android,Unity,機械学習,インフラ, etc.)によるブログです

自動マージでアセット更新作業を楽にする

Unityエンジニアの森田です。今回は前回書いた記事の続きで、CI/CDのちょっとした改善をした話を書いていきます。

tech.mirrativ.stream

課題

以下の記事で書いたように、ミラティブでは複数のアプリのバージョンを運用しており、それに合わせてアセットも複数のバージョンを管理しています。 tech.mirrativ.stream

アセットの更新がアセットのFix用ブランチとして運用しているmasterブランチに取り込まれると、その更新をリリース用のブランチにそれを取り込まなければいけないのですが、この作業をその都度デザイナーからエンジニアに依頼する形になっていました。またバージョンごとに作られたリリース用ブランチ全てに取り込まなければいけないため、とても煩わしいです。

そのため今回はmasterブランチにアセットの更新が入った時点で各リリース用ブランチに自動的にマージされる仕組みをGitHub Actionsで作成しました。

実装

今回求められたのは以下の点です

  • masterブランチに入った変更は全てのreleaseブランチに反映したい
  • 下位バージョンのreleaseブランチに入った変更は全ての上位バージョンのreleaseブランチにも反映したい(逆はしない)

ブランチの解説

これを手短に実現するために、以下の2つのworkflowを作成することにしました。

  1. masterへのマージをトリガーに、存在するreleaseブランチの中で最も下位のブランチに自動的にマージするworkflow
  2. 任意のreleaseブランチへのマージをトリガーに、上位のreleaseブランチ全てに自動的にマージするworkflow

1のworkflowでmasterを最下位バージョンのreleaseブランチにマージするのは、2のworkflowで最下位バージョンの変更が全てのreleaseブランチにも反映されるため、結果的にmasterブランチの変更が全てのreleaseブランチに反映されるためです。

1. masterへのマージをトリガーに、存在するreleaseブランチの中で最も下位のブランチに自動的にマージするworkflow

name: Auto merge master to oldest release

on:
  push:
    branches:
      - master
    
jobs:
  merge:
    runs-on: ubuntu-latest
    continue-on-error: true
    steps:
      - name: Generate github token
        id: generate_token
        uses: tibdex/github-app-token@v1
        with:
          app_id: ${{ secrets.BOT_APP_ID }}
          private_key: ${{ secrets.BOT_PRIVATE_KEY }}
          
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: '0'
          token: ${{ steps.generate_token.outputs.token }}
        env:
          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}

      - name: Set e-mail and username
        run: |
          git config --global user.email "github.actions@mirrativ.co.jp"
          git config --global user.name "GitHub Action"
          
      - name: Merge master to oldest release
        run: |
          git fetch
          list=($(git branch -a | grep remotes/origin/release/ | sed s@remotes/origin/release/@\@ | sort ))
          git checkout release/${list[0]}
          git merge master
          git push origin release/${list[0]}
          git checkout develop
          git merge master
          git push origin develop
        env:
          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}

このworkflowは単純で、masterへのマージを起点に、git branchとgrepで取得したreleaseブランチをsortして、一番下位のバージョンのreleaseブランチにmasterをマージしているだけです。注意するのはworkflow上でマージする際にGITHUB_TOKENの環境変数にパーソナルトークンを設定することです。GitHub Actionsでは意図しないworkflowの再帰実行を防ぐためにこのような制限がかかっていますが、今回下位バージョンのブランチへの変更を上位バージョンのブランチ全てに反映させたいため、パーソナルトークンを使う必要があります。

https://docs.github.com/ja/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow

しかし会社のリポジトリで個人のパーソナルトークンに依存するのはよろしくないので、今回は社内GitHub Appを作成し、tibdex/github-app-token@v1というアクションを使ってパーソナルトークンを生成するようにしました。

zenn.dev

2. 任意のreleaseブランチへのマージをトリガーに、上位のreleaseブランチ全てに自動的にマージするworkflow

name: Auto Merge Releases

on:
  push:
    branches:
      - release/*
  workflow_dispatch: 
    
jobs:
  get-branch-list:
    runs-on: ubuntu-latest
    outputs:
      branch_list: ${{ steps.branch_list.outputs.release_list }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Release branch list
        id: branch_list
        run: |
          git fetch
          temp_list=$(git branch -a | grep remotes/origin/release/ | sed s@remotes/origin/release/@\"@ | sed s/$/\",/ | sort )
          list=$(echo $temp_list | sed s/,$// )
          echo "release_list=[ $list ]" >> $GITHUB_OUTPUT
          
  merge-release-branches:
    needs: get-branch-list
    runs-on: ubuntu-latest
    continue-on-error: true
    strategy:
      fail-fast: false
      matrix:
        value: ${{ fromJson(needs.get-branch-list.outputs.branch_list) }}
    steps:
      - name: Current branch version
        id: current_version
        run: |
          version=$(echo $GITHUB_REF_NAME | sed s@release/@@ )
          echo "value=$version" >> $GITHUB_OUTPUT
          
      - name: Checkout
        if: matrix.value > steps.current_version.outputs.value
        uses: actions/checkout@v3
        with:
          fetch-depth: '0'

      - name: Set e-mail and username
        if: matrix.value > steps.current_version.outputs.value
        run: |
          git config --global user.email "github.actions@mirrativ.co.jp"
          git config --global user.name "GitHub Action"
        
      - name: Merge branch
        if: matrix.value > steps.current_version.outputs.value
        run: |
          git fetch origin release/${{ matrix.value }}
          git checkout release/${{ matrix.value }}
          git merge ${{ github.ref_name }}
          git push origin release/${{ matrix.value }}
          
      - name: Generate github token
        id: generate_token
        if: matrix.value > steps.current_version.outputs.value
        uses: tibdex/github-app-token@v1
        with:
          app_id: ${{ secrets.BOT_APP_ID }}
          private_key: ${{ secrets.BOT_PRIVATE_KEY }}
      
      - name: Kick AssetBundle Build
        if: success() && matrix.value > steps.current_version.outputs.value
        uses: benc-uk/workflow-dispatch@v1
        with:
          workflow: AssetBundle Build Second
          token: ${{ steps.generate_token.outputs.token }}
          inputs: '{ "version" : "${{ matrix.value }}" }'

このworkflowではreleaseブランチのバージョンを取得するjobと、マージを行うjobの2つに分かれています。複数バージョン運用されていることがほとんどなので、matrixを使って並列でマージを行うためにjobを分けています。

バージョンを取得する部分は1つ目のworkflowで行ったものと変わりありませんが、set-outputを使ってマージを行うjobにバージョンのリストを渡しています。GitHub Actions組み込みのfromJson()を使ってmatrixの値に展開するため、バージョンのリストは[ x.x, x.x, x.x]のようにJSONの配列形式で渡しています。

fromJson()でリストを展開した後、$GITHUB_REF_NAMEから現在のreleaseブランチのバージョンを取得し、各stepsのifでバージョン比較して上位バージョンのブランチ以外はスキップするようにします。またマージ後は再びパーソナルトークンを発行し、アセットバンドルビルドを行う他のworkflowを実行して自動的にアセットバンドルビルドが行われるようにしています。 設定が簡単だったので、今回は他のworkflowを起動するのにbenc-uk/workflow-dispatchというActionを使用しています。

github.com

結果

たったこれだけの設定ですが、アセットの更新が行われると自動的に各バージョンに反映&アセットバンドルビルドが行われるようになったため、エンジニアがいちいち作業を止めなくてもよくなりました。

We are hiring!

ミラティブでは一緒に作ってくれるUnityエンジニアを募集しているので、お気軽にお声がけください! www.mirrativ.co.jp speakerdeck.com