モバイルゲーム運用における開発アセット管理をちょっとだけ整えた話

この記事は、この記事は Akatsuki Advent Calendar 2019 18日目の記事です。

背景

運用中のモバイルゲームでは、往々にして複数アプリバージョン、並びに複数デプロイ分の並行開発が定常的に行われています。 そして、モバイルゲームは機能が一個前のリリースの機能に依存することがままあります。 12月1週目 = v1 12月2週目 = v2 12月3週目 = v3 とした場合、v2のリリースには当然v1の変更が必要です。例えばv1で新しくリリースされたキャラクターがv2で消える、なんてことはないということです。 すると、以下のようなことが起きます

ケース1

バイナリファイルがコンフリクトします。 これがアトラス画像だったりすると、ひと目見てどっちが正しいデータなのか?はたまた、どちらも間違っているのか?なんてことも考える必要が出てきます。 作業としてはこんな感じです。

その結果、こうなります。

これは当然共通で用いているリモートブランチ環境整備になるので非エンジニアメンバーの作業を大きく停滞することになります。そんなときに例えばMTGがあったりしてうっかり見逃してしまうと…

あとはおわかりでしょう。

ケース2

チームが大規模になってくると、全体の状況をコミュニケーションのみで把握するのは難しいです。 そのため、しっかり仕組みを整えないと欠落する情報が出てきます。 どちらのケースも、チームの駆け込み寺こと僕が奔走して解決することが多いですが、規模的に追いつかないこともままあります。 できれば仕組み化して余計なことを考えないようにしたいです。 これらのケースでは説明を容易にするためにブランチ数を減らしていますが、実際には多くの開発環境を扱っているので複雑さはこれの比ではありません。 これを先日リリースされたGitHub Actionsを利用して解決してみようとおもいます。

どうなればいいか

先にリリースされるブランチの情報を、それより先のブランチに常に吸収し続けていけばよいです。 つまり、v1 < v2 < v3 という継承関係が作られれば良いと考えました。 そのため、理想の状況は常に以下のようになっていることだと考えられます。

とはいえ、共有しているベースブランチをrebaseし続けるのはそれはそれで良くないです。 そのため、変更差分が自動で対象ブランチに伝搬してマージされていくことで、擬似的に上記の状況をつくりたいと考えました。

やったこと

作ったワークフローは以下のようなものです。

name: developブランチが更新されたら、「version」のつくブランチを更新する
on:
pull_request:
types: [closed]
branches: [base_branch]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Get target branches
run: |
curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.repository.url }}/branches | jq '.[].name' | grep 'target_branch_rules' > target_branches.csv
- name: update and merge PR
run: |
while read row; do
echo '{"title": "auto merge PR", "body": "ベースブランチが更新されました", "head": "base_branch", "base": '"${row}"' }' | jq . > param.json
curl -X POST -H "Authorization: token ${GITHUB_TOKEN}" -d @param.json ${{ github.event.repository.url }}/pulls -s | jq -r '._links.self.href + "/merge"' | xargs curl -X PUT -H "Authorization: token ${GITHUB_TOKEN}"
done < target_branches.csv
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

name

name: developブランチが更新されたら、「version」のつくブランチを更新する

Workflowの名前です。

on

on:
pull_request:
types: [closed]
branches: [base_branch]

Workflowを動かすフックの指定です。 base_branchブランチをターゲットとしたPRがマージされたときにWorkflowが動きます。 PRがCloseされたときも発動して失敗しますが、運用上あまりない上に失敗しても困ることはないのでいまのところは許容としてます。

jobs

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Get target branches
run: |
curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.repository.url }}/branches | jq '.[].name' | grep 'target_branch_rules' > target_branches.csv
- name: update and merge PR
run: |
while read row; do
echo '{"title": "auto merge PR", "body": "ベースブランチが更新されました", "head": "base_branch", "base": '"${row}"' }' | jq . > param.json
curl -X POST -H "Authorization: token ${GITHUB_TOKEN}" -d @param.json ${{ github.event.repository.url }}/pulls -s | jq -r '._links.self.href + "/merge"' | xargs curl -X PUT -H "Authorization: token ${GITHUB_TOKEN}"
done < target_branches.csv
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
  1. 対象リポジトリ上から、特定の命名規則に沿ったブランチ一覧を取得(v1,v2,v3としているブランチに命名規則があります)
  2. それらに対して、base_branchからPullRequestを作成
  3. 2のPullRequestをマージということをします。

GitHub Actions上では、コンテキストへの参照が容易にできます。例えば今回は自身のエンドポイントがほしいので ${{ github.event.repository.url }} といったものを用いています。 利用できるコンテキストは 公式のドキュメント から確認できます。GitHub上でなんやらかんやらする、みたいなものはほぼほぼ完結できるんじゃないかなと思います。素晴らしいですね。

まとめ

直接マージしていってしまうことも考えられますが、今回はPRを作ってそれをマージするという手法をとりました。 これは個人的な好みですが、PRするとWebのUI上で非エンジニアでも比較的簡単にマージされた時間とか確認することができるため、役立つことがあるだろうと考えたからです。 「運用でカバー!」病は小さな作業で大きな改善に進むことがあるので、常に感度を高くアンテナはっていたいですね。 運用でカバー依存症の出典↓(この本の感想はまたどこかで)

https://www.amazon.co.jp/dp/B07ZQWVND3?&linkCode=ll1&tag=urapro29-22&linkId=e512e93d9b0ba44e3eedcbfc2ada0826&language=ja_JP&ref_=as_li_ss_tl

以上です。

おまけ

つまづき1

本当は最初は

  1. 特定のブランチへ変更が入ったら、指定されたブランチをマージ
  2. 特定のラベルのついたPRを自動でマージ と2つのワークフローにしていた(2番がいろいろ流用できて便利だと思っていた)のですが、この場合Workflowは連鎖しないようでした。

つまづき2

GitHubのブランチ一覧のAPIで取得できるのは30件までです。31件以上のブランチで定常的に運用される場合は、自動更新されるブランチの定義方法を変えたほうが良さそうです。 ブランチの治安をちゃんと保とう、と感じました。