自作 CLI を brew と scoop で配る ― GoReleaser でまとめて
シリーズ・第 4 回 — 「個人で出版ツールを作って配るまで」(全 5 回予定)。前回は多言語サイトの設計でした。一覧は シリーズの記事一覧 から。今回のテーマは「brew と scoop で配る実務」です。
CLI ができたら、次は「どう配るか」です。go install でも配れますが、Homebrew や Scoop で入れられた方が、使う人にはずっと楽です。
とはいえ、mac・Windows・Linux 向けに手でビルドして、Homebrew の formula と Scoop の manifest を手書きして…は、リリースのたびにやっていられません。そこを GoReleaser に一括でやらせた話です。ひとつ、macOS で素直に詰まったところ(署名)も含めて。
GoReleaser に一括でやらせる
GoReleaser は、設定ファイル 1 枚から次を全部やってくれます。
- mac・Windows・Linux × amd64・arm64 のバイナリをビルド
- Homebrew formula と Scoop manifest を生成
- それらを公開リポジトリ(tap / bucket)へ push
リリースは、git tag を切って 1 コマンドです。
git tag v0.1.0
GITHUB_TOKEN=$(gh auth token) goreleaser release --clean
公開前に試すなら、push しない snapshot モードで中身を確認できます。
goreleaser release --snapshot --clean # dist/ に生成するだけ。公開はしない
バージョンは git tag を単一の出どころに
地味に大事なのが、バージョンの一致です。バイナリが名乗る版・ダウンロード URL・パッケージの版がバラバラだと、事故のもとになります。
GoReleaser では、git tag を ldflags でバイナリに焼き込めます。出どころを tag ひとつにすれば、全部が揃います。
builds:
- ldflags:
- -s -w -X your/module/internal/cli.Version={{.Version}}
これで crofty version が表示する版も、配布物の版も、同じ tag から来ます。
macOS の壁:cask をやめて formula に
macOS への配布で素直に詰まりました。最初は Homebrew の cask で配ろうとしたのですが、未署名のバイナリが毎回 Gatekeeper に弾かれるのです(「開発元を確認できません」のポップアップ)。
原因は隔離属性でした。cask は、ダウンロードした配布物に隔離(quarantine)を付けます。未署名だと、それが初回起動で Gatekeeper に引っかかる。警告を消すには Apple Developer ID での署名と notarization が要りますが、個人で配るには重い手続きです。
そこで cask をやめ、formula で配ることにしました。formula はダウンロードに隔離を付けないので、未署名のバイナリでも警告なしで動きます。
| cask | formula(採用) | |
|---|---|---|
| ダウンロードの隔離属性 | 付く | 付かない |
| 未署名バイナリの初回起動 | Gatekeeper がブロック | そのまま動く |
| 警告を消す条件 | Apple 署名+notarization | 不要 |
GoReleaser 側は brews: に tap(formula を置くリポジトリ)を指定するだけです。
brews:
- name: crofty
repository:
owner: <your-github>
name: homebrew-crofty
dependencies:
- name: hugo
Windows は Scoop、依存も宣言する
Windows では、Scoop が Homebrew にあたります。GoReleaser が manifest を作って、bucket リポジトリへ push してくれます。
もうひとつ大事なのが、依存の宣言です。crofty は内部で Hugo を使うので、入れたときに Hugo も一緒に入ってほしい。Homebrew は dependencies、Scoop は depends で宣言します。
scoops:
- name: crofty
repository:
owner: <your-github>
name: scoop-crofty
depends:
- main/hugo-extended
これで brew install / scoop install の一発で、ランタイムごと揃います。
まとめ
個人 OSS の配布は、こう落ち着きました。
- GoReleaser に一括 — 全 OS のビルドとマニフェスト生成・push を、tag 一発で
- バージョンは git tag が単一ソース — ldflags で焼き込む
- macOS は formula — cask だと、未署名がポップアップに弾かれる
- 依存はマニフェストで宣言 — ランタイムごと入る
署名まわりは個人にはまだ重いですが、ここまでで「tag を切れば配れる」状態にはなりました。
← 前の記事:多言語サイトの設計 | 次の記事:AI が動かす前提で CLI を設計する →