k-shinn’s 雑記

技術メモを書き溜められたら良いな

GithubActionsでVersionに沿ったTAG付けを自動化する

開発バージョンを固める時にcommitにTAGを打つと思いますが、あれって結構面倒ですし、 開発内容に直結しない作業なので手動管理だと忘れがちです。 でも打っておかないと後々問題があって遡る時に辛かったりします…つまりは、自動化したい作業です。

今回はこの作業をGithubActionsで自動化したのと、その際のActionやTipsの紹介です。

自動化したいこと

今回はPRで特定のブランチ(Releaseブランチとか)に変更点をマージした際、以下のことを自動的にやってほしいと思いました。

  • Release用のapkをbuildする
  • versionName(versionCode)でTAGを打つ
    • この際、ReleaseNotes(事前に作成してGithubで管理している)の内容をコメントに転記する
  • TAGの位置(GithubのRelease)にbuildしたapkをアップロードする

使用するAction

上記の内容を行うために、公式及びGitHubMarketplaceからの以下のActionを使用します。 なお、build環境の構築などは割愛します。

(余談)バージョン名管理で悩んだ事

今回上記の get-apk-version が大いに役立ちました。 これがあったからTAGの自動化に踏み切れたと言っても過言ではありません。

というのも、アプリのバージョン管理はあくまでもgradleファイルの中で管理していることであり、 なおかつ変数管理してnameとcodeを作る等の処理が入っているので、GithubActionsの環境下からどうやってそれを取得すれば良いか迷ったからです。

調べてみると、GithubActionsの中でバージョンを管理するためのActionなんかも有りましたが、 元々gradleで管理しているものをGithubActionsに移管するのは違うだろうと思い、これは辞めました。 (CircleCIもまだ併用中で、CI環境に依存する事はしたくなかった等の理由もあります)

では別途読み込ませるためのファイルを管理するか…とも思いましたが、それも面倒ですし、逆に手間が増えてしまうので、 あくまでも現状の開発フロー内でどうにか出来ないかと考えていた所、上記Actionを見つけたという経緯です。

これなら現状の開発フローに更に手作業を増やすことはなく、GithubActionsにバージョンを持ってくることが出来ました。

コマンド結果を変数に入れて活用する

get-apk-version を使用する際に問題が発生しました。 build時にapkファイル名にバージョンを入れて出力する設定をしていたため(「v202010-release.apk」の様な感じ)、「バージョンを取得するためのActionにファイル名を入れるためにバージョンが必要」という状況が発生したのです。

これは同僚氏から「findすれば行けるのでは」とコマンド例をもらったことで解決出来ました。 最終的には以下のようにパスを変数に格納することで、他のActionに取り回せるようになりました。

- name: get file path
  id: find_path
  run: |
    APK_PATH=`find app/build/outputs/apk/production/release/ -name \*.apk | head -n 1`
    echo "::set-output name=PATH::${APK_PATH}"
- name: Get Apk Version
  id: apk
  uses: JantHsueh/get-apk-info-action@1.0
  with:
    apkPath: ${{ steps.find_path.outputs.PATH }}

変数の活用については以下のドキュメント辺りを参照すると良いと思います。 - GitHub Actionsのワークフローコマンド - GitHub Docs

Actionを使いつつ、痒いところにはコマンドを併用することで、大抵の事は出来るようになる気がします。

完成したAction

これまでの事を活用することで、build〜Slack通知までを、以下のように書くことができました。

# build
- name: build apk
  run: |
    ./gradlew assembleProductionRelease
# APKからバージョンを取得
- name: get file path
  id: find_path
  run: |
    APK_PATH=`find app/build/outputs/apk/production/release/ -name \*.apk | head -n 1`
    echo "::set-output name=PATH::${APK_PATH}"
- name: Get Apk Version
  id: apk
  uses: JantHsueh/get-apk-info-action@1.0
  with:
    apkPath: ${{ steps.find_path.outputs.PATH }}
# Tagの作成
- name: Create Release
  id: create_release
  uses: actions/create-release@v1
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  with:
    tag_name: v${{ steps.apk.outputs.versionNum }}(${{ steps.apk.outputs.versionCode }})
    release_name: Release v${{ steps.apk.outputs.versionNum }}(${{ steps.apk.outputs.versionCode }})
    body_path: LastReleaseNotes.md
# apkのUpload
- name: Upload Release Asset
  id: 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: ${{ steps.find_path.outputs.PATH }}
    asset_name: ${{ steps.apk.outputs.versionCode }}-production-release.apk
    asset_content_type: application/vnd.android.package-archive
# Slack通知、成功時
- name: Slack Notification on SUCCESS
  if: success()
  uses: tokorom/action-slack-incoming-webhook@main
  env:
    INCOMING_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
  with:
    text: "Release Actions Succeeded!"
    attachments: |
      [
        {
          "color": "good",
          "fields": [
            {
              "title": "GitHub Actions URL",
              "value": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            },
            {
              "title": "TAG URL",
              "value": "${{ steps.create_release.outputs.html_url }}"
            }
          ]
        }
      ]
# Slack通知、失敗時
- name: Slack Notification on FAILURE
  if: failure()
  uses: tokorom/action-slack-incoming-webhook@main
  env:
    INCOMING_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
  with:
    text: "Release Actions Failed..."
    attachments: |
      [
        {
          "color": "danger",
          "fields": [
            {
              "title": "GitHub Actions URL",
              "value": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            }
          ]
        }
      ]

これによって、Assetを含んだReleaseTAGが自動で作成できました。

f:id:k-shinn:20210614104133p:plain
GithubActionsから作成されたReleaseTAG

また、リンク付きのSlack通知も出来ました。(アイコンは失敗時と分けたほうが良いかも…)

f:id:k-shinn:20210614104205p:plain
GithubActionsからのSlack通知

この様な既存Actionが簡単に使用できる他、Slack通知に使用しているようなGithub上のリンクも取得しやすいのがとても助かる点です。失敗時のリンクを貼っておくとすぐに確認しにいけて便利で特にオススメです。

今回の事例では、Acitonとコマンドの併用について学べたのが、個人的に特にプラスになった点です。 更になんとかしたい点としては、ReleaseNotesの更新漏れをどうにか検知できないかといった点があります。これについてはおいおい考えていきたい所です。

皆さんも、GithubActionsで細かなCI課題を解決していきましょう。