Swift Package Manager(SwiftPM)で作ったコマンドラインツールをHomebrewに登録する方法


[mono0926/LicensePlist](https://github.com/mono0926/LicensePlist) という、Carthage・CocoaPodsおよび手動指定ライブラリから、iOS設定アプリに載せられるplist形式のライセンスファイル群を生成するコマンドラインツールを作りまして、[それをHomebrewに登録した](https://github.com/mono0926/LicensePlist/releases/tag/1.3.4)のでやり方を共有します。

結果的にはまあまあ簡単ですが、Homebrew登録自体が初めてだったので、多少手こずりました。

# 前提

- Swift Package Manager(SwiftPM)で作ったコマンドラインツールのHomebrew登録
- 本家Homebrewリポジトリへの登録ではなく、`brew tap`方式
- [Bottles](https://github.com/Homebrew/brew/blob/master/docs/Bottles.md)という、バイナリ配布対応はスキップしたため、インストール先で都度ビルドが必要

# 対応手順

## Makefile を作成

本体のコマンドラインツールリポジトリで Makefile を用意します。SwiftPM ではHomebrew対応せずとも簡単なMakefileを用意するのがわりと一般的なので、Homebrew対応前からすでに作成済みだったりもすると思います。


```sh
PREFIX?=/usr/local

build:
	swift build -c release -Xswiftc -static-stdlib
	
install: build
	mkdir -p "$(PREFIX)/bin"
	cp -f ".build/release/LicensePlist" "$(PREFIX)/bin/license-plist"
```

`install`にて、次に作るFormulaファイルから渡す`$(PREFIX)`を使うように設定するのが肝です。そのディレクトリを生成してビルドされたバイナリファイルをコピーしています。`PREFIX`に`?=`で`/usr/local`を代入しているので、普通に`make install`した場合は、`/usr/local/bin`配下にコピーされます。

Homebrew対応の本筋とは外れますが、`swift build -c release -Xswiftc -static-stdlib` の `-Xswiftc -static-stdlib` オプションも大事です。標準ライブラリおよび依存ライブラリとstatic linkしてくれるので、単一実行バイナリが生成できます。
以下の記事で紹介されていました。このオプション指定以外にも大いに参考にさせていただきました🙇
[Building a command line tool using the Swift Package Manager](https://medium.com/@johnsundell/building-a-command-line-tool-using-the-swift-package-manager-3dd96ce360b1)


参考に、[こちらがLicensePlistのMakefileです](https://github.com/mono0926/LicensePlist/blob/master/Makefile)。

出来たら、Pushしてタグを打っておきましょう。以下では`1.3.5`タグを打ったとして説明していきます。

### [2019/04/13 追記]

#### macOS High Sierra以降では `--disable-sandbox` 指定が必要

- 参考: [Cannot install by `brew install license-plist` on Xcode 9 / High Sierra (currently beta) · Issue #71 · mono0926/LicensePlist](https://github.com/mono0926/LicensePlist/issues/71)

#### Xcode 10.2(Swift 5.0)以降では `-Xswiftc -static-stdlib` は不要(むしろ指定するとビルドエラー)

- [Swift 5.0でのABI安定化](https://swift.org/blog/abi-stability-and-more/)の影響
- 参考: [Build fails on Xcode 10.2(Swift 5.0) · Issue #102 · mono0926/LicensePlist](https://github.com/mono0926/LicensePlist/issues/102#issuecomment-480148508)

## Formulaファイル作成

https://github.com/mono0926/LicensePlist がコマンドラインツール本体ですが、それに対応する
https://github.com/mono0926/homebrew-license-plist という`brew tap`用のリポジトリを作成します本体のコマンドラインツールリポジトリがキャメルケースだったので、ちょっと揺れが出てしまっています…)。
今回は名前を合わせましたが、合わせなくても良いです。`mono0926/taps`などのリポジトリ名の方が今後他の時にも流用出来て良かったかも、と今思っています…。

まずは以下を実行してひな形を生成します。

```sh
$ brew create https://github.com/mono0926/LicensePlist/archive/1.3.5.tar.gz
```

https://github.com/mono0926/LicensePlist/archive/1.3.5.tar.gz 部分は、`リポジトリのURL`/archive/`タグ`.tar.gz という構成になっています。これは、タグを打つと、その時点のスナップショットとそのURLをGitHubが自動生成してくれるようになっています(Releasesに載っているダウンロードURLと同じです)。

`Editing /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/licenseplist.rb` というメッセージとともにひな形の編集画面が開きます。

ひな形はこうなっています。

```ruby
# Documentation: http://docs.brew.sh/Formula-Cookbook.html
#                http://www.rubydoc.info/github/Homebrew/brew/master/Formula
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!

class LicensePlist < Formula
  desc "A license list generator of all your dependencies for iOS applications"
  homepage ""
  url "https://github.com/mono0926/LicensePlist/archive/1.3.5.tar.gz"
  sha256 "bae83138b45e062cf1cfa8ee627d7cf0912147e56a9bd93f643b63125d6dd182"

  # depends_on "cmake" => :build

  def install
    # ENV.deparallelize  # if your formula fails when building in parallel

    # Remove unrecognized options if warned by configure
    system "./configure", "--disable-debug",
                          "--disable-dependency-tracking",
                          "--disable-silent-rules",
                          "--prefix=#{prefix}"
    # system "cmake", ".", *std_cmake_args
    system "make", "install" # if this fails, try separate make/make install steps
  end

  test do
    # `test do` will create, run in and delete a temporary directory.
    #
    # This test will fail and we won't accept that! It's enough to just replace
    # "false" with the main program this formula installs, but it'd be nice if you
    # were more thorough. Run the test with `brew test LicensePlist`. Options passed
    # to `brew install` such as `--HEAD` also need to be provided to `brew test`.
    #
    # The installed folder is not in the path, so use the entire path to any
    # executables being tested: `system "#{bin}/program", "do", "something"`.
    system "false"
  end
end
```

`PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!`と書かれた通り消しましょう(`brew tap`形式なので残ってても問題無いですが)。

```ruby
class LicensePlist < Formula
  desc "A license list generator of all your dependencies for iOS applications"
  homepage ""
  url "https://github.com/mono0926/LicensePlist/archive/1.3.5.tar.gz"
  sha256 "bae83138b45e062cf1cfa8ee627d7cf0912147e56a9bd93f643b63125d6dd182"

  def install
    system "./configure", "--disable-debug",
                          "--disable-dependency-tracking",
                          "--disable-silent-rules",
                          "--prefix=#{prefix}"
    system "make", "install"
  end

  test do
    system "false"
  end
end
```

そして、SwiftPMのコマンドラインツールをうまくビルドできるように編集して、以下が完成系です。

```ruby
class LicensePlist < Formula
  desc "A license list generator of all your dependencies for iOS applications"
  homepage ""
  url "https://github.com/mono0926/LicensePlist/archive/1.3.5.tar.gz"
  sha256 "bae83138b45e062cf1cfa8ee627d7cf0912147e56a9bd93f643b63125d6dd182"

  def install
    system "make", "install", "PREFIX=#{prefix}"
  end

  depends_on :xcode => ["8.3", :build]
end
```

先ほどの`Makefile`で参照できるように、`install`メソッドで`, "PREFIX=#{prefix}"`にて`PREFIX`環境変数を渡すのが肝です。ちなみに、以下のようなログが出て、`/usr/local/Cellar/license-plist/1.3.5`がセットされていることが分かります。

```sh
$ make install PREFIX=/usr/local/Cellar/license-plist/1.3.5
```

testは使わずで良いと思ったのでカットしましたが、必要に応じて記述してください。

`depends_on`は無くとも動きますが、 @ikesyo さんに[指摘していただき](https://github.com/mono0926/homebrew-license-plist/issues/1)、付け足しました。あった方が親切ですね。

うまくインストールできるか試します(ローカルではFormula以下にあるので普通にインストールできます)。

```sh
$ brew install license-plist
```

また。`ls -l`で詳細情報を見ると、先ほどの`PREFIX`配下に実態があって、シンボリックリンクが張られていることが分かります。

```sh
$ ls -l /usr/local/bin/license-plist
$ lrwxr-xr-x  1 mono  admin  47 May 20 20:51 /usr/local/bin/license-plist -> ../Cellar/license-plist/1.3.5/bin/license-plist
```

うまくいったら、以下のように`brew tap`用のリポジトリに移動してPushしましょう。

```sh
$ mv /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/licenseplist.rb "homebrew-license-plist をチェックアウトした場所"
```

[こちらがhomebrew-license-plistのFormulaです](https://github.com/mono0926/homebrew-license-plist/blob/master/license-plist.rb)。

## 確認

以下のコマンドを実行して、インストール成功すればOKです。エラー出たら、解決がんばりましょう💪

```sh
$ brew install mono0926/license-plist/license-plist
```

`オーナー名`/`brew tap用に作ったリポジトリからhomebrew-の接頭辞を省いた名前`/`Formula名`という構成です。
ちなみに、以下の短縮形です。

```sh
$ brew tap mono0926/license-plist
$ brew install license-plist
```

## アップデート作業

本体リポジトリでのリリースの度に次のような操作が必要です。

1. 本体リポジトリでタグを打つ
2. brew tap用のリポジトリで`url`に記述してあるタグ部分を書き換え、さらに `sha256` も更新してPush
3. `brew upgrade license-plist`を実行して確認

> さらに `sha256` も更新

これが地味に面倒なのですが、愚直にダウンロードしてsha256計算するしか無いのですかね？🤔良いやり方ご存じでしたら教えてください🙇

とりあえず以下のスクリプトを作ったので、`./copy_sha256.sh 1.3.5`を実行するとダウンロード・sha256の計算に加えて、さらにクリップボードにコピーまでしてくれて面倒さは解消しました(スクリプト用意しなくて良い楽な方法があれば不要になってしまいますが…)。


```sh
if [ $# -eq 0 ]; then
    echo "A tag argument is needed!(ex: ./copy_sha256 1.2.3)"
    exit 1
fi
tag=$1
echo "Tag: '${tag}'"
filename="${tag}.tar.gz"
echo "Filename: '${filename}'"
curl -LOk "https://github.com/mono0926/LicensePlist/archive/${filename}"
result=$(shasum -a 256 $filename)
echo "Result: '${result}'"
sha256=$(echo ${result} | cut -d ' ' -f 1)
echo $sha256 | tr -d '\n' | pbcopy
echo "sha256('${sha256}') was copied to your clipboard🎉"
rm $filename
```

今後の更新作業がスムーズになるように[これもリポジトリに含めました](https://github.com/mono0926/homebrew-license-plist/blob/master/copy_sha256.sh)。[アップデート更新作業全体を自動化したいなどとも思っています](https://github.com/mono0926/LicensePlist/issues/27)。


# その他

## Bottles対応を省いた理由

単純に[Bottles](https://github.com/Homebrew/brew/blob/master/docs/Bottles.md)やその他解説記事の通りにやってみたら、各プラットフォーム向けにバイナリファイル生成したり、さらにそれを更新していくのが手間(手動にしても自動スクリプト書くにしても)だと思ったからです。

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a href="https://twitter.com/_mono">@_mono</a> 初回は苦戦した記憶がありますが、1度やれば簡単だった気がします。参考になるかわかりませんが、当時のメモを貼っておきますね。 <a href="https://t.co/OBZtRlwl1S">https://t.co/OBZtRlwl1S</a></p>&mdash; ishkawa (@_ishkawa) <a href="https://twitter.com/_ishkawa/status/863658235869265920">May 14, 2017</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a href="https://twitter.com/_mono">@_mono</a> それは試したことないですね。自分はbrewで入ればなんでも良いか〜って思ってCarthageの真似で済ませちゃいました。</p>&mdash; ishkawa (@_ishkawa) <a href="https://twitter.com/_ishkawa/status/863662412196597760">May 14, 2017</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

Bottles対応を省くと以下のデメリットはあります。

- インストール・アップデート時に、ビルド時間が少しかかる
	- Bottles対応しておけばバイナリファイルダウンロード時間だけで済む
- 過去バージョンのバイナリをインストールできず、常に最新版のタグになってしまう

過去の版欲しい場合(デグレなどで最新版で動かなくなってしまった時など)は、一応[Releases](https://github.com/mono0926/LicensePlist/releases)からダウンロードする方法も提供していますし、あるいは過去のタグでビルドしてもらっても一応何とかなるので、まあ良いかなと今のところ思っています。


## 参考リンク

- SwiftPM
	- [Building a command line tool using the Swift Package Manager](https://medium.com/@johnsundell/building-a-command-line-tool-using-the-swift-package-manager-3dd96ce360b1)
	- [Swift Package Manager (SwiftPM) で作るコマンドラインツール - Qiita](http://qiita.com/mono0926/items/e8fdd97115780204f797)
- Homebrew
	- [Swiftで書いたコマンドラインツールをHomebrewでインストールできるようにする](http://blog.ishkawa.org/2015/02/18/1424261348/)
		- これは`SwiftPM`ではなく`xcodebuild`を使ったものですが、それ以外はほぼ同じです
	- [Homebrewで自作のプロジェクト（ライブラリ）を公開する - Qiita](http://qiita.com/mountcedar/items/f10161d4e483aa8c1283)
		- Bottles対応

## [LicensePlist](https://github.com/mono0926/LicensePlist)のロゴ

ロゴ作っちゃいました(　´･‿･｀)

[![LicensePlist.png](https://qiita-image-store.s3.amazonaws.com/0/19398/1ae902cb-0446-10af-24a3-0746b7361dd0.png)](https://github.com/mono0926/LicensePlist)

設定アプリに載るライセンスファイル一覧生成なので、設定アプリアイコンっぽい感じにして、さらに`Copyright @ mono`でライセンスっぽさを加えました(　´･‿･｀)