GitHub ActionsでHomebrewのtapを更新する
はじめに
こちらは Aizu Advent Calendar 2021 8日目の記事となります。
どうも、 @uzimaru0000 です。
以前、uzimaru0000/tv を開発した時にGitHubActions経由でHomebrewのtapを更新したので、そのことについてまとめます。
Homebrew Tapとは?
公式以外のリポジトリからフォーミュラを参照できる機能です。
これによりHomebrew経由で自分が作ったアプリケーションをインストールすることが出来るようになります。
詳しくは、公式docsをご覧ください。
モチベーション
もちろん、tapリポジトリは手動でも更新可能です。しかし、以下のような問題点があります。
- 開発初期の高頻度のリリースに合わせて手動で更新するのは大変
- 更新に必要な値(アプリのチェックサム等)を集めるのが大変
これらの問題が面倒になり結果的に更新、開発が止まるという事が良くありました。
こう言う面倒事は仕組みで解決したいのでCIで更新できるようにします。
更新の流れ
更新をするために以下のようなフローで行います。
- 開発ブランチで開発をする
- mainのブランチにMargeする
- 程よいタイミングでリリースする
- リリースtagがコミットされたらCIが走る
- tapリポジトリにPRが作られる
- Margeされて更新完了
GitHubActionsでは4と5の間をやります。
やり方
今回は別のリポジトリにPRを立てる方法として対象のリポジトリのCIを動かすと言う方法をとりました。
その際のtriggerには repository_dispatch を使いました。
repository_dispatch はGitHubAPI経由でActionsを発火する事ができる機能です。
詳しい使い方は、↓の記事が分かりやすいです
図にするとこんな感じ
(汚い図ですみません🙇♂️)
tap側の設定
tapリポジトリ
アップデートスクリプト
#!/usr/bin/env node
const fs = require('fs');
const { exit } = require('process');
const formulaTemplatePath = (name) => `template/${name}.rb.tmp`;
const formulaPath = (name) => `Formula/${name}.rb`;
const main = ({ formula, description, url, sha256, version }) => {
if (!fs.existsSync(formulaTemplatePath(formula))) {
console.error(`Error: ${formulaTemplatePath(formula)} is not exists`);
exit(1);
}
const template = fs.readFileSync(formulaTemplatePath(formula)).toString();
const code = template
.replace(/{{\s?description\s?}}/, `"${description}"`)
.replace('{{ url }}', `"${url}"`)
.replace('{{ sha256 }}', `"${sha256}"`)
.replace('{{ version }}', `"${version}"`);
fs.writeFileSync(formulaPath(formula), code);
};
const [, , formula, description, url, sha256, version] = process.argv;
main({ formula, description, url, sha256, version });
テンプレート
class Tv < Formula
desc {{description}}
homepage "https://github.com/uzimaru0000/tv"
url {{ url }}
sha256 {{ sha256 }}
version {{ version }}
def install
bin.install "tv"
zsh_completion.install "completions/zsh/_tv"
bash_completion.install "completions/bash/tv.bash"
fish_completion.install "completions/fish/tv.fish"
end
end
workflow.yaml
name: Update brew
on:
repository_dispatch:
types: [update-brew] # with client_payload.packages
workflow_dispatch:
inputs:
formula:
description: 'update target formula'
required: true
default: ''
description:
description: 'formula description'
required: true
default: ''
url:
description: 'Download URL'
required: true
default: ''
sha256:
description: 'checksum'
required: true
default: ''
version:
description: 'version'
required: true
default: ''
jobs:
update-brew:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: setup Node
uses: actions/setup-node@v1
- name: Update formula
run: |
./scripts/update.js\
"${{ github.event.client_payload.formula }}"\
"${{ github.event.client_payload.description }}"\
"${{ github.event.client_payload.url }}"\
"${{ github.event.client_payload.sha256 }}"\
"${{ github.event.client_payload.version }}"
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'Update packages'
committer: GitHub <noreply@github.com>
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
signoff: false
branch: feature/update-package
branch-suffix: timestamp
delete-branch: true
title: '${{ github.event.client_payload.formula }} update for ${{ github.event.client_payload.version }}'
body: |
@${{ github.actor }}
${{ github.event.client_payload.formula }} update for ${{ github.event.client_payload.version }}
- name: Check Pull Request
run: |
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
スクリプトのやってることは単純でテンプレートの決まった位置に適当な値を置換してるだけです。
API経由で渡ってきた値は以下のように参照できます。
./scripts/update.js\
"${{ github.event.client_payload.formula }}"\
"${{ github.event.client_payload.description }}"\
"${{ github.event.client_payload.url }}"\
"${{ github.event.client_payload.sha256 }}"\
"${{ github.event.client_payload.version }}"
payloadに渡したjsonのキーで各値を参照出来ます。
アプリケーション側の設定
release.yaml
一部抜粋
update-homebrew:
needs: [create-release]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v1
with:
name: checksum-x86_64-apple-darwin
- id: checksum
run: |
echo "::set-output name=sha256::$(cat checksum-x86_64-apple-darwin/x86_64-apple-darwin.sum)"
- id: version
run: |
VERSION=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")
echo ::set-output name=version::$VERSION
- uses: peter-evans/repository-dispatch@v1
with:
token: ${{ secrets.PERSONAL_TOKEN }}
repository: uzimaru0000/homebrew-tap
event-type: update-brew
client-payload: '{ "formula": "tv", "description": "Format json into table view", "url": "https://github.com/uzimaru0000/tv/releases/download/${{ steps.version.outputs.version }}/tv-x86_64-apple-darwin.zip", "sha256": "${{ steps.checksum.outputs.sha256 }}", "version": "${{ steps.version.outputs.version }}" }'
注意点としては、別リポジトリにアクセスするので PERSONAL_TOKEN が必要になります。
client-payload
には先程指定した値を設定します。
おわり
こんな感じで設定するとリリースタグを打ったタイミングで更新をしてくれるので開発だけしてリリースが出来てないと言う状況を無くせます!
自動化できる所は自動化して良い開発ライフを送りましょ〜
Discussion