ハイパーマッスルエンジニアになりたい

Vim、ShellScriptについてよく書く

GithubActionsでリリースとFormulaリポジトリの更新を自動化した

f:id:rasukarusan:20210120214244p:plain:w500

Homebrewの自作CLIツールの配布がとても面倒くさい

以前Homebrew/tapによる配布方法をまとめたが、やることが結構あって面倒くさい。
どうやらGithubActionsを使えばめちゃくちゃ楽にできるみたいなのでやってみた。

今までの流れ

1. tagをpush
2. GithubでReleaseの作成
3. Formulaファイルの作成or更新
5. 完了

今回目指すのはこれ

1. tagをpush
5. 完了
  • Githubのページを開いてReleaseノートを作成
  • Formulaリポジトリの.rbファイルを更新

この2つの作業を撲滅する。

死ぬほど参考にさせていただいたサイト

基本的に下記のサイトを参考にすればいけました、感謝です。
参考にしていて詰まったところと、変更している箇所を書いていきます。

sasa5740.hatenablog.com

対象リポジトリ

FormulaリポジトリにはFormulaディレクトリを設置していて、複数のツールが登録してある状態。ツールごとにFormulaリポジトリを用意せず、1つのリポジトリで管理するスタイルです。

流れ

  1. リリースノートを作成するworkflow(リリース用のリポジトリ)
  2. Formulaファイルを作成/更新するworkflow(Formula用のリポジトリ)

リリースノートを作成するworkflow

リリースノートには

  • 前回のtagからの差分
  • バイナリファイルのアップロード

を載せます。

リリースリポジトリに.github/workflows/release.ymlを作っていきます

name: Release

on:
  push:
    tags:
      - 'v*'
jobs:
  build:
    name: Create Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      # 1つ前のtagからの差分を取得
      - name: Get commit summary
        id: get_commit_summary
        run: |
          PREVIOUS_TAG=$(git tag --sort=-creatordate | sed -n 2p)
          COMMIT_SUMMARY="$(git log --oneline --pretty=tformat:"%h %s" $PREVIOUS_TAG..${{ github.ref }})"
          COMMIT_SUMMARY="${COMMIT_SUMMARY//$'\n'/'%0A'}"
          echo ::set-output name=COMMIT_SUMMARY::$COMMIT_SUMMARY

      # リリースノートの作成
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          body: |
            ${{ steps.get_commit_summary.outputs.COMMIT_SUMMARY }}
          draft: false
          prerelease: false

      # バイナリファイルのアップロード
      - 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: ./gitblamer
          asset_name: gitblamer
          asset_content_type: application/octet-stream

      # Formulaに値を渡す準備
      - name: Set value for formula
        id: set_value_for_formula
        run: |
          SHA256=$(openssl dgst -sha256 gitblamer | awk '{print $2}')
          echo ::set-output name=SHA256::$SHA256
          echo ::set-output name=BINARY::gitblamer
          echo ::set-output name=CLASS::Gitblamer

      # Formulaリポジトリ更新(Formulaリポジトリのworkflowを発火)
      - name: Update Formula Repository
        uses: peter-evans/repository-dispatch@v1
        with:
          token: ${{ secrets.REPO_ACCESS_TOKEN }}
          repository: Rasukarusan/homebrew-tap
          event-type: released
          client-payload: '
            {
              "ref": "${{ github.ref }}",
              "sha256": "${{ steps.set_value_for_formula.outputs.SHA256 }}",
              "binary": "${{ steps.set_value_for_formula.outputs.BINARY }}",
              "url": "${{ steps.create_release.outputs.upload_url }}",
              "class": "${{ steps.set_value_for_formula.outputs.CLASS }}"
            }
          '

とりあえずこれでtagをpushしたらリリースノートが作られます。

# tagをpush
git tag v1.2.2
git push origin v1.2.2

f:id:rasukarusan:20210120212131p:plain

Formulaファイルを作成/更新するworkflow

本来手動でbrew createコマンドで作るファイルを、step内で作っている感じですね。

name: Update Formula
on:
  repository_dispatch:
    types: [released]
jobs:
  myEvent:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Update formula file
        run: |
          VERSION=$(echo ${{ github.event.client_payload.ref  }} | sed -e "s#refs/tags/##g")
          BINARY=${{ github.event.client_payload.binary }}
          URL="https://github.com/Rasukarusan/$BINARY/releases/download/$VERSION/$BINARY"
          data=$(cat <<EOF > Formula/$BINARY.rb
            class ${{ github.event.client_payload.class }} < Formula
              url "$URL"
              sha256 "${{ github.event.client_payload.sha256 }}"
              def install
                bin.install "$BINARY"
              end
            end
          EOF
          )
      - name: Commit version change
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add -A
          git commit -m "update version"
      - name: Push changes
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}

この状態でリリースリポジトリにtagがpushされたら新しいformulaファイルがコミットされる。

f:id:rasukarusan:20210120212208p:plain

f:id:rasukarusan:20210120212929p:plain
ちゃんとファイルが更新されてる

ここまで終わったらTerminalでCLIツールを更新できるようになっている。

brew info gitblamer
brew upgrade gitblamer

f:id:rasukarusan:20210120212218p:plain

詰まったところ

前回のtagからの差分を出す

# 1つ前のtagからの差分を取得
- name: Get commit summary
id: get_commit_summary
run: |
  PREVIOUS_TAG=$(git tag --sort=-creatordate | sed -n 2p)
  COMMIT_SUMMARY="$(git log --oneline --pretty=tformat:"%h %s" $PREVIOUS_TAG..${{ github.ref }})"
  COMMIT_SUMMARY="${COMMIT_SUMMARY//$'\n'/'%0A'}"
  echo ::set-output name=COMMIT_SUMMARY::$COMMIT_SUMMARY

こちらは下記サイトを参考にさせていただきました。

zenn.dev

注意点として、最初のstepでCheckoutするときにwith: fetch-depth: 0を設定しないと前回のtagが取得できないので注意。デフォルトでは1らしい。

バイナリファイルのアップロード

# バイナリファイルのアップロード
- 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: ./gitblamer
  asset_name: gitblamer
  asset_content_type: application/octet-stream

Homebrewではバイナリを直接指定する方式を取っていたため、zipではなくバイナリをアップロードする必要があります。
つまりasset_content_typeapplication/octet-streamにする。

Github Actionsのusesって何?

誰かが作成したアクション。自分で作ることもできる。Githubが公式でいくつか用意している。
Marketplaceで色んなworkflowが検索できる。

Formulaリポジトリのworkflowを発火する箇所も、本来はcurlでGithubAPIを叩くだけなので別にusesを使わなくてもいいが、使ったほうがcurlで書くよりシンプルに見やすくなる。

# Formulaリポジトリ更新(Formulaリポジトリのworkflowを発火)
- name: Update Formula Repository
uses: peter-evans/repository-dispatch@v1
with:
  token: ${{ secrets.REPO_ACCESS_TOKEN }}
  repository: Rasukarusan/homebrew-tap
  event-type: released
  client-payload: '
    {
      "ref": "${{ github.ref }}",
      "sha256": "${{ steps.set_value_for_formula.outputs.SHA256 }}",
      "binary": "${{ steps.set_value_for_formula.outputs.BINARY }}",
      "url": "${{ steps.create_release.outputs.upload_url }}",
      "class": "${{ steps.set_value_for_formula.outputs.CLASS }}"
    }
  '

${{ github.ref }} の他には何がある?

docs.github.com

終わり

自作ツールのアップデートが最高に楽になった、やったね。