Homebrewで自分のスクリプトを配布する
(justin.searls.co)- HomebrewはmacOSでCLIツールを簡単にインストール・管理できるパッケージマネージャーであり、開発者がよく使うツールを通じてシステム環境を効率よく構成できる
- このガイドではHomebrewを使って個人のCLIスクリプトを配布する流れを説明し、GitHub連携と自動化ワークフローによって保守を簡素化する方法を示す
- 配布プロセスはCLIの作成 → GitHubリリース → Tap作成 → Formula作成と更新という流れで進み、最終的には
brew tapとbrew installコマンドだけでインストールできる - Homebrewの用語体系とベストプラクティスを理解すれば、再現性とサプライチェーンセキュリティを強化した安定的な配布が可能になる
- GitHub Actionsワークフローで自動化でき、一度設定すればその後ほかのCLIの配布も非常に簡単になる利点がある
背景と動機
- HomebrewはCLIツールのインストール時に好まれるパッケージマネージャーで、多くの開発者に使われている
- しかし、自作CLIはnpmやRubyGemで配布されることが多く、Homebrewでの配布方式は手順がなじみにくく感じられることがある
- Homebrewの公式coreリポジトリでは自作ツールの登録にHomebrewチームが消極的な方針のため、一般開発者は別個のtapとformulaで配布する
- 本ガイドはシンプルなRubyベースCLIの配布経験をもとに説明する
用語説明
- Homebrewはビール醸造テーマを反映した独特の用語を使うため、これを理解するとシステム構造を把握しやすい
- Formulaはパッケージ定義ファイルで、ソースコードやバイナリをインストールするための手順を含む
- TapはFormula群のGitリポジトリで、ユーザー単位または組織単位でカスタムパッケージを管理する
- CaskはGUIアプリや大型バイナリをインストールするためのマニフェストで、Formulaと似ているが事前ビルド済みファイルを扱う
- Bottleはソースからビルドせず事前ビルド済みバイナリパッケージをコピーする形で、インストール速度を高める
- Cellarはインストール済みFormulaが置かれるディレクトリで、たとえば
/opt/homebrew/Cellarというパスを持つ - Kegは特定Formulaのインストールインスタンス用ディレクトリで、Cellar内にバージョンごとに配置される
概要
- Homebrewのコアリポジトリはニッチなものや個人提出のコンテンツを受け付けないため、ユーザーは別途tapリポジトリを作ってCLIを配布する必要がある
- 1. CLIを作成してGitHubに置き、タグ付きリリースを作る
- 2.
brew tap-newでTapを作成してGitHubにpushする - 3.
brew createでFormulaを作成する(tarball URLとSHA256を含む) - 4. 新バージョンをリリースするたびにFormulaを更新し、ユーザーが
brew installコマンドで簡単にインストールできるようにする
- 配布完了後、ユーザーは2つのコマンドでCLIをインストールできる:
brew tap your_github_handle/tapとbrew install your_cool_cli- このガイドではCLI開発そのものは省略し、tap作成、Formula作成、更新手順に焦点を当てる
- 例として、iMessageデータベースからインタラクティブなWebアーカイブを作る
imsgCLIを使う
tapの作成
- Homebrewのtap作成ガイドに従い、GitHubのユーザー名や組織名に置き換えて設定する
- 今後すべてのCLIツールを1つのtapにまとめるため、名前は
homebrew-tapを推奨する。homebrew接頭辞はCLIで特別扱いされ、tap接頭辞は慣習的である
- 今後すべてのCLIツールを1つのtapにまとめるため、名前は
- tap作成コマンドを実行:
brew tap-new searlsco/homebrew-tap- これにより
/opt/homebrew/Library/Taps/searlsco/homebrew-tapにスキャフォールドが生成される - 対応するGitHubリポジトリを作成し、生成された内容をpushする:
cd /opt/homebrew/Library/Taps/searlsco/homebrew-tap,git remote add origin git@github.com:searlsco/homebrew-tap.git,git push -u origin main
- これにより
- tapを所有した後は、ほかのユーザーが
brew tap searlsco/tapコマンドでそのリポジトリをクローンし、/opt/homebrew/Library/Tapsに配置できる- 初期状態では有用な内容はないが、基本動作は確認できる
Formulaの作成
- HomebrewはGitHubリポジトリを直接参照できるが、バージョン付きtarballとチェックサムの使用を推奨しており、再現性とオープンソースのサプライチェーンセキュリティを強化できる
- GitHubはタグをpushすると予測可能なURLのtarballをホストする。例:
imsgリポジトリでgit tag v0.0.5,git push --tagsを実行すると、https://github.com/searlsco/imsg/archive/refs/tags/v0.0.5.tar.gzが生成される
- GitHubはタグをpushすると予測可能なURLのtarballをホストする。例:
- Formula作成コマンド:
brew create https://github.com/searlsco/imsg/archive/refs/tags/v0.0.5.tar.gz --tap searlsco/homebrew-tap --set-name imsg --ruby--tapフラグはカスタムtapを指定し、Formulaを/opt/homebrew/Library/Taps/searlsco/homebrew-tap/Formulaに配置する--set-name imsgはFormula名を明示的に設定するもので、名前の重複を避けるためユニークに選ぶべきである(例: 既存のTLDRやstandard CLIとの衝突に注意)--rubyはRuby CLI向けのテンプレートプリセットで、カスタマイズを簡素化する複数オプションの1つである
- 生成されたFormulaは最初は動かないことがあるため、LLMを活用して修正する:
brew install --verbose imsgを実行し、エラーをChatGPTに入力してFormula更新を繰り返す- 最終的なFormula/imsg.rbファイルは、Ruby CLI配布の出発点としてコピー可能
- 言語固有のパッケージマネージャーではなくHomebrew経由で配布すれば、実装言語を変更してもユーザーのアップグレードは円滑に行える
Formulaの主なポイント
- すべてのFormulaはRubyで書かれている。これはJavaScriptやAI以前に人気だった開発ツールの多くがRubyベースだったためである
headメソッドでGitリポジトリを指定できるが、実際の効果は不明livecheckを追加するとFormulaのバージョン更新が容易になり、価値がある- バイナリ実行テストはヘルプ出力の確認で簡単に実装でき、生成されたコメントに気後れする必要はない
brew style searlsco/tapコマンドでスタイルエラーを確認できる--rubyテンプレートのデフォルトであるuses_from_macos "ruby"は2.6.10版(COVID以前のリリースで、3年前にEOL)を使うため、depends_on "ruby@3"で最新のruby Formulaへの依存を推奨する
- Formulaに満足できたら
git pushで本番配布し、ユーザーはbrew tap searlsco/tapとbrew install imsgでインストールできる
各CLIリリースごとのFormula更新
- Formula先頭の
urlとsha256ハッシュをリリースごとに手動更新するのは面倒で、タグのpushやGitHubリリース作成ですら負担だと指摘している- Homebrewの
bump-formula-prコマンドやGitHub ActionsでPRを作ることもできるが、forkとPRのプロセスは不必要に複雑 - tapの所有者であれば、mainブランチへ直接コミットする簡単な方法が望ましい
- Homebrewの
- これを避けるため、FormulaリポジトリにGitHubワークフローを追加し、リリース時にtapを自動更新することを推奨する
- ワークフロー例をコピーして使える
- 設定が必要: GitHub個人アクセストークン(PAT)を作成する際、
homebrew-tapリポジトリにContent→Write権限を付与し、FormulaリポジトリのSecretsにHOMEBREW_TAP_TOKENとして保存する - 環境変数でtapとFormulaを指定する(例: 13〜15行目)
- GitHubボットアカウントでの更新を推奨:
GH_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com,GH_NAME: github-actions[bot]
- リリース作成後に
git push --tagsを実行すれば、数秒以内に自動更新され、ユーザーはbrew updateとbrew upgrade imsgでアップグレードできる
いちばん良い点
- このプロセスは複雑だが、tapの設定と1つのFormula例が完成すれば、追加のCLI配布はほぼ些細な作業になる
- 数分で新しいFormulaを公開できて便利
- Homebrewの公式プロセスはやや複雑だが、自動化によって楽になる
- 各ツールのリリースから配布までの煩雑さを減らし、さまざまな言語のCLIにも拡張して対応できる
- 実際に別のFormulaを公開するかは未定だが、その可能性が開けたことに満足している
2件のコメント
--no-forkオプションがあるため、ブランチを直接 push してマージでき、自動更新機能も提供しています。Hacker Newsのコメント
Homebrewの命名規則は時々ややこしく感じることもあるが、全体として本当に便利なツールだと改めて感じている。
また、自分のtapを作ってツールを配布する手順が、こんなに簡単だとは思わなかった。
言語ごとのパッケージマネージャー(例: uv)と比べて、どんな点が優れているのか気になる。
特に、特定のエコシステムに属していない人にとってより簡単なのか、つまり汎用性の面で優位があるのか知りたい。
感謝を伝えるとともに、パッケージレジストリを使う他のツールはたいていアカウント作成、2段階認証、署名プロセスなどが必要になる。
HomebrewではGitHubの利用規約(ToS)が信頼の基盤として機能するため、全体的にずっと簡素化されている。
Homebrewチームもこの方式のおかげで多くの複雑さを減らせている。
Pythonパッケージの観点で言うと、uvのようにすべてを一度にパッケージ化しようとする試みは、現実的には難しい。
そのため、一般的には
venv環境に固定された依存関係だけをインストールする方式が使われる。具体例としてはこのformulaを参照できる。
uvについては、公式ツール(
brew update-python-resources,homebrew-pypi-poet)でプライベートパッケージをサポートしようとしたがうまくいかなかったため、自分でuvbrewを作ってリソース生成を支援するようにした。
HomebrewでPython formulaを書く際の参考として公式ドキュメントがある。
Go開発者ならGoreleaserというツールをおすすめする。
個人tap内でバイナリ配布をとても簡単にしてくれる(公式coreでは禁止されている方式)。
各言語のプロジェクト管理で活用しやすい。
個人的には、tap側で直接アップデートを管理するほうがより理想的だと思う。
一般的なupstreamでの更新方式に近い。
このワークフローを参考にすれば、自分が所有していないformula/caskも簡単に更新できる。
brew bumpコマンドですべてをスキャンし、PRを作成しつつbrew test-botでテストまで自動化できる。実際のPR例はこちらで確認できる。
普段はGitHub Actionsの利用時間が気になってなかなか手が出なかったが、オープンソースなら無料なので、こういう使い方も良さそうだ。
自分のHomebrew tap向けのバージョン自動bumpワークフローとして、homebrew-bump-revisionを自作してみた。
複数の個人プロジェクトでうまく使っている。
自分は面倒で試していないが、いいツールだ。
Ruby Roguesポッドキャストで、HomebrewでCLIを配布するときのさまざまなコツを扱ったエピソードがあった。
関連エピソードのリンクでさらに詳しく聞ける。
Pythonツールのパッケージングについて興味深い点を見つけた。
一部のPythonパッケージはビルド過程で依存関係ループが発生し、Homebrewと互換性がない場合がある。
pipはバイナリリリースをダウンロードするので問題ないが、Homebrewは依存関係まですべて自前でビルドするため、この工程にはずっと時間がかかる。
そのため、中規模のPythonプロジェクトでも"bottle"ビルドに1時間以上かかることがある。
システム管理のためにnixを使い始めて以来、一度も後悔したことはない。
唯一惜しいのは、マルチプレイヤーゲームのためにWindowsへ依存しなければならない点だけだ。