🛠️

Swift Package Manager で作った Command Line Tool を Homebrew で配布する

に公開1件

応用編でCommand Line Toolが作れるようになったと思うので、Homebrewで配布してみましょう。

1. SPM製のCommand Line ToolのリポジトリでTagを打ってReleaseノートを公開する

git tag 1.0.0
git push origin 1.0.0

Publish Release Note

公開すると、Assetsに自動的にリポジトリのスナップショットがアーカイブされます。特にtar.gzの方が重要です。このリンクは https://github.com/org/repo/archive/refs/tags/1.0.0.tar.gz のようになっています。

Assets

2. Homebrewで配布する時に参照されるFormulaファイルを設置する専用のリポジトリを作る

作ったCommand Line Toolのリポジトリとは別に、HomebrewのFormulaファイルを設置するためのリポジトリを作ります。これは自作のツールをHomebrewで公開する時に毎回使うものになります。

リポジトリ名は homebrew- というプレフィックスで始まる必要があります。よくあるパターンだと homebrew-tap にするっぽいです。

3. Formulaファイルを作ってReleaseノートのアーカイブと紐づける

2で作ったリポジトリをcloneして作業を進めます。
ここで、1のtar.gzのリンクが必要です。

git clone git@github.com:org/repo.git
cd repo
echo ".DS_Store" > .gitignore
mkdir Formula
brew create https://github.com/org/repo/archive/refs/tags/1.0.0.tar.gz

コンソールでFormula名を聞かれるので入力します。(ここではrepoとします。)
そうするとデフォルトのエディタでrepo.rbが開かれるので編集します。

class Repo < Formula
  desc "A command line tool to do something."
  homepage "https://github.com/org/repo"
  url "https://github.com/org/repo/archive/refs/tags/1.0.0.tar.gz"
  sha256 "d0df02c78a4d406a4574feba074f3da886f6f05a1f77f14887233005457cf04b"
  license "MIT"
  head "https://github.com/org/repo.git", branch: "main"

  depends_on xcode: ["26.0", :build]

  uses_from_macos "swift" => :build

  def install
    args = if OS.mac?
      ["--disable-sandbox"]
    else
      ["--static-swift-stdlib"]
    end
    system "swift", "build", *args, "--configuration", "release"
    bin.install ".build/release/repo"
  end

  test do
    system "false"
  end
end
  • desc: リポジトリにDescriptionが指定されていれば自動で入力される
  • head: デフォルトブランチを指定
  • depends_on xcode: ビルドに使用するXcodeの下限バージョンを指定
  • uses_from_macos "swift": swiftコマンドを使うことを宣言
  • def install: インストール方法を記述
    要はtar.gzを解凍してswift buildした成果物をコピーしている
  • test do: インストールしたツールがちゃんと動いていることを確認するシェルスクリプトを書く
    system "false"のままでも配布自体はできる

これで出来上がったrepo.rbをFormulaディレクトリの中にコピーしてプッシュします。
ここまでで上手くいっていればHomebrew経由でインストールできるようになります。

brew tap org/tap
brew install org/tap/repo

同名のツールがHomebrew/Coreになければbrew install repoでもいけます。
内容を修正してプッシュしなおした場合はbrew untap org/tapしてからタップし直してください。

4. 【任意】Bottle向けのアーカイブを作ってインストールのコストを下げる

1~3の方法だとインストールする時にそれぞれの手元でツールをビルドすることになります。するとインストールに時間がかかってしまうので、アーカイブ済みのバイナリを配った方がコストが下がります。HomebrewにはBottleという仕組みがあって、アーカイブ済みのバイナリを紐づけられます。

brew uninstall org/tap/repo
brew install --build-bottle org/tap/repo
brew bottle --root-url https://github.com/org/reop/releases/download/1.0.0 org/tap/repo
==> Determining org/tap/repo bottle rebuild...
==> Bottling repo--1.0.0.arm64_tahoe.bottle.1.tar.gz...
./repo--1.0.0.arm64_tahoe.bottle.1.tar.gz
  bottle do
    root_url "https://github.com/org/reop/releases/download/1.0.0"
    rebuild 1
    sha256 cellar: :any_skip_relocation, arm64_tahoe: "4a237e30a4c8463d36d6aa766b00dbb87e69ccd731102ba2fc0a0e3a87690a91"
  end

上記を実行すると、コマンドを実行したディレクトリに repo--1.0.0.arm64_tahoe.bottle.1.tar.gz のようなファイルが出力されているのでこれをツールのリポジトリのリリースノートにAssetとしてアップロードします。ただ、不具合なのかわからないのですが、そのままの名前ではフェッチできないのでリネームします。

ハイフンが2つ連なっていたら一つにして、bottleの後ろにrebuild番号が不要なら消します。
Before: repo--1.0.0.arm64_tahoe.bottle.1.tar.gz
After: repo-1.0.0.arm64_tahoe.bottle.tar.gz

そして、brew bottleコマンドを実行した時に出力されていたDSLをrepo.rbに追記します。
このとき、rebuild番号が不要なら消してOKです。

class Repo < Formula
  desc "A command line tool to do something."
  homepage "https://github.com/org/repo"
  url "https://github.com/org/repo/archive/refs/tags/1.0.0.tar.gz"
  sha256 "d0df02c78a4d406a4574feba074f3da886f6f05a1f77f14887233005457cf04b"
  license "MIT"
  head "https://github.com/org/repo.git", branch: "main"

+  bottle do
+    root_url: "https://github.com/org/reop/releases/download/1.0.0"
+    sha256 cellar: :any_skip_relocation, arm64_tahoe: "4a237e30a4c8463d36d6aa766b00dbb87e69ccd731102ba2fc0a0e3a87690a91"
+  end

  depends_on xcode: ["26.0", :build]

  uses_from_macos "swift" => :build

  def install
    args = if OS.mac?
      ["--disable-sandbox"]
    else
      ["--static-swift-stdlib"]
    end
    system "swift", "build", *args, "--configuration", "release"
    bin.install ".build/release/repo"
  end

  test do
    system "false"
  end
end

追記したら改めてプッシュして、タップし直せばBottleからインストールができるようになると思います。

Discussion

KyomeKyome

バージョンアップする方法

  1. 新しいバージョンのtagを打ってReleaseを公開
  2. Releaseのtar.gzのリンクをコピー
  3. sha256を計算して取得
    curl -L tar.gzのリンク | shasum -a 256
    
  4. homebrew-tapのFormula内のurlとsha256を更新してコミット
  5. bottleの更新(任意)
    brew uninstall org/tap/repo
    brew update
    brew tap-info org/tap
    brew info org/tap/repo #新しいバージョンになっていればOK
    brew install --build-bottle org/tap/repo
    brew bottle --root-url https://github.com/org/repo/releases/download/新しいバージョン org/tap/repo
    
  6. 出力されたtar.gzを適切にリネームしてReleaseのAssetにアップロード
  7. 出力されたbottleをFormula内に記述してコミット